Представляя либо в pureconfig - PullRequest
1 голос
/ 06 апреля 2020

У меня есть конфигурация 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-адресов, каждый из которых имеет ключ?

Я пробовал это:

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, значит ли это, что его можно заменить чем-то другим?

Ответы [ 2 ]

4 голосов
/ 06 апреля 2020

Как видите, 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 - это поле будет использоваться для отправки анализа на указанный подтип c. Все остальные поля будут переданы как поля для анализа в этом подтипе.

Если это не сработает, вы можете попробовать реализовать код c самостоятельно:

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

Это не предусмотрено по умолчанию, поскольку вам придется ответить на несколько вопросов самостоятельно:

  • как вы решите, если Вы должны go с Left или Right декодированием?
  • делает Left отступление Right или Right отступление Left имеет смысл?
  • как насчет Either[X, X]?

Если вы понимаете, что такое ожидаемое поведение, вы можете реализовать свой изношенный код c и использовать его при деривации.

1 голос
/ 06 апреля 2020

Может быть несколько способов сделать это, но мне не нравится использовать 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. Для поддержки обобщенных c типов вы должны добавить имя типа spefici для каждого подтипа. Я приведу здесь полный пример, чтобы вы могли запустить его на своем компьютере:

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])

}

Подробнее о Запечатанных Семействах можно прочитать здесь

...