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?
As you can see Either
in not on list oftypes 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:
Left
or Right
decoding?Left
fallback Right
or Right
fallback Left
make sense? Either[X, X]
? If you have an idea what is expected behavior you can implement your own codec and use it in derivation.
There are might be several ways of doing it, but I don't like using Either as a config representation. Thus, I would suggest to use ADT approach with sealed trait:
sealed trait NameUrl {
val name: Int
}
case class Name(
name: Int,
url: String
) extends NameUrl
case class NameUrlObj(
name: Int,
url: Map[String, String]
) extends NameUrl
Sorry for my naming here. This would be a representation of of your config. We need to modify a bit our config to parse easily the config with you ADT. In order to support generic types you should add your spefici type name for each subtype. I'm going to put here full example so that you can run it on your machine:
import com.typesafe.config.ConfigFactory
import pureconfig.generic.auto._
import pureconfig.ConfigSource
object TstObj extends App {
sealed trait NameUrl {
val name: Int
}
case class Name(
name: Int,
url: String
) extends NameUrl
case class NameUrlObj(
name: Int,
url: Map[String, String]
) extends NameUrl
val cfgStr = ConfigFactory.parseString(
"""
|abc: [
| {
| type: name,
| name = 1
| url = "http://example.com"
| },
| {
| type: name,
| name = 1
| url = "http://example.com"
| },
| {
| type: name-url-obj,
| name = 3
| url = {
| "A": "http://example3.com"
| "B": "http://example4.com"
| }
| }
|]
|""".stripMargin
)
case class RootA(abc: List[NameUrl])
println(ConfigSource.fromConfig(cfgStr).loadOrThrow[RootA])
}
You can read more here about Sealed Families here
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.