Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update case class from incomplete JSON with Argonaut or Circe

I need to create an updated instance from a case class instance (with any needed DecodeJsons implicitly derived), given an incomplete json (some fields missing). How can this be accomplished with Argonaut (preferably) or Circe (if I have to)?

Example:

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) 

I'm pretty sure I have to parse the json to json AST, then convert it to Shapeless LabelledGeneric, then use Shapeless update somehow to update the case class instance.


Edit 2

After reading the Shapeless source I found that I can generate my own "Default"-object. I managed to create a solution which requires the instance of the case class to be present while parsing the json. I was hoping to avoid this and instead provide the instance later. Anyway here it is:

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

This yields Person(Old Name Kept,42).

like image 371
eirirlar Avatar asked Sep 03 '16 16:09

eirirlar


2 Answers

For the sake of completeness: support for "patching" instances like this has been provided in circe since the 0.2 release:

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)

And then:

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

My original blog post about this technique uses Argonaut (mostly since I wrote it a couple of months before I started working on circe), and that implementation is available as a library, although I've never published it anywhere.

like image 153
Travis Brown Avatar answered Oct 29 '22 16:10

Travis Brown


You can generate those implicit val defs / pd with macro annotation on Person (in object Person, for example, and do import Person._ to summon implicits). See this unfinished Simulacrum in scalameta (scala-reflect is fine too, but seems like scalameta can be enough here) for usage examples. Also you have to specify missing default value (42) somewhere, for example, in case class constructor (age: Int = 42, recognition can be done in macro too).

like image 3
dveim Avatar answered Oct 29 '22 14:10

dveim