0
votes

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.

3
@LuisMiguelMejíaSuárez I corrected the code - Arjun Karnwal
@LuisMiguelMejíaSuárez I am stuck with writing custom code too since its map since map. I do not know how to proceed. - Arjun Karnwal

3 Answers

2
votes

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())
//   )
// )
1
votes

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 -> ...
-1
votes

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)