Нет средства форматирования Json для Option [String]?

Я пытаюсь маршалировать и демаршалировать поле Option[String] в JSON и из него. В моем случае значение None должно быть маршалировано как "null". Вот код, который у меня есть:

import org.scalatest.{FlatSpec, Matchers}

import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._


case class Person(
  id: Int,
  firstName: Option[String],
  lastName: Option[String]
)

object Person {
  implicit lazy val personFormat = (
    (__ \ "id").format[Int] and
    (__ \ "first_name").format[Option[String]] and
    (__ \ "last_name").format[Option[String]]
  )(Person.apply, unlift(Person.unapply))
}

class PersonSpec extends FlatSpec with Matchers {
  "When Person instance is marshaled None fields " should
    "be serialized as \"null\" values" in {
    val person = Person(1, None, None)
    import Person._
    val json = Json.toJson(person)
    println(json)
    (json \ "id").as[Int] should be (1)
    (json \ "first_name").get should be (JsNull)
    (json \ "last_name").get should be (JsNull)
  }
}

Это приводит к следующей ошибке компилятора:

PersonSpec.scala:19: No Json formatter found for type Option[String]. Try to implement an implicit Format for this type.
[error]     (__ \ "first_name").format[Option[String]] and
[error]                               ^

Вот некоторые из вещей, которые я пробовал:

Замена (__ \ "first_name").format[Option[String]] на (__ \ "first_name").formatNullable[String] делает компилятор счастливым, но тест завершается неудачно (""java.util.NoSuchElementException: None.get"") со следующим выводом (из println(json))

{"id":1}

Это подтверждается поведением formatNullable (не отображать поля со значениями None).

Затем я заменил формат на writes. Вот так:

object Person {
  implicit lazy val personWrite = (
    (__ \ "id").write[Int] and
    (__ \ "first_name").write[Option[String]] and
    (__ \ "last_name").write[Option[String]]
  )(unlift(Person.unapply))
}

Теперь компилятор доволен, и тест проходит.

Но теперь мне нужно реализовать отдельный Reads. Если бы я мог, я бы предпочел этого не делать, поскольку это нарушает принцип DRY.

Что я делаю неправильно, и когда write[Option[...]] работает отлично, почему бы не format[Option[...]]?


person Babu Subburathinam    schedule 26.03.2017    source источник


Ответы (2)


Добавление этого кода, чтобы он был неявно виден из вашего PersonFormat, заставит его работать.

implicit def optionFormat[T: Format]: Format[Option[T]] = new Format[Option[T]]{
    override def reads(json: JsValue): JsResult[Option[T]] = json.validateOpt[T]

    override def writes(o: Option[T]): JsValue = o match {
      case Some(t) ⇒ implicitly[Writes[T]].writes(t)
      case None ⇒ JsNull
    }
  }

Я думаю, что в игре предполагается, что поля с опционными значениями вообще должны рассматриваться как необязательные, отсюда и поведение, которое вы наблюдали с formatNullable.

person Tomasz Perek    schedule 26.03.2017

Ты можешь использовать:

(__ \ "first_name").formatNullable[String]
person Liyosi    schedule 14.11.2017
comment
Именно то, что мне нужно, чтобы прочитать необязательный [String], спасибо! - person calloc_org; 18.06.2020