【问题标题】:Outputting 'null' for Option[T] in play-json serialization when value is None当值为 None 时,在 play-json 序列化中为 Option[T] 输出“null”
【发布时间】:2014-04-07 03:30:35
【问题描述】:

我正在使用 play-json 的宏来定义隐式 Writes 来序列化 JSON。但是,默认情况下,play-json 似乎忽略了 Option 字段设置为 None 的字段。有没有办法更改默认值,以便它输出null?我知道如果我定义自己的 Writes 定义,这是可能的,但我有兴趣通过宏来减少样板代码。

示例

case class Person(name: String, address: Option[String])

implicit val personWrites = Json.writes[Person]    
Json.toJson(Person("John Smith", None))

// Outputs: {"name":"John Smith"}
// Instead want to output: {"name":"John Smith", "address": null}

【问题讨论】:

    标签: json scala playframework-2.0


    【解决方案1】:

    Json.writes 宏为可选字段生成writeNullable[T]。如您所知(或不知道),writeNullable[T] 会在值为 None 时忽略该字段,而 write[Option[T]] 会生成一个空字段。

    定义自定义编写器是获得此行为的唯一选择。

    ( 
      (__ \ 'name).write[String] and
      (__ \ 'address).write[Option[String]]
    )(unlift(Person.unapply _))
    

    【讨论】:

      【解决方案2】:

      你可以使用自定义的隐式JsonConfiguration,见Customize the macro to output null

      implicit val config = JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)
      
      implicit val personWrites = Json.writes[Person]    
      Json.toJson(Person("John Smith", None))
      

      【讨论】:

        【解决方案3】:

        对于您的情况,这不是真正的解决方案。但比必须手动写入要好一些

        我创建了一个可以“确保”字段的辅助类。

        implicit class WritesOps[A](val self: Writes[A]) extends AnyVal {
            def ensureField(fieldName: String, path: JsPath = __, value: JsValue = JsNull): Writes[A] = {
              val update = path.json.update(
                __.read[JsObject].map( o => if(o.keys.contains(fieldName)) o else o ++ Json.obj(fieldName -> value))
              )
              self.transform(js => js.validate(update) match {
                case JsSuccess(v,_) => v
                case err: JsError => throw new JsResultException(err.errors)
              })
            }
        
            def ensureFields(fieldNames: String*)(value: JsValue = JsNull, path: JsPath = __): Writes[A] =
              fieldNames.foldLeft(self)((w, fn) => w.ensureField(fn, path, value))
        
        }
        

        这样你就可以写了

        Json.writes[Person].ensureFields("address")()
        

        【讨论】:

        • 同一件事的更安全和简洁的版本:implicit class WritesOps[A](val self: OWrites[A]) extends AnyVal { def ensureFields(fieldNames: String*)(value: JsValue = JsNull, path: JsPath = _): OWrites[A] ={ def tf(o: JsObject): JsObject = o ++ JsObject(fieldNames.withFilter(v => !o.keys.contains(v)).map( -> value)(collection.breakOut)) self.transform(tf(_)) } }
        【解决方案4】:

        与上述类似的答案,但有另一种语法:

        implicit val personWrites = new Writes[Person] {
          override def writes(p: Person) = Json.obj(
            "name" -> p.name,
            "address" -> p.address,
          )
        }
        

        【讨论】:

          【解决方案5】:

          这很简单:

          implicit val personWrites = new Writes[Person] {
            override def writes(p: Person) = Json.obj(
              "name" -> p.name,
              "address" -> noneToString(p.address),
            )
          }
          
          def optToString[T](opt: Option[T]) = 
             if (opt.isDefined) opt.get.toString else "null"
          

          【讨论】:

            【解决方案6】:

            你可以这样定义:

              implicit class JsPathExtended(path: JsPath) {
                def writeJsonOption[T](implicit w: Writes[T]): OWrites[Option[T]] = OWrites[Option[T]] { option =>
                  option.map(value =>
                    JsPath.createObj(path -> w.writes(value))
                  ).getOrElse(JsPath.createObj(path -> JsNull))
                }
              }
            

            如果你使用的是游戏框架:

              implicit val totoWrites: Writes[Toto] = (
                  (JsPath \ "titre").write[String] and
                    (JsPath \ "option").writeJsonOption[String] and
                    (JsPath \ "descriptionPoste").writeNullable[String]
                  ) (unlift(Toto.unapply))
            
               implicit val totoReads: Reads[Toto] = (
                  (JsPath \ "titre").read[String] and
                    (JsPath \ "option").readNullable[String] and
                    (JsPath \ "descriptionPoste").readNullable[String]
                  ) (Toto.apply _)
            

            【讨论】:

              【解决方案7】:

              您可以包装您的选项并重新定义序列化行为:

              case class MyOption[T](o: Option[T])
              implicit def myOptWrites[T: Writes] = Writes[MyOption[T]](_.o.map(Json.toJson).getOrElse(JsNull))
              

              注意事项:

              1) 这种方法仅适用于仅用作序列化协议定义的案例类。如果您在控制器中重用某些数据模型类 - 为它们定义自定义序列化程序,以免污染模型。

              2)(仅与 Option 相关)如果您使用相同的类进行写入和读取。 Play 将要求在反序列化期间存在包装的字段(可能为 null)。

              P.S.: 未能对类型标记做同样的事情。编译器错误就像 No instance of play.api.libs.json.Writes is available for tag.<refinement>(鉴于明确定义了所需的写入)。 看起来像是 Play 的宏错误。

              【讨论】:

                猜你喜欢
                • 2020-06-30
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-11-20
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多