【问题标题】:Json serialization, deserialization in Scala with collections and circular dependenciesScala 中的 Json 序列化、反序列化,带有集合和循环依赖
【发布时间】:2014-11-21 15:59:20
【问题描述】:

所以我一直在尝试在 Scala 中编写 JSON 解析器。到目前为止,我已经尝试过 Jackson、gson 和 flexjson,但我无法让它与我的示例一起使用。 这个例子可能看起来很傻,但它说明了我的问题。

我得到的最长的一次是杰克逊使用注释。
@JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class)
在我想保存的每一堂课上。这似乎创建了一个正确的 JSON 文件,但我无法将其反序列化回我的 Garage 对象。 这种方法的一个问题也是注释,如果可能我想跳过注释,因为在我的真实示例中我没有完全控制源。

我在下面插入了所有代码(Jackson 示例)和我的依赖项(gradle 格式)。

代码:

import java.io.StringWriter

import com.fasterxml.jackson.annotation.{JsonIdentityInfo, ObjectIdGenerators}
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule

object JsonTester extends App {

  trait TestData {
    var volvo1 = new Car(null, "volvo")
    var volvo2 = new Car(null, "volvo")
    var bmw = new Car(null, "bmw")
    var jeep1 = new Car(null, "jeep")
    var jeep2 = new Car(null, "jeep")
    var ford = new Car(null, "ford")

    val p1 = new Person("John", List[Car](volvo1, jeep1))
    volvo1.owner = p1
    jeep1.owner = p1

    val p2 = new Person("Anna", List[Car](volvo2))
    volvo2.owner = p2

    val p3 = new Person("Maria", List[Car](bmw))
    bmw.owner = p3

    val p4 = new Person("Kevin", List(ford, jeep2))
    ford.owner = p4
    jeep2.owner = p4

    val customers = List(p1, p2, p3, p4)
    val carModels = Map("volvo" -> List(volvo1, volvo2), "bmw" -> List(bmw), "jeep" -> List(jeep1, jeep2), "ford" -> List(ford))
    val garage = new Garage[Person, Car]("FixYourCar", customers, carModels);

  }

  new TestData() {
    val originalToString = garage.toString
    println("Garage: " + originalToString)

    val json: String = toJson(garage)
    println(json)
    val garageFromJson: Garage[Person, Car] = fromJson(json)
    println("garageFromJson: " + garageFromJson)

    garageFromJson.customers.foreach(println(_))
    assert(originalToString.equals(garageFromJson.toString))
  }

  def toJson(garage: Garage[Person, Car]): String = {
    import com.fasterxml.jackson.module.scala.DefaultScalaModule

    val mapper = new ObjectMapper()
    mapper.registerModule(DefaultScalaModule)

    println("Saving graph to json")
    val writer = new StringWriter()
    mapper.writeValue(writer, garage)
    writer.toString
  }

  def fromJson(json: String): Garage[Person, Car] = {
    val mapper = new ObjectMapper()
    mapper.registerModule(DefaultScalaModule)
    mapper.readValue[Garage[Person, Car]](json, classOf[Garage[Person, Car]])
  }


}

@JsonIdentityInfo(generator = classOf[ObjectIdGenerators.UUIDGenerator])
case class Garage[P, C](name: String, customers: List[P], models: Map[String, List[C]])

@JsonIdentityInfo(generator = classOf[ObjectIdGenerators.UUIDGenerator])
case class Person(name: String, cars: List[Car])

@JsonIdentityInfo(generator = classOf[ObjectIdGenerators.UUIDGenerator])
case class Car(var owner: Person, model: String) {
  override def toString(): String = s"model: $model, owner:${owner.name}"
}

依赖关系: 编译 'org.scala-lang:scala-library:2.11.2' 编译“org.scalatest:scalatest_2.11:2.2.2”

compile 'com.typesafe.akka:akka-actor_2.11:2.3.6'
compile 'com.typesafe.akka:akka-testkit_2.11:2.3.6'

compile 'net.sf.opencsv:opencsv:2.3'

compile 'jfree:jcommon:1.0.16'
compile 'org.jfree:jfreechart:1.0.15'
compile 'org.jgrapht:jgrapht-ext:0.9.0'

compile 'org.hibernate:hibernate-core:3.6.0.Final'
compile 'org.hibernate:hibernate-entitymanager:3.6.0.Final'
compile 'mysql:mysql-connector-java:5.1.27'

// json
compile 'com.fasterxml.jackson.module:jackson-module-scala_2.11:2.4.2'
compile 'com.google.code.gson:gson:2.3'
compile 'net.sf.flexjson:flexjson:3.2'

运行结果:

Garage: .....
Saving graph to json
{"@id":"282559ae-70ea-4d74-8363-4b37f1691dba"....
garageFromJson: Garage(FixYourCar,List(Map(@id -> 7ae4b765-c0dc-4a8e-867f-23bc7672db91, name -> John, cars -> ....
Exception in thread "main" java.lang.ExceptionInInitializerError
    at JsonTester.main(JsonTester.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.lang.ClassCastException: scala.collection.immutable.Map$Map3 cannot be cast to Person
    at JsonTester$$anon$1$$anonfun$1.apply(JsonTester.scala:49)
    at scala.collection.immutable.List.foreach(List.scala:381)
    at JsonTester$$anon$1.<init>(JsonTester.scala:49)
    at JsonTester$.<init>(JsonTester.scala:40)
    at JsonTester$.<clinit>(JsonTester.scala)
    ... 6 more

【问题讨论】:

  • 循环依赖是什么意思?另外:Car 的第一个参数是什么(你把null-value 放在哪里)?关于注释:你知道json4s吗?它建立在杰克逊的基础上,并提供了一个简洁的、自动的案例类序列化/反序列化。 github.com/json4s/json4s
  • 没关系,我明白你所说的循环依赖是什么意思。你的Car 有一个Person,但Person 也有Car。好吧...我认为这不适用于常量类的案例类,因为您永远不能同时初始化两者。也许您可以将您的 JSON 反序列化为辅助对象,然后将它们转换为您想要的 Cars 和 Persons?

标签: json scala serialization jackson deserialization


【解决方案1】:

您确定要真正明确地将循环引用放入序列化表单中吗?这样做您不会获得任何信息,我可以想象您会遇到很多问题。您没有获得任何信息的原因是,如果您知道一个人拥有的汽车,那么您可以推断出每辆汽车的所有者。

在下文中,我会将您的示例简化为仅CarPerson(即我将省略Garage),因为这种情况已经很复杂了。我也不会明确地将Carowner 放入其序列化形式,但我将向您展示如何反序列化它并获得循环依赖项。

我将在示例中使用json4s,因为我对它有点熟悉,而且我听说它是​​Scala 中json 序列化/反序列化的事实标准。你也不必写那些你不喜欢的讨厌的注释。

序列化形式

正如我所提到的,我不会在您的序列化表单中使用循环依赖项(尽管我确信您可以通过编写自定义序列化器/反序列化器以某种方式做到这一点)。让我们将CarPerson 的初始化阶段想象为一个真实的人去供应商并购买汽车。所以我们有一个CarBuyingPerson,即将购买ListCars。这些汽车必须还没有所有者(假设供应商不计为所有者),所以我们有一个CarWithoutOwner。这两个案例类如下所示:

case class CarBuyingPerson(name: String, cars: List[CarWithoutOwner])
case class CarWithoutOwner(model: String)

现在,我们可以序列化和反序列化这些汽车和人:

val volvo1 = new CarWithoutOwner("volvo")
val volvo2 = new CarWithoutOwner("volvo")
val bmw = new CarWithoutOwner("bmw")
val jeep1 = new CarWithoutOwner("jeep")
val jeep2 = new CarWithoutOwner("jeep")
val ford = new CarWithoutOwner("ford")

val p1 = new CarBuyingPerson("John", List[CarWithoutOwner](volvo1, jeep1))
val p2 = new CarBuyingPerson("Anna", List[CarWithoutOwner](volvo2))
val p3 = new CarBuyingPerson("Maria", List[CarWithoutOwner](bmw))
val p4 = new CarBuyingPerson("Kevin", List(ford, jeep2))


def main(args: Array[String]) {
  implicit val formats = Serialization.formats(NoTypeHints)

  val ser = write(List(p1, p2, p3, p4))
  print(pretty(parse(ser)))
  ???
}

到目前为止一切都很好,但我们仍然希望这些汽车拥有一个所有者。所以让我们定义我们的内部类CarPerson,它们代表我们完全初始化的对象。但是,它们相互依赖,因此我们需要以某种方式先实例化一个。我发现另一个 Stack Overflow 帖子正好解决了这个问题:Scala: circular references in immutable data types?

我们的想法是不按值传递构造函数参数。 (但是,我不确定正确的术语是“按引用调用”还是“按名称调用”)。所以,我们定义我们的类如下:

class Person(name: String, cars: => List[Car]) {
  override def toString = s"Person $name with cars: $cars"
}

class Car(owner: => Person, model: String) {
  // must not create circular toString calls!
  override def toString = s"Car with model: $model"
}

现在我们只需要能够初始化这些类。因此,让我们定义一个函数buyCars 来执行此操作:

case class CarBuyingPerson(name: String, cars: List[CarWithoutOwner]) {
  def buyCars: Person = {
    lazy val This: Person = new Person(name, theCars)
    lazy val theCars: List[Car] = cars map {car => new Car(This, car.model)}
    This
  } 
}

通过使用惰性 val,我们可以使用尚未定义的 val,即,我们可以在实例化 This 时使用 theCars。这将为您提供所需的循环数据结构。

让我们在main-方法中测试一下:

def main(args: Array[String]) {
  implicit val formats = Serialization.formats(NoTypeHints)

  val ser = write(List(p1, p2, p3, p4))
  print(pretty(parse(ser)))

  println()
  println()

  val deSer = read[List[CarBuyingPerson]](ser)
  val peopleAfterBuyingCar = deSer map {_.buyCars}
  print(peopleAfterBuyingCar)
}

我发现那些循环依赖并不容易理解。我对你的建议是首先认真考虑你是否真的需要它们。也许更改您的设计并让Car 不知道它的所有者会更容易。

【讨论】:

  • 感谢您的回复,我会研究一下json4s。不幸的是,我需要那些循环引用,这个例子很愚蠢,但在我的真实代码中,我有那些无法修改的循环引用。数据以具有循环引用的图表表示。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-17
  • 1970-01-01
  • 2014-04-30
  • 1970-01-01
  • 1970-01-01
  • 2017-04-04
  • 2014-07-02
相关资源
最近更新 更多