Using Lenses on Scala Regular Classes

前端 未结 2 436
青春惊慌失措
青春惊慌失措 2021-01-21 02:42

Most popular JSON libraries for Scala have the ability to serialize and deserialize to case classes.

Unfortunately, until Scala 2.11 is released, there is a restriction

相关标签:
2条回答
  • 2021-01-21 03:06

    If you're using lenses anyway, just nest your case classes. You'll have more potential for reuse of data, and the main reason not to nest is to avoid monstrosities like

    record.copy(person = record.person.copy(name = record.person.name.capitalize))
    

    which are (largely) solved if you use lenses. JSON can handle nested classes.

    0 讨论(0)
  • 2021-01-21 03:14

    It seems to be that, by defining a copy function (unfortunately by hand), regular classes can work with lenses, as Travis mentioned in his comment to the question, above.

    Below is a proof of concept that works (using json4s and a copy of an old Scalaz lens implementation, borrowed from Daniel Sobral's answer to Cleaner way to update nested structures):

    import org.json4s._
    import org.json4s.JsonDSL._
    import org.json4s.native.JsonMethods._
    import native.Serialization.write
    
    class Parent(val name:String, val age:Int, val kids:List[Kid]){
      override def toString() = s"""$name is $age years old, her/his kids are ${kids.mkString(", ")}."""
    
      def copy(name:String = name, age:Int = age, kids:List[Kid] = kids) = 
        new Parent(name, age, kids)
    }
    
    class Kid(val name:String, val age:Int){
      override def toString() = s"""$name ($age)"""
    
      def copy(name:String = name, age:Int = age) = 
        new Kid(name, age)
    }
    
    object TestJson {
      implicit val formats = DefaultFormats
    
      val json = """{"name":"John", "age":41, "kids":[{"name":"Mary", "age":10}, {"name":"Tom", "age":7}]}"""
    
      def main(args: Array[String]): Unit = {
        val parentKidsLens = Lens(
          get = (_: Parent).kids, 
          set = (p: Parent, kids: List[Kid]) => p.copy(kids = kids))
    
        val firstKidLens = Lens(
          get = (_: List[Kid]).head,
          set = (kds: List[Kid], kid: Kid) => kid :: kds.tail)
    
        val kidAgeLens = Lens(
          get = (_: Kid).age,
          set = (k: Kid, age: Int) => k.copy(age = age))
    
        val parentFirstKidAgeLens = parentKidsLens andThen firstKidLens andThen kidAgeLens
    
    
        println( parentFirstKidAgeLens.mod(parse(json).extract[Parent], age => age + 1) )    
      }
    }
    
    case class Lens[A,B](get: A => B, set: (A,B) => A) extends Function1[A,B] with Immutable {
      def apply(whole: A): B   = get(whole)
      def updated(whole: A, part: B): A = set(whole, part)
      def mod(a: A, f: B => B) = set(a, f(this(a)))
      def compose[C](that: Lens[C,A]) = Lens[C,B](
        c => this(that(c)),
        (c, b) => that.mod(c, set(_, b))
      )
      def andThen[C](that: Lens[B,C]) = that compose this
    }
    
    0 讨论(0)
提交回复
热议问题