【问题标题】:Explicitly output JSON null in case of missing optional value在缺少可选值的情况下显式输出 JSON null
【发布时间】:2015-12-16 16:31:41
【问题描述】:

考虑这个使用 Play 的 JSON API (play.api.libs.json) 的示例:

case class FooJson(
   // lots of other fields omitted
   location: Option[LocationJson]
)

object FooJson {
  implicit val writes = Json.writes[FooJson]
}

case class LocationJson(latitude: Double, longitude: Double)

object LocationJson {
  implicit val writes = Json.writes[LocationJson]
}

如果locationNone,则生成的JSON 将根本没有location 字段。这很好,可以理解。但是如果我出于某种原因想要(例如,让我的 API 更加自文档化),我怎样才能在 JSON 中显式输出 null

{      
  "location": null
}

我还尝试将字段定义为location: LocationJson 并将option.orNull 传递给它,但它不起作用(scala.MatchError: null play.api.libs.json.OWrites$$anon$2.writes)。对于 String 或 Double 等非自定义类型,这种方法在 JSON 输出中产生 null。

所以,在使用 Json.writes[FooJson] 时(或同样简单的方法,即 不必 必须编写自定义的 Writes 实现),是否有一个干净的在 JSON 中包含空值的方法?

我要问的是类似于杰克逊图书馆中的JsonInclude.Include.ALWAYS(也是杰克逊的默认行为)。同样in Gson 这将是微不足道的
(new GsonBuilder().serializeNulls().create())。

播放 2.4.4

【问题讨论】:

    标签: json scala playframework


    【解决方案1】:

    Greg Methvin,Play 提交者,wrote this answer 在相关的 GitHub 问题中给我:

    JSON 宏仅支持一种编码可选值的方式, 这是从 JSON 中省略 None 值。这不是错误,而是 而是实施的限制。如果你想包括 不幸的是,您将不得不实现自己的空值 Writes

    我确实认为我们应该尝试为 不过宏。

    在这种情况下,我会让 Play 在值为 null 时排除该字段,即使它稍微牺牲了 API 一致性和自记录性。这仍然是一件微不足道的事情(在这个特定的 API 中),它不保证像自定义 Writes 对具有十几个值的案例类所采取的那样丑化代码。

    我希望他们能在未来的 Play 版本中使其更易于配置。

    【讨论】:

      【解决方案2】:

      来自未来的你好。

      从 Play 2.7 开始,为自动化 JSON 编解码器引入了一个相当简单的解决方案。我们可以在Format/Reads/Writes 的范围内为JsonConfiguration 引入适当的隐式值。以下配置将为空的Options 写入nulls,而不是完全省略这些字段。

      import play.api.libs.json._
      
      implicit val config = JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)
      implicit val residentWrites = Json.writes[Resident]
      

      Reference

      【讨论】:

        【解决方案3】:

        这是一种方法:

        object MyWrites extends DefaultWrites{
        
          override def OptionWrites[T](implicit fmt: Writes[T]): Writes[Option[T]] = new Writes[Option[T]] {
            override def writes(o: Option[T]): JsValue = {
              o match {
                case Some(a) => Json.toJson(a)(fmt)
                case None => JsNull
              }
            }
          }
        }
        

        这将覆盖不会创建元素的默认实现。我在你的示例代码中使用了这个:

        case class FooJson(
                        // ...
                        location: Option[LocationJson]
                      )
        
        case class LocationJson(latitude: Double, longitude: Double)
        
        object LocationJson {
          implicit val writes = Json.writes[LocationJson]
        }
        
        implicit val fooJsonWriter: Writes[FooJson] = new Writes[FooJson] {
          override def writes(o: FooJson): JsValue = {
            JsObject(Seq(
              "location" -> Json.toJson(o.location)
              // Additional fields go here.
            ))
          }
        }
        
        Json.toJson(FooJson(None))
        

        得到了这个结果res0: play.api.libs.json.JsValue = {"location":null}

        【讨论】:

        • 谢谢。来自问题:“使用Json.writes 时如上所示(或同样简单的东西)” 我想说的是自定义Writes 实现并不简单或干净。在我的真实代码中,该类有 10 多个其他字段,并且必须执行 Json.writes[FooJson] 自动执行的操作会变得很丑。
        • 我明白了。如果您不介意,我会尝试做其他事情,如果我无法提出更好的建议,请删除我的答案。
        【解决方案4】:

        如果我们有空值,那么我们必须在案例类中添加带有成员的选项,这将解决问题

        case class response(
         name:String,
         age: option[int]
        )
        
         object response {
            implicit val format = Json.format[response]
          }
        

        这里的选项是我们的答案。如果我们是年龄的 JSON 响应为 null,这将为我们处理解决方案。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-11-28
          • 2014-09-08
          • 2020-05-02
          • 1970-01-01
          • 2020-12-26
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多