Jea*_*ean 34 json scala playframework-2.2
我在play scala中有一个等效的以下模型:
case class Foo(id:Int,value:String)
object Foo{
import play.api.libs.json.Json
implicit val fooFormats = Json.format[Foo]
}
Run Code Online (Sandbox Code Playgroud)
对于以下Foo实例
Foo(1, "foo")
Run Code Online (Sandbox Code Playgroud)
我会得到以下JSON文档:
{"id":1, "value": "foo"}
Run Code Online (Sandbox Code Playgroud)
此JSON是持久存储的,并从数据存储区读取.现在我的要求已经改变了,我需要为Foo添加一个属性.该属性具有默认值:
case class Foo(id:String,value:String, status:String="pending")
Run Code Online (Sandbox Code Playgroud)
写入JSON不是问题:
{"id":1, "value": "foo", "status":"pending"}
Run Code Online (Sandbox Code Playgroud)
但是从它读取会产生一个JsError,错过了"/ status"路径.
如何以最小的噪音提供默认值?
(ps:我有一个答案,我将在下面发布,但我对此并不满意,并且会赞成并接受任何更好的选择)
Jea*_*ean 38
玩2.6
根据@ CanardMoussant的回答,从Play 2.6开始,play-json宏已得到改进,并提出了多个新功能,包括在反序列化时使用默认值作为占位符:
implicit def jsonFormat = Json.using[Json.WithDefaultValues].format[Foo]
Run Code Online (Sandbox Code Playgroud)
对于低于2.6的游戏,最佳选择仍然使用以下选项之一:
玩-JSON-EXTRA
我发现了一个更好的解决方案来解决我在play-json中遇到的大多数缺点,包括问题中的一个:
play-json-extra在内部使用[play-json-extensions]来解决这个问题中的特定问题.
它包含一个宏,它将自动包含序列化器/解串器中缺少的默认值,使得重构更不容易出错!
import play.json.extra.Jsonx
implicit def jsonFormat = Jsonx.formatCaseClass[Foo]
Run Code Online (Sandbox Code Playgroud)
您可能想要检查的库有更多:play-json-extra
Json变形金刚
我目前的解决方案是创建一个JSON Transformer并将其与宏生成的Reads相结合.变换器通过以下方法生成:
object JsonExtensions{
def withDefault[A](key:String, default:A)(implicit writes:Writes[A]) = __.json.update((__ \ key).json.copyFrom((__ \ key).json.pick orElse Reads.pure(Json.toJson(default))))
}
Run Code Online (Sandbox Code Playgroud)
然后格式定义变为:
implicit val fooformats: Format[Foo] = new Format[Foo]{
import JsonExtensions._
val base = Json.format[Foo]
def reads(json: JsValue): JsResult[Foo] = base.compose(withDefault("status","bidon")).reads(json)
def writes(o: Foo): JsValue = base.writes(o)
}
Run Code Online (Sandbox Code Playgroud)
和
Json.parse("""{"id":"1", "value":"foo"}""").validate[Foo]
Run Code Online (Sandbox Code Playgroud)
确实会生成一个应用了默认值的Foo实例.
我认为这有两个主要缺陷:
Ed *_*aub 21
我发现最干净的方法是使用"或纯",例如,
...
((JsPath \ "notes").read[String] or Reads.pure("")) and
((JsPath \ "title").read[String] or Reads.pure("")) and
...
Run Code Online (Sandbox Code Playgroud)
当默认值为常量时,可以以常规隐式方式使用此方法.当它是动态的,那么你需要编写一个方法来创建Reads,然后在范围内引入它,a la
implicit val packageReader = makeJsonReads(jobId, url)
Run Code Online (Sandbox Code Playgroud)
Mik*_*ben 16
另一种解决方案是formatNullable[T]
与inmap
from 结合使用InvariantFunctor
.
import play.api.libs.functional.syntax._
import play.api.libs.json._
implicit val fooFormats =
((__ \ "id").format[Int] ~
(__ \ "value").format[String] ~
(__ \ "status").formatNullable[String].inmap[String](_.getOrElse("pending"), Some(_))
)(Foo.apply, unlift(Foo.unapply))
Run Code Online (Sandbox Code Playgroud)
我认为官方的答案现在应该是使用Play Json 2.6中的WithDefaultValues:
implicit def jsonFormat = Json.using[Json.WithDefaultValues].format[Foo]
Run Code Online (Sandbox Code Playgroud)
编辑:
值得注意的是,该行为与play-json-extra库不同.例如,如果你有一个DateTime参数,其默认值为DateTime.Now,那么你现在将得到进程的启动时间 - 可能不是你想要的 - 而使用play-json-extra你有时间创建来自JSON.