Как обрабатывать различные схемы JSON и отправлять их с помощью правильного парсера? - PullRequest
1 голос
/ 13 июля 2020

В настоящее время я создаю очень простой парсер JSON в Scala, который имеет дело с двумя (немного) разными схемами. Моя цель - проанализировать одно значение в json, и на основе этого значения я хотел бы отправить его соответствующему декодеру. Я использовал circe для своей реализации, но другие реализации и / или предложения также приветствуются.

Я сформулировал упрощенную версию для своего примера, чтобы помочь прояснить вопрос.

Есть два типа из JSON, которые я могу получить, либо акцию:

"data": {
  "name": "XYZ"
},
  "type": "STOCK"
}

, либо цитату (которая аналогична акции, но включает цену).

"data": {
  "name": "ABC",
  "price": 1151.6214,
},
  "type": "QUOTE"
}

Со своей стороны, я разработали простой декодер, который выглядит так (для акций):

implicit private val dataDecoder: Decoder[Stock] = (hCursor: HCursor) => {
    for {
      isin <- hCursor.downField("data").downField("name").as[String]
      typ <- hCursor.downField("type").as[StockType]
    } yield Instrument(name, typ, LocalDateTime.now())
  }

Я мог бы также разработать синтаксический анализатор, который анализирует только "тип" части JSON, а затем отправляет данные в обрабатываться соответствующим парсером (котировкой или акцией). Однако мне интересно, что такое:

  1. Эффективный способ сделать это
  2. Идиоматический / чистый способ сделать это

Чтобы помочь перефразировать мой вопрос при необходимости, каков правильный и эффективный способ обработки слегка отличающихся схем JSON и пересылки их для обработки правильному синтаксическому анализатору.

1 Ответ

2 голосов
/ 13 июля 2020

Я обычно сталкиваюсь с этой ситуацией, когда нужно сериализовать ADT. Как кто-то упомянул в комментариях к вашему вопросу, circe поддерживает автоматическое создание кода ADT c, однако я обычно предпочитаю писать кодеки вручную.

В любом случае в такой ситуации, как ваша, я бы сделал что-то по этим строкам:

sealed trait Data
case class StockData(name: String) extends Data
case class QuoteData(name: String, quote: Double) extends Data

implicit val stockDataEncoder: Encoder[StockData] = ???
implicit val stockDataDecoder: Decoder[StockData] = ???
implicit val quoteDataEncoder: Encoder[QuoteData] = ???
implicit val quoteDataDecoder: Decoder[QuoteData] = ???

implicit val dataEncoder: Encoder[Data] = Encoder.instance {
  case s: StockData => stockDataEncoder(s).withObject(_.add("type", "stock))
  case q: QuoteData => quoteDataEncoder(q).withObject(_.add("type", "quote"))
}

implicit val dataDecoder: Decoder[Data] = Decoder.instance { c =>
  for {
    stype <- c.get[String]("type)
    res <- stype match {
       case "stock" => stockDataDecoder(c)
       case "quote" => quoteDataDecoder(c)
       case unk => Left(DecodingFailure(s"Unsupported data type: ${unk}", c.history))
    }
  } yield res
}
...