Search code examples
jsonscalacirce

Deserialize JSON based on Field in Payload in Scala


I have a similar question with Deserialize json based on fields in .Net (C#), but do it in Scala.

I have an app which streams in 2 types of json objects (Account and User).

Account:

{
  "data_type": "account",
  "id": 1,
  "type": "Trial",
  "created_at": 1523982003,
}

User:

{
  "data_type": "user",
  "id": 1,
  "account_id": 1,
  "department": "Finance"
  "created_at": 1523982122
}

I need to deserialize the two above json objects based on the field data_type in Scala with help of Circe library.

How can I do this?


Solution

  • This snippet works for me in Ammonite:

    import $ivy.`io.circe:circe-core_2.12:0.9.3`, io.circe._
    import $ivy.`io.circe:circe-generic_2.12:0.9.3`, io.circe.generic._
    import $ivy.`io.circe:circe-generic-extras_2.12:0.9.3`, io.circe.generic.extras._
    interp.load.plugin.ivy("org.scalamacros" % "paradise_2.12.4" % "2.1.1")
    
    implicit val config: Configuration = Configuration.
      default.
      withSnakeCaseMemberNames.
      withDiscriminator("data_type").
      copy(transformConstructorNames = _.toLowerCase)
    
    {
    @ConfiguredJsonCodec
    sealed trait InputEntity
    object InputEntity {
      @ConfiguredJsonCodec case class Account(id: Long, `type`: String, createdAt: Long) extends InputEntity
      @ConfiguredJsonCodec case class User(id: Long, accountId: Long, department: String, createdAt: Long) extends InputEntity
    }
    } 
    
    import $ivy.`io.circe:circe-parser_2.12:0.9.3`, io.circe.parser._
    
    
    val accountJson = """
    {
      "data_type": "account",
      "id": 1,
      "type": "Trial",
      "created_at": 1523982003
    }
    """
    val account = decode[InputEntity](accountJson)
    // account: Either[Error, InputEntity] = Right(Account(1L, "Trial", 1523982003L)
    
    val userJson = """
    {
      "data_type": "user",
      "id": 1,
      "account_id": 1,
      "department": "Finance",
      "created_at": 1523982122
    }
    """
    val user = decode[InputEntity](userJson)
    // user: Either[Error, InputEntity] = Right(User(1L, 1L, "Finance", 1523982122L))
    

    (BTW: you had syntax errors in your JSON examples that would make the parser fail, so I fixed them in code above).

    The most important here is

    • Configuration from io.circe.generic.extras._ that defines discriminator field,
    • keeping classes as sum type,
    • if you use annotations for generating codecs replace @JsonCodec with @ConfiguredJsonCodec.

    Actually, you could also replace these Strings with enums, and read createdAt as LocalDateTime or similar, but that would be out of scope of this question.