Using Circe to Decode Json String

Using Circe to Decode Json String

I have a Json string as

val configInfo =   {
      "car": {
        "requesters": {
          "request1": "key1",
          "request2": "key2"
        },
        "provider": {
          "request1": "key3",
          "request2": "key4"
        }
      },
      "truck": {
        "requesters": {
          "request1": "key2",
          "request2": "key3"
        },
        "provider": {
          "request1": "key2",
          "request2": "key3"
        }
      }
    }

I have the following case class to decode json string.

final case class TenantSettings(requesters: Map[String, String], provider: Map[String, String] = Map.empty)

Also, providers in above case class is optional, it may or may not be present in json string.

I want to decode this using circe to

val value = decode[Map[String, TenantSettings]](configInfo)

I understand I need to use custom circe codec, but I am unable to do it, Can someone please help out on this.

2

3 Answers

You do not need a custom codec. You only need to need to configure the derived codec using circe-generic-extras.

So given:

val configInfo =
    """{
        "car": {
          "requesters": {
            "request1": "key1",
            "request2": "key2"
          },
          "provider": {
            "request1": "key3",
            "request2": "key4"
          }
        },
        "truck": {
          "requesters": {
            "request1": "key2",
            "request2": "key3"
          },
          "provider": {
            "request1": "key2",
            "request2": "key3"
          }
        },
        "blah": {
          "requesters": {
            "request1": "key2"
          }
        }
      }"""

You can:

import io.circe.Decoder
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.semiauto.deriveConfiguredDecoder

final case class TenantSettings(requesters: Map[String, String], provider: Map[String, String] = Map.empty)
object TentantSettings {
  private implicit final val customConfig: Configuration = Configuration.default.withDefaults
  implicit final val TenantSettingsDecoder: Decoder[TenantSettings] = deriveConfiguredDecoder
}

And then:

io.circe.parser.decode[Map[String, TenantSettings]](configInfo)
// res: Either[io.circe.Error, Map[String, TenantSettings]] = Right(
//   Map(
//     "car" -> TenantSettings(Map("request1" -> "key1", "request2" -> "key2"), Map("request1" -> "key3", "request2" -> "key4")),
//     "truck" -> TenantSettings(Map("request1" -> "key2", "request2" -> "key3"), Map("request1" -> "key2", "request2" -> "key3")),
//     "blah" -> TenantSettings(Map("request1" -> "key2"), Map())
//   )
// )
8

Circe provides all Decoder instances you need except the one for TenantSettings since it's a custom type. Map and String are no problem.

circe-generic-extras or circe-derivation are very useful to generate Decoder instances for case classes. In this case you want to enable "defaults" in the config, so it falls back to the empty map instead of failing.

Here's the code:

// you need circe-core, circe-parser and circe-generic-extras as dependencies
import io.circe._
import io.circe.parser.decode
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.semiauto._

val configInfo = """{
    "car": {
      "requesters": {
        "request1": "key1",
        "request2": "key2"
      },
      "provider": {
        "request1": "key3",
        "request2": "key4"
      }
    },
    "truck": {
      "requesters": {
        "request1": "key2",
        "request2": "key3"
      },
      "provider": {
        "request1": "key2",
        "request2": "key3"
      }
    }
  }
  """

final case class TenantSettings(
  requesters: Map[String, String],
  provider: Map[String, String] = Map.empty
)
object TenantSettings {
  implicit private val derivationConfig: Configuration = Configuration.default.withDefaults

  implicit val decodeTenantSettings: Decoder[TenantSettings] = deriveConfiguredDecoder
}

val value = decode[Map[String, TenantSettings]](configInfo)
println(value) // Right(Map(car -> TenantSettings(Map(request1 -> key1, request2 -> ...
5

This is what I did and worked for me

implicit val decodeTenantSetting: Decoder[TenantSettings] = new Decoder[TenantSettings] {
      final def apply(c: HCursor): Decoder.Result[TenantSettings] =
        for {
          requesters <- c.downField("requesters").as[Map[String, String]]
        } yield {
          TenantSettings(requesters)
        }
    }

val abc = decode[Map[String, TenantSettings]](configInfo)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Robert Thorne
Author

Robert Thorne

Robert Thorne covers electric vehicle innovations, autonomous driving systems, global mobility trends, and automotive engineering developments.