mattl
mattl

Reputation: 2232

circe type field not showing

When encoding to Json with circe we really want the type field to show e.g.

scala> val fooJson = foo.asJson
fooJson: io.circe.Json =
{
  "this_is_a_string" : "abc",
  "another_field" : 123,
  "type" : "Foo"
}

This is taken from the release notes which previously mentions that you can configure the encoding like this:

implicit val customConfig: Configuration = 
Configuration.default.withSnakeCaseKeys.withDefaults.withDiscriminator("type")

Also other information about circe here suggests that without any configuration you should get some class type information in the encoding json.

Am I missing something? How do you get the class type to show?

Upvotes: 2

Views: 2805

Answers (1)

mdm
mdm

Reputation: 3988

UPDATE 30/03/2017: Follow up to OP's comment

I was able to make this work, as shown in the linked release notes.

Preparation step 1: add additional dependency to build.sbt

libraryDependencies += "io.circe" %% "circe-generic-extras" % "0.7.0"

Preparation step 2: setup dummy sealed trait hierarchy

import io.circe.{ Decoder, Encoder }
import io.circe.parser._, io.circe.syntax._
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.auto._
import io.circe.generic.{ semiauto => boring } // <- This is the default generic derivation behaviour
import io.circe.generic.extras.{ semiauto => fancy } // <- This is the new generic derivation behaviour

implicit val customConfig: Configuration = Configuration.default.withDefaults.withDiscriminator("type")

sealed trait Stuff
case class Foo(thisIsAString: String, anotherField: Int = 13) extends Stuff
case class Bar(thisIsAString: String, anotherField: Int = 13) extends Stuff

object Foo {
  implicit val decodeBar: Decoder[Bar] = fancy.deriveDecoder
  implicit val encodeBar: Encoder[Bar] = fancy.deriveEncoder
}

object Bar {
  implicit val decodeBar: Decoder[Bar] = boring.deriveDecoder
  implicit val encodeBar: Encoder[Bar] = boring.deriveEncoder
}

Actual code using this:

val foo: Stuff = Foo("abc", 123)
val bar: Stuff = Bar("xyz", 987)

val fooString = foo.asJson.noSpaces
// fooString: String = {"thisIsAString":"abc","anotherField":123,"type":"Foo"}

val barString = bar.asJson.noSpaces
// barString: String = {"thisIsAString":"xyz","anotherField":987,"type":"Bar"}

val bar2 = for{
  json <- parse(barString)
  bar2 <- json.as[Stuff]
} yield bar2
// bar2: scala.util.Either[io.circe.Error,Stuff] = Right(Bar(xyz,987))

val foo2 = for{
  json <- parse(fooString)
  foo2 <- json.as[Stuff]
} yield foo2
// foo2: scala.util.Either[io.circe.Error,Stuff] = Right(Foo(abc,123))

So, provided you import the extra dependency (which is where Configuration comes from), it looks like it works.

Finally, as a sidenote, it does seem that there is some disconnection between Circe's DESIGN.md and practice, for which I am actually happy.


Original answer: I am not sure this is supposed to be supported, by design.

Taken from Circe's DESIGN.md:

Implicit scope should not be used for configuration. Lots of people have asked for a way to configure generic codec derivation to use e.g. a type field as the discriminator for sealed trait hierarchies, or to use snake case for member names. argonaut-shapeless supports this quite straightforwardly with a JsonCoproductCodec type that the user can provide implicitly.

I don't want to criticize this approach—it's entirely idiomatic Scala, and it often works well in practice—but I personally don't like using implicit values for configuration, and I'd like to avoid it in circe until I am 100% convinced that there's no alternative way to provide this functionality.

What this means concretely: You'll probably never see an implicit argument that isn't a type class instance—i.e. that isn't a type constructor applied to a type in your model—in circe, and configuration of generic codec derivation is going to be relatively limited (compared to e.g. argonaut-shapeless) until we find a nice way to do this kind of thing with type tags or something similar.

In particular, customConfig: Configuration seems to be exactly the type of argument that the last paragraph refers to (e.g. an implicit argument that isn't a type class instance)

I am sure that @travis-brown or any other Circe's main contributors could shed some more light on this, in case there was in fact a way of doing this - and I would be very happy to know it! :)

Upvotes: 8

Related Questions