Search code examples
jsonscalacirce

Scala - Ignore case class field when decoding JSON


I'm playing with the example ADT in the circe documentation to reproduce an issue that I have with JSON decoding.

To achieve that, I'm using ShapesDerivation :

scala>   object ShapesDerivation {
     | 
     |     implicit def encodeAdtNoDiscr[Event, Repr <: Coproduct](implicit
     |       gen: Generic.Aux[Event, Repr],
     |       encodeRepr: Encoder[Repr]
     |     ): Encoder[Event] = encodeRepr.contramap(gen.to)
     | 
     |     implicit def decodeAdtNoDiscr[Event, Repr <: Coproduct](implicit
     |       gen: Generic.Aux[Event, Repr],
     |       decodeRepr: Decoder[Repr]
     |     ): Decoder[Event] = decodeRepr.map(gen.from)
     | 
     |   }
defined object ShapesDerivation

The ADT to decode is composed by two values : a simple case class and another one that I have dedicated Encoder / Decoder (to reproduce in minimal example the issue that I really have) :

scala> :paste
// Entering paste mode (ctrl-D to finish)

 sealed trait Event

  object Event {

    case class Foo(i: Int) extends Event

    case class Bar(f : FooBar) extends Event

    case class FooBar(x : Int) 

    implicit val encoderFooBar : Encoder[FooBar] = new Encoder[FooBar] {
      override def apply(a: FooBar): Json = Json.obj(("x", Json.fromInt(a.x)))
    }

    implicit val decodeFooBar: Decoder[FooBar] = new Decoder[FooBar] {
      override def apply(c: HCursor): Result[FooBar] =
        for {
          x <- c.downField("x").as[Int]
        } yield FooBar(x)
    }
  }

Then when I try to decode a simple value like this, it's working well :

scala> import ShapesDerivation._
import ShapesDerivation._

scala> decode[Event](""" { "i" : 10 }""")
res1: Either[io.circe.Error,Event] = Right(Foo(10))

But if I tried to decode something that should be a Bar that contains a Foobar, I get a decoding failure :

scala> decode[Event](""" { "x" : 10 }""")
res2: Either[io.circe.Error,Event] = Left(DecodingFailure(CNil, List()))

But this one works because I explicitely put the case class field name :

scala> decode[Event](""" { "f" : { "x" : 10 }}""")
res7: Either[io.circe.Error,Event] = Right(Bar(FooBar(10)))

I don't what to put the case class field, directly the JSON but I think it's not possible to achieve a such behaviour. The reason why I think it's impossible is how it will know to match the good case class if there is not the field but I want to be sure that there is no way with circe to do that


Solution

  • Here's how you do it using just semi-auto derivation.

    import io.circe.Decoder.Result
    import io.circe.{Decoder, Encoder, HCursor, Json}
    import io.circe.parser._
    import io.circe.generic.semiauto._
    
    object Example extends App {
    
      sealed trait Event
    
      object Event {
    
        case class Foo(i: Int) extends Event
    
        object Foo {
          implicit val decoder: Decoder[Foo] = deriveDecoder
        }
    
        case class Bar(f: FooBar) extends Event
    
        object Bar {
          implicit val decoder: Decoder[Bar] = Decoder[FooBar].map(Bar.apply)
        }
    
        implicit val decoder: Decoder[Event] = Decoder[Foo].widen.or(Decoder[Bar].widen)
      }
    
      case class FooBar(x: Int)
    
      object FooBar {
        implicit val encoderFooBar: Encoder[FooBar] = deriveEncoder
        implicit val decodeFooBar: Decoder[FooBar]  = deriveDecoder
      }
    
      println(decode[Event](""" { "x" : 10 }"""))
    }
    
    

    Outputs

    Right(Bar(FooBar(10)))
    

    It gets a bit noisy with the explicit decoders, but if you care about compilation speed, it's the way to go since you'll only derive decoders once.