Вывод "null" для опции [T] в сериализации play-json, когда значение равно None
Я использую макросы 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}
Ответы
Ответ 1
Макрос Json.writes
генерирует writeNullable[T]
для дополнительных полей. Как вы знаете (или нет), writeNullable[T]
опускает поле, если значение равно None, тогда как write[Option[T]]
генерирует нулевое поле.
Определение пользовательского сценария - единственный вариант, который вы должны получить.
(
(__ \ 'name).write[String] and
(__ \ 'address).write[Option[String]]
)(unlift(Person.unapply _))
Ответ 2
Не настоящее решение для вас. Но немного лучше, чем вручную писать записи
Я создал вспомогательный класс, который может "обеспечивать" поля.
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")()
Ответ 3
Аналогичный ответ выше, но еще один синтаксис для этого:
implicit val personWrites = new Writes[Person] {
override def writes(p: Person) = Json.obj(
"name" -> p.name,
"address" -> p.address,
)
}
Ответ 4
Это просто:
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"
Ответ 5
Вы можете определить что-то вроде этого:
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 _)
Ответ 6
Вы можете свернуть свой вариант и переопределить поведение сериализации:
case class MyOption[T](o: Option[T])
implicit def myOptWrites[T: Writes] = Writes[MyOption[T]](_.o.map(Json.toJson).getOrElse(JsNull))
ПРЕДОСТЕРЕЖЕНИЯ:
1) этот подход хорош только для классов случаев, используемых исключительно как определение протокола сериализации. Если вы повторно используете некоторые классы модели данных в контроллерах - определите пользовательские сериализаторы, чтобы они не загрязняли модель.
2) (относится только к Option), если вы используете один и тот же класс для записи и чтения. Для воспроизведения потребуется присутствие обернутых полей (возможно, нулевых) во время десериализации.
PS: не удалось сделать то же самое с маркировкой типа. Ошибка компилятора похожа на No instance of play.api.libs.json.Writes is available for tag.<refinement>
(учитывая, что необходимые записи были определены явно). Похоже, ошибка воспроизведения макроса.
Ответ 7
Вы можете использовать пользовательскую неявную JsonConfiguration, см. Настройка макроса для вывода null
implicit val config = JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)
implicit val personWrites = Json.writes[Person]
Json.toJson(Person("John Smith", None))