【问题标题】:Why isn't a member of type Option[String] being serialized?为什么不序列化 Option[String] 类型的成员?
【发布时间】:2017-07-17 02:14:04
【问题描述】:

我有一个虚拟类Foo,它有三个成员:

import play.api.libs.json.Json

case class Foo(id: String, fooType: FooType, nextId: Option[String])

object Foo {
  implicit val fooReads = Json.reads[Foo]
  implicit val fooFormat = Json.format[Foo]
}

FooType 定义为

import play.api.libs.json.Json

case class FooType(a: String, b: String)

object FooType {
  implicit val fooTypeReads = Json.reads[FooType]
  implicit val fooTypeFormat = Json.format[FooType]
}

我在序列化Foo 对象时发现了一些有趣的行为。如果我将Foo序列化为JSON,解析JSONified Foo,我发现所有成员都被正确解析:

val id = "id"
val fooType = FooType("a", "b")
val nextId = None

val foo = Foo(id, fooType, nextId)
val jsonFoo = Json.toJson(foo)
val parsedFoo = Json.parse(jsonFoo.toString).as[Foo]

assert(parsedFoo == foo)
assert(parsedFoo.id == id)
assert(parsedFoo.fooType == fooType)
assert(parsedFoo.nextId.isEmpty)

这很好,因为这是我所期望的。

但是,在我的下一个测试中,我发现 nextId 字段根本不可可序列化:

val id = "id"
val fooType = FooType("a", "b")
val nextId = None

val foo = Foo(id, fooType, nextId)
val jsonFoo = Json.toJson(foo)

assert((jsonFoo \ "id").as[String] == id)
assert((jsonFoo \ "fooType").as[FooType] == fooType)
assert((jsonFoo \ "nextId").as[Option[String]].isEmpty)

这会失败并出现以下错误:

Error:(38, 35) No Json deserializer found for type Option[String]. Try to implement an implicit Reads or Format for this type.
    assert((jsonFoo \ "nextId").as[Option[String]].isEmpty)

Error:(38, 35) not enough arguments for method as: (implicit fjs: play.api.libs.json.Reads[Option[String]])Option[String].
Unspecified value parameter fjs.
    assert((jsonFoo \ "nextId").as[Option[String]].isEmpty)

同样,我发现当我打印Json.toJson(foo)转储的JSON对象时,JSON对象中缺少nextId字段:

println(Json.prettyPrint(jsonFoo))

{
  "id" : "id",
  "fooType" : {
    "a" : "a",
    "b" : "b"
  }
}

不过,我可以用toOption 解析nextId 字段;即,

assert((jsonFoo \ "nextId").toOption.isEmpty)

如果我的对象之一不是本机可反序列化的,如何从 JSON 中正确解析我的对象?

【问题讨论】:

    标签: json scala serialization optional play-json


    【解决方案1】:

    nextId 字段是可序列化的,否则,您根本无法将Foo 写入 JSON。

    jsonFoo: play.api.libs.json.JsValue = {"id":"id","fooType":{"a":"a","b":"b"}}
    

    您遇到的问题是Option[A] 没有Reads。选项由 Play JSON 专门处理。当使用 JSON 组合器时,我们使用 readNullable[A] 和 writeNullable[A] 而不是 read[Option[A]] 和 write[Option[A]]。同样,当使用方法从JsValue 中提取单个字段时,调用as 将不起作用,因为它需要隐式Reads[A] 用于您给它的类型(在本例中为Reads[Option[String]],它不存在) .

    相反,您需要使用asOpt,它将正确处理下面的Option:

    scala> (jsonFoo \ "nextId").asOpt[String]
    res1: Option[String] = None
    

    nextId 不会出现在打印的 JSON 中,因为您要序列化的值是 None。这是预期会发生的事情。由于该值是可选的,因此它会从 JSON 中省略(它只是 JavaScript 中的 undefined)。如果有值,就会出现:

    scala> Json.toJson(Foo("id", FooType("a", "b"), Option("abcdef")))
    res3: play.api.libs.json.JsValue = {"id":"id","fooType":{"a":"a","b":"b"},"nextId":"abcdef"}
    

    如果某些内容无法使用 Play JSON 序列化,它根本无法编译。

    【讨论】:

    • 有趣。我可能希望None 被序列化为null 或Play 中的JsNull 对象,但我现在看到我必须为此定义一个自定义编写器。感谢您花时间回答!
    • 只有一种方式。 JSON 中缺少的字段或null 将映射到Option,但Option 只能映射到相反的两个字段之一。设计师选择省略该字段是更明智的做法。
    • 有道理。只要能正确解析就可以了——这就是症结所在。
    猜你喜欢
    • 2014-06-07
    • 2018-03-19
    • 2012-08-11
    • 2018-11-22
    • 1970-01-01
    • 2011-03-17
    • 2019-12-28
    • 2019-10-20
    • 1970-01-01
    相关资源
    最近更新 更多