播放json:JsNull不等于JsString(null),如何回避这个问题?
我正在处理一些大的JSON输出。我们有一系列的测试来检查输出。通过在磁盘上保存JSON副本,在播放json:JsNull不等于JsString(null),如何回避这个问题?,json,scala,play-json,Json,Scala,Play Json,我正在处理一些大的JSON输出。我们有一系列的测试来检查输出。通过在磁盘上保存JSON副本,在InputStream上执行JSON.parse(),并将其与内存中构建的JsObject进行比较,我们创建了测试 在我开始将一些写入操作转换为使用函数语法之前,这种方法一直运行良好(即,使用构建器,而不是覆盖trait中的写入操作方法) 突然,测试开始失败:抱怨null字段不相等 显然,在使用函数语法时,选项[String]将转换为JsString(null),而不是JsNull。这在字符串化版本中不
InputStream
上执行JSON.parse()
,并将其与内存中构建的JsObject进行比较,我们创建了测试
在我开始将一些写入操作
转换为使用函数语法之前,这种方法一直运行良好(即,使用构建器,而不是覆盖trait中的写入操作
方法)
突然,测试开始失败:抱怨null
字段不相等
显然,在使用函数语法时,选项[String]
将转换为JsString(null)
,而不是JsNull
。这在字符串化版本中不明显
考虑以下代码段,使用
libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4"
import play.api.libs.json_
导入play.api.libs.functional.syntax_
对象FooBar{
案例类Foo(选项:选项[String])
def main(参数:数组[字符串]):单位={
val classic:OWrites[Foo]=新的OWrites[Foo]{
重写def写入(f:Foo):JsObject=Json.obj(“Foo”->f.option)
}
val dsl:OWrites[Foo]=(\uuuu\“Foo”).write[String].contramap(Foo=>Foo.option.orNull)
val json_classic=json.toJsObject(Foo(None))(classic)
val json_dsl=json.toJsObject(Foo(None))(dsl)
val json_parse=json.parse(“{”foo:null}”)
val string_classic=Json.prettyPrint(Json_classic)
val string_dsl=Json.prettyPrint(Json_dsl)
普林顿(
结果是:
|json_dsl==json_classic:${json_dsl==json_classic}//(预期为真)
|json_dsl==json_parse:${json_dsl==json_parse}//(预期为真)
|json_classic==json_parse:${json_classic==json_parse}//(预期为真)
|string_classic==string_dsl:${string_classic==string_dsl}//(期望为true)
|“.stripMargin)
println(s“classic:\n$string\u classic”)
println(s“dsl:\n$string\u dsl”)
}
}
实际产量为
Result is:
json_dsl == json_classic : false // (expect true)
json_dsl == json_parse : false // (expect true)
json_classic == json_parse : true // (expect true)
string_classic == string_dsl : true // (expect true)
classic:
{
"foo" : null
}
dsl:
{
"foo" : null
}
在调试时,您将看到经典的方法使用Tuple(“foo”,JsNull)
创建包装器对象,而dsl使用Tuple(“foo”,JsString(null))
创建包装器
在这种情况下,dsl的预期方式似乎是使用writeNullable
,但这种方式让人感到奇怪
我希望JsString(null)==JsNull
为真,或者dsl将捕获null
值并阻止创建JsString
我是在做一些完全被误导的事情吗
我只想重写为.writeNullable[String]
,这将从JSON中删除该字段,但我们有一个要求该字段存在的模式:
...
"properties": {
...
"foo": {
"oneOf": [
{"type": "string"},
{"type": "null"}
]
},
...
}
...
"required": [ "foo" ],
这是API的一部分,因此更改它需要时间
澄清一下:字符串表示在所有情况下都是正确的。我只对
JsValue
的内存表示感兴趣,这样我就可以在测试过程中使用它的相等性。我无法使用Play JSON 2.7.4在REPL中重现您的测试:
import play.api.libs.json._
case class Foo(option: Option[String])
val classic: OWrites[Foo] = new OWrites[Foo] {
override def writes(f: Foo): JsObject = Json.obj("foo" -> f.option)
}
val dsl: OWrites[Foo] = (__ \ "foo").write[String].contramap(foo => foo.option.orNull)
val json_classic = Json.toJsObject(Foo(None))(classic)
val json_dsl = Json.toJsObject(Foo(None))(dsl)
val json_classic = Json.toJsObject(Foo(None))(classic)
// json_classic: play.api.libs.json.JsObject = {"foo":null}
val json_dsl = Json.toJsObject(Foo(None))(dsl)
// json_dsl: play.api.libs.json.JsObject = {"foo":null}
json_classic
和json_dsl
之间的不平等是另一回事,但json表示在这两种情况下是一致的,即使foo.option.orNull对我来说是不安全的/奇怪的
另一方面,如果您想考虑<代码>“null”<代码> >代码> null <代码>,您可以重写默认的<代码>读[String ] 。
scala> val legacyStrReads: Reads[Option[String]] =
| Reads.optionWithNull(Reads.StringReads).map {
| case Some("null") => None
| case other => other
| }
legacyStrReads: play.api.libs.json.Reads[Option[String]] = play.api.libs.json.Reads$$anon$6@138decb1
scala> Json.toJson("null").validate(legacyStrReads)
res9: play.api.libs.json.JsResult[Option[String]] = JsSuccess(None,)
我想你想要的是:
val-dsl:OWrites[Foo]=(\uuuu\“Foo”)。writeOptionWithNull[String]
.contramap(u.option)
或者,如果使用的是较旧的play json版本,该版本没有WriteOptions WithNull
:
val-dsl:OWrites[Foo]=(_u\“Foo”)。写入[JsValue]
.contramap(u.option匹配){
case None=>JsNull
case Some(string)=>JsString(string)
})
请注意,play.api.libs.json.JsNull
和null
是两个完全不同且不相关的概念,决不能混淆或混淆。在Scala-only API中,整个Scala生态系统的约定是永远不要使用null
,因为大多数库只是假装它根本不存在。因此,您不会发现很多Scala库执行空检查,它们只是假设所有内容都是非空的,而一旦您开始使用空,您就处于未测试和不支持的边缘情况的西部。您使用或处理空值的唯一时间是在使用Java API时,因为它们使用空值,而且那里的惯例是尽可能早地在选项
中包装任何可以产生null
的内容,并尽可能晚地使用orNull
打开选项
,因此,至少在不直接与Java代码接口的Scala代码内部,所有内容都使用选项
,而不是null
。但是play json是专为Scala使用而设计的,因此与Scala only生态系统的其他部分一样,它只是假设null
不存在
当然,在json中,null
确实存在,使用它可能有正当的理由(特别是在与其他需要它的系统集成时)。所以play json确实对它进行了建模,但它以一种非常强类型的方式对它进行了建模——在Scala中没有任何东西会自动从null
变为null
,在json中,它总是非常显式的,这与Scala的强类型一致。另外,在JSON中使用null
并不常见,因此大多数默认方法(即writeNullable
方法)将选项映射到一个不存在的值,因此在编写JSONnull
时,必须更加明确一点,如上所示。p.S.:我知道我可以在这个例子中使用经典的符号,这样它就可以做它想做的事情