3

大多数流行的 Scala JSON 库都能够序列化和反序列化为案例类。

不幸的是,在 Scala 2.11 发布之前,案例类可以拥有的参数数量是有限制的(最多 22 个)。作为超出此限制的解决方法,可以改用常规类。(例如:如何使用 *non-case* 类从带有 Scala 的 JSON 反序列化?)。

但是,这失去了案例类的好处。例如,没有自动生成的复制构造函数,并且镜头不适用于常规类,因此操作结构变得非常麻烦(除非将类中的每个字段都设置为 a var,放弃不变性的好处)。

有没有办法让常规类的行为更像案例类,例如,镜头也可以对它们起作用?

4

2 回答 2

0

似乎是,通过定义一个复制函数(不幸的是手动),常规类可以使用镜头,正如特拉维斯在他对问题的评论中提到的那样,上面。

下面是一个有效的概念证明(使用 json4s 和旧 Scalaz 镜头实现的副本,借自 Daniel Sobral 对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
}
于 2013-06-22T22:19:20.517 回答
0

如果您仍然使用镜头,只需嵌套您的案例类。您将有更多的数据重用潜力,不嵌套的主要原因是避免像这样的怪物

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

如果您使用镜头,这(很大程度上)可以解决。JSON 可以处理嵌套类。

于 2013-06-23T02:18:46.583 回答