我在play scala中有一个等效的以下模型:
case class Foo(id:Int,value:String) object Foo{ import play.api.libs.json.Json implicit val fooFormats = Json.format[Foo] }
对于以下Foo实例
Foo(1, "foo")
我会得到以下JSON文档:
{"id":1, "value": "foo"}
此JSON是持久存储的,并从数据存储区读取.现在我的要求已经改变了,我需要为Foo添加一个属性.该属性具有默认值:
case class Foo(id:String,value:String, status:String="pending")
写入JSON不是问题:
{"id":1, "value": "foo", "status":"pending"}
但是从它读取会产生一个JsError,错过了"/ status"路径.
如何以最小的噪音提供默认值?
(ps:我有一个答案,我将在下面发布,但我对此并不满意,并且会赞成并接受任何更好的选择)
我发现最干净的方法是使用"或纯",例如,
... ((JsPath \ "notes").read[String] or Reads.pure("")) and ((JsPath \ "title").read[String] or Reads.pure("")) and ...
当默认值为常量时,可以以常规隐式方式使用此方法.当它是动态的,那么你需要编写一个方法来创建Reads,然后在范围内引入它,a la
implicit val packageReader = makeJsonReads(jobId, url)
玩2.6
根据@ CanardMoussant的回答,从Play 2.6开始,play-json宏已得到改进,并提出了多个新功能,包括在反序列化时使用默认值作为占位符:
implicit def jsonFormat = Json.using[Json.WithDefaultValues].format[Foo]
对于低于2.6的游戏,最佳选择仍然使用以下选项之一:
玩-JSON-EXTRA
我发现了一个更好的解决方案来解决我在play-json中遇到的大多数缺点,包括问题中的一个:
play-json-extra在内部使用[play-json-extensions]来解决这个问题中的特定问题.
它包含一个宏,它将自动包含序列化器/解串器中缺少的默认值,使得重构更不容易出错!
import play.json.extra.Jsonx implicit def jsonFormat = Jsonx.formatCaseClass[Foo]
您可能想要检查的库有更多: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)))) }
然后格式定义变为:
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) }
和
Json.parse("""{"id":"1", "value":"foo"}""").validate[Foo]
确实会生成一个应用了默认值的Foo实例.
我认为这有两个主要缺陷:
默认密钥名称是字符串,不会被重构所取代
默认值是重复的,如果在一个地方更改,则需要在另一个地方手动更改
另一种解决方案是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))
我认为官方的答案现在应该是使用Play Json 2.6中的WithDefaultValues:
implicit def jsonFormat = Json.using[Json.WithDefaultValues].format[Foo]
编辑:
值得注意的是,该行为与play-json-extra库不同.例如,如果你有一个DateTime参数,其默认值为DateTime.Now,那么你现在将得到进程的启动时间 - 可能不是你想要的 - 而使用play-json-extra你有时间创建来自JSON.