所以我一直在尝试在Scala中编写JSON解析器。到目前为止,我已经尝试过Jackson,gson和flexjson,但我无法使用我的示例。 这个例子看起来很愚蠢,但它证明了我的问题。
我得到的最长时间是杰克逊使用注释。
@JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class)
在每个我想要保存的课程上。这似乎创建了一个正确的JSON文件,但我无法将其反序列化回我的Garage对象。
这种方法的一个问题也是注释,如果可能的话我想跳过注释,因为我在实际例子中没有完全控制源。
我已经在下面插入了所有代码(Jackson-example)和我的依赖项(以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
答案 0 :(得分:1)
您确定要实际明确地将循环引用放入序列化表单中吗?你没有获得任何信息,我可以想象你会遇到很多问题。您没有获得任何信息的原因是,如果您知道一个人拥有的汽车,那么您可以推断出每辆车的车主。
在下文中,我将您的示例缩小为仅Car
和Person
(即,我将省略Garage
),因为此方案已经很复杂。我也没有明确地将owner
的{{1}}放入其序列化形式中,但我将向您展示如何对其进行反序列化并获得循环依赖关系。
我将在示例中使用json4s,因为我对它有点熟悉,因为我听说它是Scala中json序列化/反序列化的事实上的标准。你也不必写那些你不喜欢的讨厌的注释。
序列化表格
正如我所提到的,我不会在序列化表单中使用循环依赖(尽管我确信你可以通过编写自定义序列化器/反序列化器来实现这一点)。让我们想象一下Car
和Car
的初始化阶段,作为真实的人去供应商并购买汽车。我们有Person
,即将购买CarBuyingPerson
List
个Car
。汽车尚未拥有所有者(假设供应商不算作所有者),因此我们有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)))
???
}
到目前为止一直很好,但我们仍然希望汽车能拥有一辆车。因此,让我们定义代表完全初始化对象的内部类Car
和Person
。但是,它们彼此依赖,所以我们需要以某种方式在每个方面之前将它们连接起来。我找到了另一个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,即我们可以在实例化theCars
时使用This
。这将为您提供所需的循环数据结构。
让我们在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
不知道其所有者更容易。