Scala 2.10 + Json序列化和反序列化

37

据观察,Scala 2.10似乎已经破坏了一些老的库(至少暂时是这样),比如Jerkson和lift-json。

目标可用性如下:

case class Person(name: String, height: String, attributes: Map[String, String], friends: List[String])

//to serialize
val person = Person("Name", ....)
val json = serialize(person)

//to deserialize
val sameperson = deserialize[Person](json)

但我在查找使用Scala 2.10生成和反序列化Json的好方法方面遇到了困难。

在Scala 2.10中有最佳实践方法可以做到这一点吗?

5个回答

41

Jackson 是一个快速处理 JSON 的 Java 库。Jerkson 项目封装了 Jackson,但似乎已经被放弃了。我已经转而使用 Jackson 的 Scala Module 来将数据序列化和反序列化为本地 Scala 数据结构。

要获取它,请在您的 build.sbt 中包含以下内容:

libraryDependencies ++= Seq(
  "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.1.3",
   ...
)

那么你的示例将直接适用于以下Jackson包装器(我从jackson-module-scala测试文件中提取了它):

import java.lang.reflect.{Type, ParameterizedType}
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.`type`.TypeReference;

object JacksonWrapper {
  val mapper = new ObjectMapper()
  mapper.registerModule(DefaultScalaModule)
  
  def serialize(value: Any): String = {
    import java.io.StringWriter
    val writer = new StringWriter()
    mapper.writeValue(writer, value)
    writer.toString
  }

  def deserialize[T: Manifest](value: String) : T =
    mapper.readValue(value, typeReference[T])

  private [this] def typeReference[T: Manifest] = new TypeReference[T] {
    override def getType = typeFromManifest(manifest[T])
  }

  private [this] def typeFromManifest(m: Manifest[_]): Type = {
    if (m.typeArguments.isEmpty) { m.runtimeClass }
    else new ParameterizedType {
      def getRawType = m.runtimeClass
      def getActualTypeArguments = m.typeArguments.map(typeFromManifest).toArray
      def getOwnerType = null
    }
  }
}

其他Scala 2.10的JSON选项包括基于Programming Scala书籍的Twitter scala-json,它简单易懂但性能较差。还有spray-json,它使用parboiled进行解析。最后,Play的JSON处理看起来不错,但很难与Play项目分离。

Jackson Scala模块不适用于枚举类型:http://stackoverflow.com/questions/15887785/jackson-scala-module-serialize-enumeration-fails - Sebastien Lorber
这个问题可能已经在夜间构建中得到修复:https://github.com/FasterXML/jackson-module-scala/wiki/Enumerations - Kipton Barros
"erasure" 似乎已经被弃用。 - Mermoz
1
弃用的 m.erasure 可以在两个位置更改为 m.runtimeClass,它可以很好地工作。 - simbo1905

7

提到长期解决方案,需要提到包装jackson、lift-json或其自己的本地实现的json4s


6
我可以热情推荐argonaut作为Scala中的JSON支持。您只需要配置一行代码即可序列化您的客户对象:
implicit lazy val CodecCustomer: CodecJson[Customer] =
casecodec6(Customer.apply, Customer.unapply)("id","name","address","city","state","user_id")

这将提升您的类,使其具有.asJson方法,将其转换为字符串。它还将提升字符串类,以提供一个.decodeOption[List[Customer]]方法来解析字符串。它可以很好地处理您的类中的选项。这是一个工作类,带有通过测试和运行的主要方法,您可以将其放入argonaut的git克隆中,以确保一切正常运行:

package argonaut.example

import org.specs2.{ScalaCheck, Specification}
import argonaut.CodecJson
import argonaut.Argonaut._

case class Customer(id: Int, name: String, address: Option[String],
                    city: Option[String], state: Option[String], user_id: Int)

class CustomerExample extends Specification with ScalaCheck {

  import CustomerExample.CodecCustomer
  import CustomerExample.customers

  def is = "Stackoverflow question 12591457 example" ^
    "round trip customers to and from json strings " ! {
      customers.asJson.as[List[Customer]].toOption must beSome(customers)
    }
}

object CustomerExample {

  implicit lazy val CodecCustomer: CodecJson[Customer] =
    casecodec6(Customer.apply, Customer.unapply)("id","name","address","city","state","user_id")

  val customers = List(
    Customer(1,"one",Some("one street"),Some("one city"),Some("one state"),1)
    , Customer(2,"two",None,Some("two city"),Some("two state"),2)
    , Customer(3,"three",Some("three address"),None,Some("three state"),3)
    , Customer(4,"four",Some("four address"),Some("four city"),None,4)
  )

  def main(args: Array[String]): Unit = {

    println(s"Customers converted into json string:\n ${customers.asJson}")

    val jsonString =
      """[
        |   {"city":"one city","name":"one","state":"one state","user_id":1,"id":1,"address":"one street"}
        |   ,{"city":"two city","name":"two","state":"two state","user_id":2,"id":2}
        |   ,{"name":"three","state":"three state","user_id":3,"id":3,"address":"three address"}
        |   ,{"city":"four city","name":"four","user_id":4,"id":4,"address":"four address"}
        |]""".stripMargin


    var parsed: Option[List[Customer]] = jsonString.decodeOption[List[Customer]]

    println(s"Json string turned back into customers:\n ${parsed.get}")

  }
}

开发人员也很乐意帮助新手,并且能够及时响应。


4

我该如何在IntelliJ Idea 12中安装并使用它? - Alan Coromano
很抱歉,我无法帮助你解决这个问题。最终我选择了spray-json而不是jerkson。 - Sebastian Ganslandt
你安装它的方式无关紧要。 - Alan Coromano
我从来没有安装过jerkson,只是注意到有一个更新的版本 : )。如果你指的是spray-json,那么我只是将它作为一个sbt依赖项指出来了。 - Sebastian Ganslandt
我在询问如何安装 spray-json? - Alan Coromano
1
安装是什么意思?如果您正在使用某种依赖管理构建工具,则只需将其添加为依赖项。对于sbt,请参见https://github.com/spray/spray-json下的安装说明。 - Sebastian Ganslandt

2
因为没有错误信息和错误的示例代码,我怀疑这更多是因为不理解lift-json提取的工作原理所导致的问题。如果我误解了,请在评论中告诉我。所以,如果我是正确的,那么你需要做的就是:

进行序列化:

import net.liftweb.json._
  import Extraction._

implicit val formats = DefaultFormats

case class Person(...)
val person = Person(...)
val personJson = decompose(person) // Results in a JValue

然后,要反向此过程,您可以执行以下操作:
// Person Json is a JValue here.
personJson.extract[Person]

如果这不是你遇到麻烦的部分,请告诉我,我可以尝试修改我的答案以使其更有帮助。


1
我认为问题在于Scala 2.10破坏了二进制兼容性,而lift-json目前还没有更新。 - Kipton Barros
1
lift-json针对2.10版本现已推出并更新!目前我的使用情况良好。请参见:http://liftweb.net/25 - jpswain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接