Парс Json от случая змеи к случаю верблюда - PullRequest
0 голосов
/ 14 октября 2019

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

 val json = """
      {
        "items": [
        {
          "id": "7913",
          "route_id": "33",
          "predictable": true,
          "run_id": "33_486_1",
          "latitude": 34.0234949,
          "longitude": -118.398712,
          "heading": 236,
          "seconds_since_report": 59
        },
        {
          "id": "4140",
          "route_id": "76",
          "predictable": true,
          "run_id": "76_174_0",
          "latitude": 34.0331945,
          "longitude": -118.2646534,
          "heading": 122,
          "seconds_since_report": 12
        },
        {
          "id": "7620",
          "route_id": "20",
          "predictable": true,
          "run_id": "20_669_0",
          "latitude": 34.013733,
          "longitude": -118.490067,
          "heading": 334,
          "seconds_since_report": 172
        }
        ]
      }
      """.stripMargin

, который я хочу преобразовать в

  final case class Sample(
    id: Int,
    routeId: Int,
    predictable: Boolean,
    runId: String,
    latitude: Double,
    longitude: Double,
    heading: Int,
    secondsSinceReport: Int
  )

Пробовал использовать

implicit val sampleDecoder = Decoder[List[Sample]].prepare(_.downField("items"))
val decodingResult = parser.decode(json)(sampleDecoder)

, но результат получается как

Attempt to decode value on failed cursor: DownField(routeId),DownArray,DownField(items)

, однако, если я закомментирую поля с верблюжьим регистром из класса наблюдений, я получу

Sample(7913,true,34.0234949,-118.398712,236)
Sample(4140,true,34.0331945,-118.2646534,122)
Sample(7620,true,34.013733,-118.490067,334)

Ответы [ 3 ]

1 голос
/ 14 октября 2019

Ваш вариант использования прямо из документов: https://circe.github.io/circe/codecs/custom-codecs.html

import io.circe.generic.extras._, io.circe.syntax._
// import io.circe.generic.extras._
// import io.circe.syntax._

implicit val config: Configuration = Configuration.default.withSnakeCaseMemberNames
// config: io.circe.generic.extras.Configuration = Configuration(io.circe.generic.extras.Configuration$$$Lambda$9172/0x0000000801132040@69e0f3f6,io.circe.generic.extras.Configuration$$$Lambda$9171/0x0000000801133040@66433b0e,false,None,false)

@ConfiguredJsonCodec case class User(firstName: String, lastName: String)
// defined class User
// defined object User

User("Foo", "McBar").asJson
// res1: io.circe.Json =
// {
//   "first_name" : "Foo",
//   "last_name" : "McBar"
// }

Вам нужна зависимость generic-extras в build.sbt:

libraryDependencies ++= Seq(
  "io.circe" %% "circe-core",
  "io.circe" %% "circe-generic",
  "io.circe" %% "circe-generic-extras",
  "io.circe" %% "circe-parser"
).map(_ % circeVersion)

addCompilerPlugin("org.scalamacros" %% "paradise" % "2.1.1" cross CrossVersion.full)

Документы говорят, что вымне не нужен плагин компилятора в 2.13.x, но я не смог его скомпилировать. Также рай еще не опубликован для 2.13.x. Таким образом, это решение работает только в 2.12 и более ранних версиях (пока).

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


   implicit val decodeSample: Decoder[Sample] = new Decoder[Sample] {
     final def apply(c: HCursor): Decoder.Result[Sample] =
       for {
         id <- c.downField("id").as[Int]
         routeId <- c.downField("route_id").as[Int]
         predictable <- c.downField("predictable").as[Boolean]
         runId <- c.downField("run_id").as[String]
         latitude <- c.downField("latitude").as[Double]
         longitude <- c.downField("longitude").as[Double]
         heading <- c.downField("heading").as[Int]
         secondsSinceReport <- c.downField("seconds_since_report").as[Int]
       } yield {
         new Sample(id, routeId, predictable, runId, latitude, longitude, heading, secondsSinceReport)
       }
   }
1 голос
/ 14 октября 2019

Наконец-то разобрался без


implicit val encoder : Encoder[Sample] = 
    Encoder.forProduct8(
      "id",
      "route_id",
      "predictable",
      "run_id",
      "latitude",
      "longitude",
      "heading",
      "seconds_since_report"
    )(Sample.unapply(_).get)

  implicit val decoder : Decoder[Sample] =
    Decoder.forProduct8(
      "id",
      "route_id",
      "predictable",
      "run_id",
      "latitude",
      "longitude",
      "heading",
      "seconds_since_report"
    )(Sample.apply)



   implicit val sampleDecoder = Decoder[List[Sample]].prepare(_.downField("items"))
   val decodingResult = parser.decode(json)(sampleDecoder)

   decodingResult match {
        case Left(value) => println(value.getMessage())
        case Right(value) => value.foreach(println)
   }

И результат соответствует поставленному json

Sample(7913,33,true,33_486_1,34.0234949,-118.398712,236,59)
Sample(4140,76,true,76_174_0,34.0331945,-118.2646534,122,12)
Sample(7620,20,true,20_669_0,34.013733,-118.490067,334,172)
1 голос
/ 14 октября 2019

Если вы в порядке с другой библиотекой JSON, "play-json" подойдет. Это отдельная библиотека.

final case class Sample(
    id: String,
    routeId: String,
    predictable: Boolean,
    runId: String,
    latitude: Double,
    longitude: Double,
    heading: Int,
    secondsSinceReport: Int)

final case class Samples(items: Seq[Sample])
import play.api.libs.json._

implicit val jsonCaseConversion = JsonConfiguration(JsonNaming.SnakeCase)
implicit val sampleJsonFormat = Json.format[Sample]
implicit val samplesJsonFormat = Json.format[Samples]

val json = """
  {
    "items": [
    {
      "id": "7913",
      "route_id": "33",
      "predictable": true,
      "run_id": "33_486_1",
      "latitude": 34.0234949,
      "longitude": -118.398712,
      "heading": 236,
      "seconds_since_report": 59
    },
    {
      "id": "4140",
      "route_id": "76",
      "predictable": true,
      "run_id": "76_174_0",
      "latitude": 34.0331945,
      "longitude": -118.2646534,
      "heading": 122,
      "seconds_since_report": 12
    },
    {
      "id": "7620",
      "route_id": "20",
      "predictable": true,
      "run_id": "20_669_0",
      "latitude": 34.013733,
      "longitude": -118.490067,
      "heading": 334,
      "seconds_since_report": 172
    }
    ]
  }
""".stripMargin

val optSamples = Json.parse(json).asOpt[Samples]

Я изменил Sample.id на String тип. Если вы хотите, чтобы это было Int, проще всего добавить метод, подобный этому

// this might thorw an exception.
// maybe there is a reason why id in json is string
def intId: Int = id.toInt
...