Как написать собственный декодер для [Option [Option [A]] в Circe? - PullRequest
0 голосов
/ 20 сентября 2018

Я написал Reads конвертер в play-json для Option[Option[A]], который имел следующее поведение:

//given this case class
case class MyModel(field: Option[Option[String]])

//this JSON -- maps to --> this MyModel:
//"{ \"field\": \"value\" }"  -->  MyModel(field = Some(Some("value")))
//"{ \"field\": null, ...  }"   -->  MyModel(field = Some(None))
//"{ }"  -->  MyModel(field = None)

Итак, предоставив значение, сопоставленное Some[Some[A]], предоставив null сопоставленныйдо Some[None] (то есть Some[Option.empty[A]]), и не предоставляя значение, сопоставленное просто None (т.е. Option.empty[Option[A]]).Вот конвертер play-json:

def readOptOpt[A](implicit r: Reads[A]): Reads[Option[Option[A]]] = {
  Reads[Option[Option[A]]] { json =>
    path.applyTillLast(json).fold(
      identity,
      _.fold(_ => JsSuccess(None), {
        case JsNull => JsSuccess(Some(None))
        case js => r.reads(js).repath(path).map(a => Some(Some(a)))
      })
    )
  }
}

Теперь я конвертирую свой код play-json в Circe, но я не могу понять, как написать Decoder[Option[Option[A]] с таким же поведением.То есть мне нужно

def optOptDecoder[A](implicit d: Decoder[A]): Decoder[Option[Option[A]] = ??? //help!

Любые идеи о том, как я могу сделать эту работу?Спасибо

Я понял это:

Были две проблемы:

1) Как справиться со случаем, когда поле полностью отсутствовало изJSON.Оказывается, вы должны использовать Decoder.reattempt в своем пользовательском декодере, следуя коду Circe decodeOption, который работает.

2) Как заставить компилятор распознавать случаи Option[Option[A]], когда ваш код декодера сидит ввспомогательный объект (или где угодно).Оказывается, если вы используете полуавтоматическую деривацию, вы можете создать неявное в объекте-компаньоне, и это заменит значения по умолчанию:

//companion object
object MyModel {
  implicit def myModelOptOptDecoder[A](implicit d: Decoder[A]): Decoder[Option[Option[A]]] = 
    MyHelperObject.optOptDecoder
  implicit val myModelDecoder: Decoder[MyModel] = deriveDecoder
}

В любом случае, я не думаю, что это сильно поможеткто-нибудь в будущем, поэтому, если я не получу никаких голосов в течение следующих нескольких часов, я думаю, что я просто удалю это.

Edit2: Хорошо, ответ был получен, поэтому я не буду удалять его,Оставайся сильным, эзотерический вопрос о цирче, оставайся сильным ...

1 Ответ

0 голосов
/ 21 сентября 2018

Option[Option[A]] немного странно.Я понимаю и в основном согласен с аргументацией, но я думаю, что это достаточно странно, что может потребоваться просто заменить его собственным классом (и написать для этого декодер).Что-то вроде:

sealed trait OptionalNull[+A] {
  def toOption: Option[Option[A]]
}
object NotPresent extends OptionalNull[Nothing] {
  override def toOption = None
}
object PresentButNull extends OptionalNull[Nothing] {
  override def toOption = Some(None)
}
case class PresentNotNull[A](value: A) extends OptionalNull[A] {
  override def toOption = Some(Some(value))
}

Это дает дополнительное преимущество: вам не нужно беспокоиться о неявных приоритетах и ​​подобных вещах.Может упростить ваш декодер.

...