2014-09-26 70 views
0

所以我一直在嘗試在Scala中編寫JSON解析器。到目前爲止,我已經嘗試過Jackson,gson和flexjson,但是我無法使用它包含我的示例。 這個例子看起來很愚蠢,但它證明了我的問題。Json序列化,Scala中的反序列化與集合和循環依賴

我得到的最長時間是與傑克遜一起使用註釋。
@JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class)
我想要保存的每個類。這似乎創建了一個正確的JSON文件,但我無法將其反序列化回我的Garage對象。 這種方法的一個問題也是註釋,如果可能的話我想跳過註釋,因爲在我的實例中我沒有完全控制源代碼。

我已經在下面插入了所有的代碼(傑克遜例子)和我的依賴(以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浪:斯卡拉庫: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

你是什麼意思的循環依賴?另外:'Car'的第一個參數是什麼(放置'null'值的地方)?關於註釋:你知道json4s嗎?它建立在jackson上,並提供了一個整潔的,自動的序列化/反序列化的case類。 https://github.com/json4s/json4s – 2014-09-27 01:43:48

+0

沒關係,我想通了你的意思是循環依賴。你的'Car'有一個'Person',但是'Person'也有'Car'。那麼......我認爲這不適用於大小寫常量的case類,因爲你永遠無法同時初始化它們。也許你可以將你的JSON反序列化爲輔助對象,然後將它們轉換成你想要的'Car's和'Person's? – 2014-09-27 02:03:05

回答

1

您確定要實際上明確地將你的循環引用放入你的序列化表單中?這樣做並不能獲得任何信息,我可以想象你會遇到很多問題。你沒有獲得任何信息的原因是,如果你知道一個人擁有的汽車,那麼你可以推斷出每輛汽車的主人。

在下面,我會將您的示例僅限於CarPerson(即,我將省略Garage),因爲此場景已經很複雜。我也不會明確地將Carowner轉換爲它的序列化形式,但我會告訴你如何反序列化它並獲得循環依賴。

在這個例子中,我將使用json4s,因爲我對它有點熟悉,因爲我聽說它是​​Scala中json序列化/反序列化的實際標準。你也不必寫那些你不喜歡的討厭的註釋。

序列化形式

正如我所說,我不會在你的序列化形式使用循環依賴(雖然我相信你可以通過編寫自定義串行器/解串器在某種程度上做到這一點)。讓我們將CarPerson的初始化階段想象成真正的人去供應商和購買汽車。所以我們有一個CarBuyingPerson,即即將購買Car s的List。汽車必須沒有車主(假設供應商不算作車主),所以我們有一個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))) 
    ??? 
} 

到目前爲止好,但我們STIL希望汽車有一個老闆。因此,讓我們定義我們的內部類CarPerson,它們代表我們完全初始化的對象。然而,它們彼此依賴,所以我們需要以某種方式將每一個先於另一個瞬間化。我發現了另一個堆棧溢出帖子,正好解決了這個問題: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,即,我們可以實例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不知道它的主人。

+0

感謝您的回覆,我會研究json4s。不幸的是我需要這些循環引用,這個例子很愚蠢,但是在我的真實代碼中,我有那些無法修改的循環引用。數據在具有循環引用的圖表中表示。 – Simpor 2014-09-29 09:40:46