Is the a way to warn about unused values with decoding in Circe ?

233 views Asked by At

Using circe, it's is easy to decode Json in case classes :

import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._


case class Bar(xs: List[String])


scala> decode[Bar]("{\"xs\":[\"a\",\"b\"]}")
res2: Either[io.circe.Error,Bar] = Right(Bar(List(a, b)))

scala> decode[Bar]("{\"xs\":[\"a\",\"b\"],\"addedField\":true}")
res3: Either[io.circe.Error,Bar] = Right(Bar(List(a, b)))

But I can see a way to check if all the fields have been used and mapped in the result.

Currently, one the way to check if some information as been lost is the retransform the result in json and diff the jsons (with Json4)

scala> val Diff(_,_,removed) = parse(input) diff parse(result.asJson.noSpaces)
removed: org.json4s.JsonAST.JValue = JObject(List((addedField,JBool(true))))

val noLoss = removed == JNothing

Is there a way to that directly in circe ?

2

There are 2 answers

1
Odomontois On BEST ANSWER

Main problem here, that there is not clear way to determine whether field is consumed by your decoder.
If we'll try to define some notion of such consumption, working as expected for case classes:

import shapeless._
import shapeless.labelled.FieldType 

trait FieldNames[T] {
  def names: Set[String]
}

object FieldNames {
  implicit val hNilNames = new FieldNames[HNil] {
    def names = Set.empty
  }

  implicit def hConsNames[S <: Symbol, A, T <: HNil]
  (implicit witness: Witness.Aux[S], tail: FieldNames[T]) = new FieldNames[FieldType[S, A] :: T] {
    lazy val names = tail.names + witness.value.name
  }

  implicit def ccNames[CC, Out <: HList]
  (implicit lgen: LabelledGeneric.Aux[CC, Out], genNames: Lazy[FieldNames[Out]]) = new FieldNames[CC] {
    lazy val names = genNames.value.names
  }
}

we can now represent some remainder catcher wrapper and corresponding decoder:

case class Tidy[X](value: X, remains: Option[JsonObject] = None)

object Tidy {
  implicit def tidyDecoder[X](implicit decoder: Decoder[X], fields: FieldNames[X]) =
    Decoder.instance[Tidy[X]] { cur =>
      decoder(cur) map { x =>
        Tidy(x, cur.focus.asObject.flatMap { obj =>
          Some(obj.filterKeys(!fields.names.contains(_))).filter(_.nonEmpty)
        })
      }
    }
}

having this you can try to extract remaning fields:

val json1 =
  """{
    "xs" : ["a", "b"]
    }
  """

val json2 =
  """{
    "xs" : ["a", "b"],
    "addedField": true
    }
  """

decode[Tidy[Bar]](json1) // Right(Tidy(Bar(List(a, b)),None))
decode[Tidy[Bar]](json2) // Right(Tidy(Bar(List(a, b)),Some(object[addedField -> true])))
1
C4stor On

There's no simple built-in way.

You can refer to this issue for a discussion between circe main maintainer and a contributor in this topic.

Disclaimer : there's no definitive conclusion ^^