3

我有一个这样的 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]

但它导致“预期类型 OBJECT。找到 STRING。”

我知道 pureconfig 支持Option. 我发现没有提到支持Either,是不是意味着它可以用其他东西代替?

4

2 回答 2

5

如您所见Either,不在开箱即用支持的类型列表中。

然而Either属于密封的家庭,所以:

@ 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")))

默认情况下不提供此功能,因为您必须自己回答一些问题:

  • 你如何决定是否应该使用LeftRight解码?
  • Left后备RightRight后备Left有意义吗?
  • 怎么样Either[X, X]

如果您知道预期的行为是什么,您可以实现磨损的编解码器并在派生中使用它。

于 2020-04-06T13:07:30.333 回答
1

可能有几种方法可以做到这一点,但我不喜欢使用 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])

}

你可以在这里阅读更多关于密封家庭的信息

于 2020-04-06T13:12:03.340 回答