← Back to list

Dynamically derivate JSON traits and case classes with Circe in Scala

In our scenario to decode and model a Json string (e.g. a configuration file) that may have different data structures for its fields.

Lorenzo Silvestrini · 2021-03-10 12:54 · 0 claps · 1.3 min read
#scala #json #circes #traits #case-class
Open on Medium ↗
Wiki topics: 💻 · Programming

Dynamically derivate JSON traits and case classes with Circe in Scala

In our scenario to decode and model a Json string (e.g. a configuration file) that may have different data structures for its fields.

First of all, the required libraries:

libraryDependencies ++= Seq(
  "io.circe" %% "circe-core",
  "io.circe" %% "circe-generic",
  "io.circe" %% "circe-parser"
).map(_ % "0.12.3")
libraryDependencies += "io.circe" %% "circe-derivation-annotations" %"0.12.0-M7"

libraryDependencies += "io.circe" %% "circe-generic-extras" % "0.13.1-M4"

A Scala common approach to model this kind of data is reflected by traits and case classes data structures.

Let’s suppose we expect a configuration file structured like the following:

{
  "reader": "myReader",
  "writer": "myWriter"
  "firstConf": {
    "broker": "broker",
    "topic": "myTopic"
  },
  "secondConf": {
    "schema": {
      "type": "record",
      "name": "myName",
      "namespace": "myNamespace",
      "fields": [
        {
          "name": "FirstName",
          "type": [
            "null",
            "string"
          ],
          "default": null
        },
        {
          "name": "LastName",
          "type": [
            "null",
            "string"
          ],
          "default": null
        }
      ]
    },
    "numOfShards": 1
  },
  "thirdConf": {
    "windowDuration": 900
  }
}

As you can see, each _Conf field is structured in a different way, since we want to provide each time a customized configuration file, but keeping a generic approach.

We want to model the result of our Circe parsing like this:

import io.circe.JsonObject
import io.circe.generic.JsonCodec
@JsonCodec
case class AppConfig(
  reader: String,
  writer: String,
  firstConf: Conf,
  secondConf: Conf,
  thirdConf: Option[Conf] = None
)

With the Conf trait extended by the case classes:

sealed trait Conf
@JsonCodec
case class FirstConf(broker: String,
                     kafkaTopic: String,
                     ) extends Conf

@JsonCodec
case class SecondConf(
                    schema: Json,
                    numOfShards: Int
                  ) extends Conf

@JsonCodec
case class ThirdConf(
                          windowDuration: Int
                          ) extends Conf

And parse the config file like this:

io.circe.parser.decode[AppConfig](jsonString)

In order to let Circe dynamically infer the type of the data that extends the trait, we must modify our trait in this way:

import io.circe.generic.extras.{Configuration, ConfiguredJsonCodec}
@ConfiguredJsonCodec
sealed trait Conf

object Conf {
  implicit val config: Configuration =
    Configuration.default.withDiscriminator("type")
}

And add a field “type” to the data structures that should be modeled under the trait:

"firstConf": {
  "type": "FirstConf",
  "broker": "broker",
  "topic": "myTopic"
},
"secondConf": {
  "type": "SecondConf",
...
},
"thirdConf": {
    "type": "ThirdConf",
    "windowDuration": 900
  }

메타데이터
post_id
cc4060179d48
slug
dynamically-derivate-json-traits-and-case-classes-with-circe-in-scala-cc4060179d48
url
https://medium.com/@lorenzo-silvestrini1995/dynamically-derivate-json-traits-and-case-classes-with-circe-in-scala-cc4060179d48
canonical_url
https://medium.com/@lorenzo-silvestrini1995/dynamically-derivate-json-traits-and-case-classes-with-circe-in-scala-cc4060179d48
author_url
https://medium.com/@lorenzo-silvestrini1995
status
ok
fetched_at
2026-08-26 15:29:17