GithubHelp home page GithubHelp logo

kailuowang / henkan Goto Github PK

View Code? Open in Web Editor NEW
268.0 8.0 10.0 218 KB

A small library for converting between case classes.

License: Other

Scala 100.00%
scala typelevel conversion transformation shapeless

henkan's Introduction

Continuous Integration Latest version

Henkan [変換]

A small library for converting between case classes.

Modules

henkan.convert

Transform between case classes, which minimize the need to manually using constructor to transform information from one case class to another.

Features:

  1. quick transformation when the source case class has all the fields the target case class has: e.g. a.to[B]()

  2. supplement (if source case class doesn't have a field) or override field values. e.g. a.to[B].set(foo = "bar")

  3. use the default values of the target case classes if needed

henkan.optional

Conversion between case classes with optional fields and case class with required fields. One of the use cases for such conversions is conversion between scalaPB generated classes where most fields are Options and internal case classes where you have required fields.

Get started

Henkan is available on Scala 2.12, 2.13 as well as Scala.js

 libraryDependencies += "com.kailuowang" %% "henkan-convert" % "0.6.5"

 libraryDependencies += "com.kailuowang" %% "henkan-optional" % "0.6.5"

Examples

Transform between case classes

import java.time.LocalDate

case class Employee(name: String, address: String, dateOfBirth: LocalDate, salary: Double = 50000d)

case class UnionMember(name: String, address: String, dateOfBirth: LocalDate)

val employee = Employee("George", "123 E 86 St", LocalDate.of(1963, 3, 12), 54000)

val unionMember = UnionMember("Micheal", "41 Dunwoody St", LocalDate.of(1994, 7, 29))

Now use the henkan magic to transform between UnionMember and Employee

import henkan.convert.Syntax._
// import henkan.convert.Syntax._

employee.to[UnionMember]()
// res0: UnionMember = UnionMember(George,123 E 86 St,1963-03-12)

unionMember.to[Employee]()
// res1: Employee = Employee(Micheal,41 Dunwoody St,1994-07-29,50000.0)

unionMember.to[Employee].set(salary = 60000.0)
// res2: Employee = Employee(Micheal,41 Dunwoody St,1994-07-29,60000.0)

Missing fields will fail the compilation

case class People(name: String, address: String)
// defined class People

val people = People("John", "49 Wall St.")
// people: People = People(John,49 Wall St.)
scala> people.to[Employee]() //missing DoB
<console>:20: error: 
    You have not provided enough arguments to convert from People to Employee.
    shapeless.HNil

       people.to[Employee]() //missing DoB
                          ^

Wrong argument types will fail the compilation

scala> unionMember.to[Employee].set(salary = 60) //salary was input as Int rather than Double
<console>:20: error: 
    You have not provided enough arguments to convert from UnionMember to Employee.
    shapeless.labelled.FieldType[Symbol @@ String("salary"),Int] :: shapeless.HNil

error after rewriting to henkan.convert.Syntax.henkanSyntaxConvert[UnionMember](unionMember).to[Employee].set.applyDynamicNamed("apply")(scala.Tuple2("salary", 60))
possible cause: maybe a wrong Dynamic method signature?
       unionMember.to[Employee].set(salary = 60) //salary was input as Int rather than Double
                                   ^

Transform between case classes with optional field

cats.optional provides some facility to transform between case classes with optional fields and ones with required fields. Suppose you have two case classes: Message whose fields are optional and Domain whose fields are required

case class Message(a: Option[String], b: Option[Int])
case class Domain(a: String, b: Int)

You can validate an instance of Message to a Validated Domain

import cats.data.Validated
import cats.implicits._
import henkan.optional.all._
validate(Message(Some("a"), Some(2))).to[Domain]
// res0: henkan.optional.ValidateFromOptional.Result[Domain] = Valid(Domain(a,2))

validate(Message(Some("a"), None)).to[Domain]
// res1: henkan.optional.ValidateFromOptional.Result[Domain] = Invalid(NonEmptyList(RequiredFieldMissing(b)))

The compilation will fail if the from case class doesn't have all fields the target case class needs

case class MessageWithMissingField(a: Option[String])
scala> validate(MessageWithMissingField(Some("a"))).to[Domain]
<console>:24: error: Cannot build validate function from MessageWithMissingField to Domain, possibly due to missing fields in MessageWithMissingField or missing cats instances (`Traverse` instances are needed to convert fields in containers)
       validate(MessageWithMissingField(Some("a"))).to[Domain]
                                                      ^

You can convert in the opposite direction as well

from(Domain("a", 2)).toOptional[Message]
// res3: Message = Message(Some(a),Some(2))

Note that if you from case class does not have all the fields the target class has, they will be set as None

case class DomainWithMissingField(a: String)
scala> from(DomainWithMissingField("a")).toOptional[Message]
res4: Message = Message(Some(a),None)

henkan.optional supports nested case classes as well.

Note that if you are converting scalaPB generated case class, it generates Seq for repeated items, although the underlying implementation is actually List. henkan.optional.all has a Traverse instance for Seq but only works fine when the underlying implementation is either a List or Vector

Other examples can be found in examples including a typesafe config transformer

Contributors and participation

henkan is currently maintained by Kailuo Wang.

Any form of contribution (issue report, PR, etc) is more than welcome.

The henkan project supports the Typelevel code of conduct and wants all of its channels (Gitter, GitHub, etc.) to be welcoming environments for everyone.

License

henkan is licensed under the Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Special Thanks

The convert was originally adapted from this gist by @joprice.

henkan's People

Contributors

joprice avatar kailuowang avatar nrjais avatar scala-steward avatar scala-steward-bot-kailuowang[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

henkan's Issues

Support dropping fields during conversion

Given

case class A(foo: String, bar: String)
case class B(foo: String)

I'd like to "shrink" an instance of A to B, but I get an error

val a = A("hey", "ho")
val b = a.to[B]() // One of more fields of <shapeless repr of A> is not present in B

Is this already possible somehow?

If not, I would propose something like

val a = A("hey", "ho")
val b = a.to[B].drop('bar)

toOptional not working with a nested case class

Seems to be related related to #43
Tested with version 0.6.2
This snippet:

import henkan.optional.all._

object HenkanTest extends App {

  case class Foo(value: String)
  case class Domain(foo: Foo)
  case class Message(foo: Option[Foo])

  val dom = Domain(Foo("123"))
  val msg = Message(Some(Foo("123")))
  validate(msg).to[Domain] // works
  from(dom).toOptional[Message] // fails to compile

}

Results in the following error messages:

Error:(14, 23) Cannot build toOptional conversion from HenkanTest.Domain to HenkanTest.Message, possibly due to missing cats instances (`Functor` instances are needed to convert fields in containers)
  from(dom).toOptional[Message] // fails to compile
Error:(14, 23) not enough arguments for method toOptional: (implicit t: henkan.optional.ToOptional[HenkanTest.Domain,HenkanTest.Message])HenkanTest.Message.
Unspecified value parameter t.
  from(dom).toOptional[Message] // fails to compile

Custom transformer/converter

Would it be possible to provide a custom transformer/converter that would be used for nested field?

Our usecase is that ScalaPB prduces case classes with ByteString fields, but in our domain case classes we use ByteVector (from scodec). So we would like to be able to provide a ByteString => ByteVector trasformer which would enable converting from ScalaPB to domain.

It would be analogous to Chimney's Transformer

Error when the last field is a nested class

I meet an error when transform a typesafe config to a case class below.

case class Weapon(t: String, attack: Int, defense: Int)
case class Weapon2(t: String, attack: Int)
case class Person(name: String, weapon: Weapon, life: Long)
case class WWeapon(weapon: Weapon)
case class Person2(name: String, life: Long, weapon: Weapon)
case class WWeapon2(weapon: Weapon2)

Class Weapon has two fields: attack and defense. Both are Int. It may cause some problems:

val conf: Config
extract Option, Weapon // OK
extract Option, Weapon2 // OK
extract Option, Person // OK
extract Option, WWeapon // ERROR
extract Option, Person2 // ERROR
extract Option, WWeapon2 // ERROR
implicitly[Extractor[Option,Config,Weapon]] // OK
implicitly[Extractor[Option,Config,Weapon2]] // OK
implicitly[Extractor[Option,Config,Person]] // OK
implicitly[Extractor[Option,Config,WWeapon]] // ERROR
implicitly[Extractor[Option,Config,Person2]] // ERROR
implicitly[Extractor[Option,Config,WWeapon2]] // ERROR

scala 2.11, java 7, henken 0.1.0

Overriding optional fields in a case class

I'm having trouble converting between case classes that contain some optional fields and overriding the optional fields.

See this example:

case class A(a: Int, b: Option[Int])
case class B(a: Int, b: Option[Int], c: String)
object B {
  def from(a: A): B = {
    a.to[B].set(
      b = Some(1),
      c = "hi"
    )
  }
}

Here is the error message I'm getting when compiling:
[error] You have not provided enough arguments to convert from B.
[error] shapeless.::[shapeless.labelled.FieldType[shapeless.tag.@@[Symbol,String("b")],Some[Int]],shapeless.::[shapeless.labelled.FieldType[shapeless.tag.@@[Symbol,String("c")],String],shapeless.HNil]]
[error]
[error] error after rewriting to henkan.convert.Syntax.henkanSyntaxConvertA.to[com.truex.powerline.schema.uplift.surveys.B].set.applyDynamicNamed("apply")(scala.Tuple2("b", Some(1)), scala.Tuple2("c", "hi"))
[error] possible cause: maybe a wrong Dynamic method signature?
[error] a.to[B].set(

Is there something I'm doing wrong? Thanks.

optional not working with nested case classes?

The following gives a compiler error:

import $ivy.`com.kailuowang::henkan-convert:0.6.1`
import $ivy.`com.kailuowang::henkan-optional:0.6.1`

import henkan.optional.all._
import cats.data.Validated
import cats.implicits._

case class Foo(bar: String)
case class Message(a: Option[Foo], b: Option[Int])
case class Domain(a: Foo, b: Int)

validate(Message(Some(Foo("")), Some(2))).to[Domain]
test.sc:12: ambiguous implicit values:
 both method mkGenValidateFromOptional in trait MkValidateFromOptional of type [From, To, FL <: shapeless.HList, TL <: shapeless.HList](implicit genFrom: shapeless.LabelledGeneric.Aux[From,FL], implicit genTo: shapeless.LabelledGeneric.Aux[To,TL], implicit convertHList: shapeless.Lazy[henkan.optional.ValidateFromOptional[FL,TL]])henkan.optional.ValidateFromOptional[From,To]
 and method mkFromIdentity in trait MkValidateFromOptional1 of type [V]=> henkan.optional.ValidateFromOptional[V,V]
 match expected type henkan.optional.ValidateFromOptional[ammonite.$file.test.Foo,ammonite.$file.test.Foo]
val res_8 = validate(Message(Some(Foo("")), Some(2))).to[Domain]
    ^
test.sc:12: Cannot build validate function from ammonite.$file.test.Message to ammonite.$file.test.Domain, possibly due to missing fields in ammonite.$file.test.Message or missing cats instances (`Traverse` instances are needed to convert fields in containers)
val res_8 = validate(Message(Some(Foo("")), Some(2))).to[Domain]
                                                        ^

Replacing Foo by String (as in the examples) works fine though. Is there something special I need to do to have henkan convert nested case classes with optionals?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.