Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Representing Either in pureconfig

I have a HOCON config like this:

[
    {
        name = 1
        url = "http://example.com"
    },
    {
        name = 2
        url = "http://example2.com"
    },
    {
        name = 3
        url = {
            A = "http://example3.com"
            B = "http://example4.com"
        }
    }
]

I want to parse it with pureconfig. How can I represent that the URL can be either a string or a map of multiple urls, each having a key?

I have tried this:

import pureconfig.ConfigSource
import pureconfig.generic.auto.exportReader

case class Site(name: Int, url: Either[String, Map[String, String]])
case class Config(sites: List[Site])
ConfigSource.default.loadOrThrow[Config]

But it resulted in "Expected type OBJECT. Found STRING instead."

I know pureconfig supports Option. I have found no mention of supporting Either, does it mean it can be replaced with something else?

like image 943
mirelon Avatar asked Dec 09 '25 21:12

mirelon


1 Answers

As you can see Either in not on list of types supported out of the box.

However Either falls under sealed family, so:

@ ConfigSource.string("""{ type: left, value: "test" }""").load[Either[String, String]]
res15: ConfigReader.Result[Either[String, String]] = Right(Left("test"))

@ ConfigSource.string("""{ type: right, value: "test" }""").load[Either[String, String]]
res16: ConfigReader.Result[Either[String, String]] = Right(Right("test"))

works. If you have a sealed hierarchy, what pureconfig will do is require an object which has a field type - this field will be used to dispatch parsing to a specific subtype. All the other fields will be passed as fields to parse into that subtype.

If that doesn't work for you, you might try to implement the codec yourself:

// just an example
implicit def eitherReader[A: ConfigReader, B: ConfigReader] =
  new ConfigReader[Either[A, B]] {
    def from(cur: ConfigCursor) =
      // try left, if fail try right
      ConfigReader[A].from(cur).map(Left(_)) orElse ConfigReader[B].from(cur).map(Right(_))
  }

which now will not require discrimination value:

@ ConfigSource.string("""{ test: "test" }""").load[Map[String, Either[String, String]]]
res26: ConfigReader.Result[Map[String, Either[String, String]]] = Right(Map("test" -> Left("test")))

This is not provided by default because you would have to answer a few things yourself:

  • how do you decide if you should go with Left or Right decoding?
  • does Left fallback Right or Right fallback Left make sense?
  • how about Either[X, X]?

If you have an idea what is expected behavior you can implement your own codec and use it in derivation.

like image 84
Mateusz Kubuszok Avatar answered Dec 12 '25 12:12

Mateusz Kubuszok