Разобрать список json на два типа списков по значению поля в Scala circe

Мне дали (пример) json:

{
    "version": 1.1,
    "author": "XYZ",
    "elements": [{
            "type": "nodeX",
            "id": 1,
            "a": 1,
            "b": 2
        },
        {
            "type": "nodeX",
            "id": 2,
            "a": 1,
            "b": 2
        },

        ...

        {
            "type": "nodeX",
            "id": 13214,
            "a": 1,
            "b": 2
        },
        {
            "type": "nodeY",
            "id": 1,
            "c": [
                "qaz",
                "wsx"
            ]
        },
        {
            "type": "nodeY",
            "id": 2,
            "c": [
                "qaz",
                "wsx"
            ]
        },

        ...

        {
            "type": "nodeY",
            "id": 3,
            "c": [
                "qaz",
                "wsx"
            ]
        }
    ]
}

Список элементов всегда содержит объекты с двумя возможными вариантами:

  • введите «nodeX» и свойства: id, a и b.
  • введите «nodeY» и свойства: id и c.

Я хочу получить два списка данных классов:

case class NodeX(val id:Long, val a:Long, val b:Long)
case class NodeY(val id:Long, val c:List[String])

Я пробовал circe (библиотека Scala), чтобы разобрать этот json на классы:

case class Element(val 'type':String, val id:Long, val a:Option[Long],val b:Option[Long], val c:Option[List[String]])
case class MyJson(val version:Double, val author:String, val elements:List[Element])

но, к сожалению, я получил список объектов с дополнительными полями.
В настоящее время я использую это как обходной путь:

val elements = // MyJson.elements
for (elem <- elements)
    elem match {
      case Element("nodeX", _,_,_,_) => //here convert to NodeX and add to list List[NodeX]
      case Element("nodeY", _,_,_,_) => //here convert to NodeY and add to list List[NodeY]
    }

Я ищу лучшее решение, более быстрое решение, потому что список в этом json не содержит менее 70 тыс. Элементов.

Заранее спасибо :)


person BoyFarmer    schedule 05.09.2017    source источник
comment
Как ты расшифровываешь? Полуавтоматические производные декодеры?   -  person michaJlS    schedule 06.09.2017
comment
@michaJlS Я декодирую с помощью decode[MyJson](json), у меня нет опыта работы с Circe. Я читал о полуавтоматических производных декодерах, но понятия не имею, как их использовать   -  person BoyFarmer    schedule 06.09.2017
comment
Вам нужно будет показать свой код синтаксического анализа и сказать, откуда вы берете этот json.   -  person michaJlS    schedule 09.09.2017


Ответы (2)


другой способ - динамический Scala в Circe + Monocle, который дает вам небезопасную гибкость динамического языка:

libraryDependencies += "io.circe" %% "circe-optics" % circeVersion
import io.circe.optics.JsonPath._
import io.circe.parser

val json = parser.parse(jsonString).right.get

case class NodeX(val id:Long, val a:Long, val b:Long)
case class NodeY(val id:Long, val c:List[String]) 

val nodexs = root.elements.each
    .filter(root.`type`.string.getOption(_).contains("nodeX"))
    .as[NodeX].getAll(json)
//res: List[NodeX] = List(NodeX(1L, 1L, 2L), NodeX(2L, 1L, 2L))
val nodeys = root.elements.each
    .filter(root.`type`.string.getOption(_).contains("nodeY"))
    .as[NodeY].getAll(json)
//res: List[NodeY] = List(NodeY(3L, List("qaz", "wsx")))

И я считаю, что circe-generic автоматический режим может делать то же, что описано в ответе Андрея Плохотнюка.

person WeiChing 林煒清    schedule 23.03.2018

Если вам разрешено определять классы NodeX и NodeY как ADT с запечатанным признаком, это можно легко проанализировать с помощью jsoniter-scala.

Добавьте библиотеку в список зависимостей

libraryDependencies ++= Seq(
  "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "0.29.2" % Compile, 
  "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "0.29.2" % Provided // required only in compile-time
)

Определите свои классы случаев:

sealed trait Node
final case class NodeX(val id:Long, val a:Long, val b:Long) extends Node
final case class NodeY(val id:Long, val c:List[String]) extends Node

case class MyJson(val version:Double, val author:String, val elements:List[Node])

Сгенерируйте кодек для корневого класса и используйте его

import java.io._
import com.github.plokhotnyuk.jsoniter_scala.macros._
import com.github.plokhotnyuk.jsoniter_scala.core._

val myJsonCodec = JsonCodecMaker.make[MyJson](CodecMakerConfig())

val myJson = {
  val fin = new FileInputStream("/tmp/my.json")
  try readFromStream(codec, fin)
  finally fin.close()
}
person Andriy Plokhotnyuk    schedule 07.02.2018