简体   繁体   English

播放Scala JSON-有条件地在Writes中将字段添加到JSON对象

[英]Play Scala JSON - conditionally add field to JSON object in Writes

In our app, we have pretty complex structure of objects that is getting converted to JSON and back. 在我们的应用程序中,我们具有非常复杂的对象结构,这些对象将被转换为JSON并返回。 Until now most of the formatted are symmetrical (except some very specific cases, and even these for security reasons). 到目前为止,大多数格式化的文件都是对称的(除了一些非常特殊的情况,甚至出于安全原因,这些情况也是如此)。

Now we are facing a more complex case where conversion of an object into JSON (writes) needs to create an additional field at a time of conversion while the case class does not have that field. 现在,我们面临着一个更复杂的案例,其中将对象转换为JSON(写入)需要在转换时创建一个附加字段,而case类没有该字段。 For example, here is one of our existing formatters: 例如,这是我们现有的格式化程序之一:

case class ChecklistColumn(kind: ColumnKind.Value, descriptor: Descriptor.Value, data: JsValue) extends Column

implicit val checklistResultChecklistDataFormat: Format[ChecklistColumn] = (
  (__ \ "kind").format[ColumnKind.Value] and
  (__ \ "descriptor").format[Descriptor.Value] and
  (__ \ "data").format[JsValue]
)(ChecklistColumn.apply, unlift(ChecklistColumn.unapply))

This one creates a json that will looks like: 这将创建一个类似于以下内容的json:

{
  "kind": <String>,
  "descriptor": <String>,
  "data": <JsValue>
}

What we need to achieve is: 我们需要实现的是:

{
  "kind": <String>,
  "descriptor": <String>,
  "data": <JsValue>,
  "normalized_data": <JsString>
}

But, only in case when the data is type of JsString (in any other case normalized_data can be left empty, byt ideally should not even exist). 但是,仅在数据为JsString类型的情况下(在任何其他情况下normalized_data都可以留空,理想情况下, JsString甚至不应该存在)。

I do understand that we have to create separate Reads & Writes for that. 我确实知道我们必须为此创建单独的读取和写入。 But, I am not sure, how to implement the logic that will react differently to a different type of data . 但是,我不确定如何实现对不同类型的data做出不同反应的逻辑。

Of course, there is always an option to create fully custom writes : 当然,总有一个选项可以创建完全自定义的writes

override def writes(column: ChecklistColumn): JsValue = {...}

But, this will create a huge complexity in a code that will be hard to maintain. 但是,这将在难以维护的代码中造成巨大的复杂性。

What is the cleanest way to implement something like that? 实现这样的最干净的方法是什么?

Have a look at ScalaJsonTransformers . 看看ScalaJsonTransformers You can create a transformer that creates the normalised field from a string data value and use it to, erm, transform your original Format to a new Writes . 您可以创建一个转换器,该转换器从字符串数据值创建标准化字段,并将其用于将原始Format转换为新的Writes Here's a slightly simplified example that could doubtless be improved (you'll want to check various edge cases): 这是一个稍微简化的示例,无疑可以改进(您将需要检查各种边缘情况):

case class ChecklistColumn(kind: String, descriptor: String, data: JsValue)

// The original format.
val checklistFormat: Format[ChecklistColumn] = (
  (__ \ "kind").format[String] and
  (__ \ "descriptor").format[String] and
  (__ \ "data").format[JsValue]
)(ChecklistColumn.apply, unlift(ChecklistColumn.unapply))

// A transformer that looks for a "data" field with a string
// value and adds the normalized_data field if it finds one.
val checklistTransformer: Reads[JsObject] = JsPath.json.update(
  (__ \ "data").read[String].flatMap (
     str => (__ \ "normalized_data").json.put(JsString(str + "!!!"))))

// A new derived Writes which writes the transformed value if
// the transformer succeeds (a data string), otherwise the
// original value.
implicit val checklistWrites: Writes[ChecklistColumn] = checklistFormat
  .transform (js => js.transform(checklistTransformer).getOrElse(js))

That gives me: 这给了我:

Json.prettyPrint(Json.toJson(ChecklistColumn("a", "b", JsNumber(1))))
// {
//   "kind" : "a",
//   "descriptor" : "b",
//   "data" : 1
// }

Json.prettyPrint(Json.toJson(ChecklistColumn("a", "b", JsString("c"))))
// {
//   "kind" : "a",
//   "descriptor" : "b",
//   "data" : "c",
//   "normalized_data" : "c!!!"
// }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM