Обновить класс кейса из неполного JSON с помощью Argonaut или Circe

Мне нужно создать обновленный экземпляр из экземпляра класса case (с любыми необходимыми DecodeJsons, неявно производными), учитывая неполный json (некоторые поля отсутствуют). Как это можно сделать с помощью Аргонавта (желательно) или Цирцеи (если нужно)?

Пример:

case class Person(name:String, age:Int)
val person = Person("mr complete", 42)
val incompletePersonJson = """{"name":"mr updated"}"""
val updatedPerson = updateCaseClassFromIncompleteJson(person, incompletePersonJson)

println(updatedPerson)
//yields Person(mr updated, 42) 

Я почти уверен, что мне нужно разобрать json на json AST, затем преобразовать его в Shapeless LabelledGeneric, а затем каким-то образом использовать обновление Shapeless, чтобы обновить экземпляр класса case.


Изменить 2

После прочтения источника Shapeless я обнаружил, что могу сгенерировать свой собственный объект "Default". Мне удалось создать решение, которое требует наличия экземпляра класса case при разборе json. Я надеялся избежать этого и вместо этого предоставил экземпляр позже. В любом случае вот оно:

import shapeless._
import argonaut._
import ArgonautShapeless._
import shapeless.ops.hlist.Mapper

case class Person(name: String, age: Int)

object MkDefault {

  object toSome extends Poly1 {
    implicit def default[P] = at[P](Some(_))
  }

  def apply[P, L <: HList, D <: HList]
  (p: P)
  (implicit
   g: Generic.Aux[P, L],
   mpr: Mapper.Aux[toSome.type, L, D]
  ): Default.Aux[P, mpr.Out] =
    Default.mkDefault[P, D](mpr(g.to(p)))
}


object Testy extends App {
    implicit val defs0 = MkDefault(Person("new name? NO", 42))
    implicit def pd = DecodeJson.of[Person]
    val i = """{"name":"Old Name Kept"}"""
    val pp = Parse.decodeOption[Person](i).get
    println(pp)
}

Это дает Person(Old Name Kept,42).


person eirirlar    schedule 03.09.2016    source источник
comment
Отладка ArgonautShapeless 'вывод DecodeJson (ArgonautShapeless.dehibitedDecodeJson), я вижу, что объект по умолчанию = Defaults $ AsOptions $$ anon $ 9 создается со значениями None :: None :: HNil. Мне кажется, что если бы я мог каким-то образом заменить это неявным экземпляром, который я предоставляю сам, я мог бы сделать значения по умолчанию, чтобы каким-то образом заполнить отсутствующий json.   -  person eirirlar    schedule 06.09.2016
comment
Единственное, что я могу придумать, чтобы сделать это безопасным для типа способом, - это повторно сериализовать существующий класс case в json, преобразовать оба в Map [String, Any], затем объединить карты, преобразовать обратно в json, а затем повторный синтаксический анализ   -  person Falmarri    schedule 13.09.2016
comment
@Falmarri, на самом деле это не такая уж плохая идея, я буду помнить об этом как о решении для резервного копирования, поскольку это, очевидно, потребует от компьютера больше времени и ресурсов.   -  person eirirlar    schedule 13.09.2016


Ответы (2)


Для полноты: поддержка таких экземпляров "исправления" была предоставлена ​​в circe начиная с версии 0.2:

import io.circe.jawn.decode, io.circe.generic.auto._

case class Person(name: String, age: Int)

val person = Person("mr complete", 42)
val incompletePersonJson = """{"name":"mr updated"}"""

val update = decode[Person => Person](incompletePersonJson)

А потом:

scala> println(update.map(_(person)))
Right(Person(mr updated,42))

Моя исходная запись в блоге об этом техника использует Argonaut (в основном, поскольку я написал его за пару месяцев до того, как начал работать над circe), и эта реализация доступна как библиотека, хотя я ее нигде не публиковал.

person Travis Brown    schedule 22.09.2016
comment
Это именно то, что я искал. Сейчас в моих проектах переключаюсь с Аргонавта на Цирцею. Отличная работа, так держать: D - person eirirlar; 23.09.2016
comment
Заметил существенное отличие от Argonaut после возврата от HTTP PATCH к тестированию HTTP POST с помощью Circe: значения по умолчанию классов case полностью игнорируются в Circe, тогда как в Argonaut, если поле отсутствует в json и имеет значение по умолчанию, оно будет декодировать в порядке. Также заметил открытую проблему по этому поводу в Цирце: github.com/travisbrown/circe/issues/65 Интересно, планируется ли его выпуск в ближайшем будущем? - person eirirlar; 26.09.2016

Вы можете сгенерировать эти implicit val defs / pd с аннотацией макроса на Person (например, в object Person и сделать import Person._ для вызова имплицитов). См. этот незаконченный Симулякр в скалямете (scala-reflection тоже подойдет , но похоже, что здесь может хватить скаламеты) для примеров использования. Также необходимо где-то указать отсутствующее значение по умолчанию (42), например, в случае конструктора класса (age: Int = 42, распознавание также может выполняться в макросе).

person dveim    schedule 17.09.2016
comment
Спасибо за это. Я пытался избежать создания собственных макросов, поскольку они обычно превращаются в ад поддержки, но я еще не смотрел на скалямету или симулякр. Я оставлю награду открытой сегодня на тот случай, если кто-то опубликует бесформенное решение без пользовательских макросов для circe или argonaut. Если не будет никаких ответов, я просто дам вам 50 за ваши усилия :) - person eirirlar; 20.09.2016
comment
Я работаю над более подробным руководством по аннотациям макросов на основе скаляметы. Надеюсь закончить его через ~ неделю. simulacrum-meta - это просто пример API-интерфейсов scalameta. Вообще говоря, scalameta следует предпочесть scala-Reflection, если она удовлетворяет требованиям (например, семантический API еще не поддерживается). В этом случае я думаю, что скаламета должна сделать эту работу. - person dveim; 20.09.2016