播放2 JSON格式中缺少属性的默认值

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]inmapfrom 结合使用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)


Can*_*ant 6

我认为官方的答案现在应该是使用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.