Как написать собственный декодер для [Option [Option [A]]] в Circe?

Я написал 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 в своем собственном декодере, следуя коду Цирцеи 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: Хорошо, на него ответили, поэтому я не буду его удалять. Оставайся сильным, эзотерический вопрос о круге, оставайся сильным ...


person Lasf    schedule 20.09.2018    source источник
comment
Я полагаю, следующий вопрос будет заключаться в том, как заставить декодеры класса case Circe даже искать неявное Decoder[Option[Option[A]] при столкновении с [Option[Option[A]], поскольку Circe, похоже, просто с радостью просто использует свой декодер Option по умолчанию и в этом случае. Фу.   -  person Lasf    schedule 20.09.2018
comment
Насколько Option[Option[T]] имеет смысл?   -  person cchantep    schedule 21.09.2018
comment
@cchantep Допустим, вы хотите обновить модель в REST API. Вы ПАТЧИВАЕТЕ модель JSON к API. Отсутствующие поля игнорируются и не затрагиваются (обозначены None). Нулевые значения поля удаляются из модели (это обозначается _2 _..., т. Е. Предпринимает какое-то действие, и это действие перезаписывается на None). Остальные значения полей обновлены (Some[Some[A]]). В любом случае, я близок к тому, чтобы понять это (я думаю), поэтому я либо удалю q, либо отвечу на него сам, чтобы он мог бесполезно отдыхать всю вечность.   -  person Lasf    schedule 21.09.2018
comment
Мне такая укладка кажется странной   -  person cchantep    schedule 21.09.2018
comment
Почему Option[A] декодер не работает с Option[Option[A]]?   -  person sarveshseri    schedule 21.09.2018
comment
@SarveshKumarSingh Смотрите мой комментарий выше? Не уверен, что еще я могу вам предоставить.   -  person Lasf    schedule 21.09.2018
comment
@cchantep интересно   -  person Lasf    schedule 21.09.2018
comment
Может быть, пользовательский тип с 3 регистрами будет более понятным, чем Option[Option[A]] в вашем случае   -  person crak    schedule 23.01.2019


Ответы (1)


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))
}

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

person Joe K    schedule 20.09.2018
comment
Да, это был мой первый подход ... Я использовал type MaybeOption[A] = Either[Unit, Option[A]] в качестве шаблона. НО ... это объемный устаревший код, который я конвертирую из play-json в circe, и я работаю на стартап (нужно ли еще говорить?), И я бы предпочел оставить его как наш странный вложенный вариант. В любом случае, приветствую, я ценю ответ. - person Lasf; 21.09.2018