在 pureconfig 中表示 Either
Representing Either in pureconfig
我有这样的 HOCON 配置:
[
{
name = 1
url = "http://example.com"
},
{
name = 2
url = "http://example2.com"
},
{
name = 3
url = {
A = "http://example3.com"
B = "http://example4.com"
}
}
]
我想用 pureconfig 解析它。
我如何表示 URL 可以是字符串或多个 url 的映射,每个 url 都有一个键?
我试过这个:
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]
但结果是 "Expected type OBJECT. Found STRING instead."
我知道 pureconfig 支持 Option
。没找到支持Either
,是不是可以换成别的东西?
如您所见,Either
不在 types supported out of the box 的列表中。
但是 Either
属于 sealed family,所以:
@ 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"))
有效。如果你有一个密封的层次结构,pureconfig 将做的是需要一个具有字段 type
的对象——这个字段将用于将解析分派到特定的子类型。所有其他字段将作为字段传递以解析为该子类型。
如果这对您不起作用,您可以尝试自己实现编解码器:
// 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(_))
}
现在不需要区分值:
@ ConfigSource.string("""{ test: "test" }""").load[Map[String, Either[String, String]]]
res26: ConfigReader.Result[Map[String, Either[String, String]]] = Right(Map("test" -> Left("test")))
这不是默认提供的,因为您必须自己回答一些问题:
- 你如何决定你应该使用
Left
还是 Right
解码?
Left
回退 Right
或 Right
回退 Left
是否有意义?
Either[X, X]
怎么样?
如果您知道预期的行为是什么,您可以实现旧的编解码器并在推导中使用它。
可能有几种方法可以做到这一点,但我不喜欢使用 Either 作为配置表示。因此,我建议使用具有密封特征的 ADT 方法:
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
抱歉我在这里命名。这将代表您的配置。
我们需要稍微修改一下我们的配置,以便使用您的 ADT 轻松解析配置。为了支持通用类型,您应该为每个子类型添加您的特定类型名称。
我将在此处提供完整示例,以便您可以 运行 在您的计算机上使用它:
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])
}
您可以在此处阅读有关 Sealed Families here
的更多信息
我有这样的 HOCON 配置:
[
{
name = 1
url = "http://example.com"
},
{
name = 2
url = "http://example2.com"
},
{
name = 3
url = {
A = "http://example3.com"
B = "http://example4.com"
}
}
]
我想用 pureconfig 解析它。 我如何表示 URL 可以是字符串或多个 url 的映射,每个 url 都有一个键?
我试过这个:
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]
但结果是 "Expected type OBJECT. Found STRING instead."
我知道 pureconfig 支持 Option
。没找到支持Either
,是不是可以换成别的东西?
如您所见,Either
不在 types supported out of the box 的列表中。
但是 Either
属于 sealed family,所以:
@ 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"))
有效。如果你有一个密封的层次结构,pureconfig 将做的是需要一个具有字段 type
的对象——这个字段将用于将解析分派到特定的子类型。所有其他字段将作为字段传递以解析为该子类型。
如果这对您不起作用,您可以尝试自己实现编解码器:
// 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(_))
}
现在不需要区分值:
@ ConfigSource.string("""{ test: "test" }""").load[Map[String, Either[String, String]]]
res26: ConfigReader.Result[Map[String, Either[String, String]]] = Right(Map("test" -> Left("test")))
这不是默认提供的,因为您必须自己回答一些问题:
- 你如何决定你应该使用
Left
还是Right
解码? Left
回退Right
或Right
回退Left
是否有意义?Either[X, X]
怎么样?
如果您知道预期的行为是什么,您可以实现旧的编解码器并在推导中使用它。
可能有几种方法可以做到这一点,但我不喜欢使用 Either 作为配置表示。因此,我建议使用具有密封特征的 ADT 方法:
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
抱歉我在这里命名。这将代表您的配置。 我们需要稍微修改一下我们的配置,以便使用您的 ADT 轻松解析配置。为了支持通用类型,您应该为每个子类型添加您的特定类型名称。 我将在此处提供完整示例,以便您可以 运行 在您的计算机上使用它:
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])
}
您可以在此处阅读有关 Sealed Families here
的更多信息