Используйте платформу Play для чтения JSON с рекурсивным списком запечатанных признаков

У меня есть JSON, который мне нужно разобрать, и мой проект уже использует Play, так что это кажется правильным местом для начала.

Учитывая эти определения:

sealed trait Thing
case class Foo(i: Int) extends Thing
case class Bar(s: String, t: List[Thing]) extends Thing

Я бы хотел этот JSON:

{
  s: "doomy doomy doom",
  t: [ 24, { s: "doooom!", t: [ 1, 2, 3 ] }, 42, 126 ]
}

Чтобы стать этим объектом:

Bar("doomy doomy doom", List(Foo(24), Bar("doooom!", List(Foo(1), Foo(2), Foo(3))), Foo(42), Foo(126)))

Какие-либо предложения?

Вы пробовали playframework.com/documentation/2.8.x/… stackoverflow.com/questions/75596492/… ?

Dmytro Mitin 10.04.2023 21:36

@Dmytro Mitin Спасибо за ваше предложение. Один из моих камней преткновения заключается в том, что любой элемент списка может быть либо Foo, либо Bar, к чему этот вопрос не относится.

Jeremy 10.04.2023 21:39
Как сделать HTTP-запрос в Javascript?
Как сделать HTTP-запрос в Javascript?
В JavaScript вы можете сделать HTTP-запрос, используя объект XMLHttpRequest или более новый API fetch. Вот пример для обоих методов:
0
2
68
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Попробуйте определить пользовательские кодеки для трейта (без дискриминатора) и ленивые кодеки для рекурсивного типа.

import play.api.libs.json._
import play.api.libs.functional.syntax._

sealed trait Thing
object Thing {
  implicit val thingReads: Reads[Thing] = Foo.fooReads.or[Thing](Bar.barReads.widen)

  implicit val thingWrites: OWrites[Thing] = {
    case x: Foo => Foo.fooWrites.writes(x)
    case x: Bar => Bar.barWrites.writes(x)
  }
}

case class Foo(i: Int) extends Thing
object Foo {
  implicit val fooReads: Reads[Foo] = Json.reads[Foo]
  implicit val fooWrites: OWrites[Foo] = Json.writes[Foo]
}

case class Bar(s: String, t: List[Thing]) extends Thing
object Bar {
  implicit val barReads: Reads[Bar] = (
    (__ \ "s").read[String] and
      (__ \ "t").lazyRead(Reads.list[Thing](Thing.thingReads))
  )(Bar.apply _)

  implicit val barWrites: OWrites[Bar] = (
    (__ \ "s").write[String] and
      (__ \ "t").lazyWrite(Writes.list[Thing](Thing.thingWrites))
  )(unlift(Bar.unapply))
}

val thing: Thing = Bar("doomy doomy doom", List(Foo(24), Bar("doooom!", List(Foo(1), Foo(2), Foo(3))), Foo(42), Foo(126)))
val str = Json.stringify(Json.toJson(thing))
//{"s":"doomy doomy doom","t":[{"i":24},{"s":"doooom!","t":[{"i":1},{"i":2},{"i":3}]},{"i":42},{"i":126}]}
val thing1 = Json.parse(str).as[Thing]
// Bar(doomy doomy doom,List(Foo(24), Bar(doooom!,List(Foo(1), Foo(2), Foo(3))), Foo(42), Foo(126)))
thing1 == thing // true

Scala play json анализ вложенных циклических зависимостей json

Ответ принят как подходящий

Чтобы добавить к ответу @dmytro-mitin, вы можете использовать класс значений Scala для Foo. Документация Play JSON включает операции чтения/записи/форматирования для классов значений. Затем вы можете использовать Int вместо объекта с одним полем в исходном примере. Вот обновленный пример с классом значений:

import play.api.libs.json._
import play.api.libs.functional.syntax._

sealed trait Thing extends Any

object Thing {
  implicit val thingReads: Reads[Thing] = Foo.fooReads.or[Thing](Bar.barReads.widen)

  implicit val thingWrites: Writes[Thing] = {
    case f: Foo => Foo.fooWrites.writes(f)
    case b: Bar => Bar.barWrites.writes(b)
  }
}

case class Foo(i: Int) extends AnyVal with Thing

object Foo {
  implicit val fooReads: Reads[Foo] = Json.valueReads[Foo]
  implicit val fooWrites: Writes[Foo] = Json.valueWrites[Foo]
}

case class Bar(s: String, t: List[Thing]) extends Thing

object Bar {
  implicit val barReads: Reads[Bar] = (
    (__ \ "s").read[String] and
      (__ \ "t").lazyRead(Reads.list[Thing](Thing.thingReads))
  )(Bar.apply _)

  implicit val barWrites: Writes[Bar] = (
    (__ \ "s").write[String] and
      (__ \ "t").lazyWrite(Writes.list[Thing](Thing.thingWrites))
  )(unlift(Bar.unapply))
}

val thing: Thing = Bar("doom", List(Foo(12), Bar("doom", List(Foo(1), Foo(2), Foo(3)))))
val json = Json.toJson(thing)
val str = Json.stringify(json)
// {"s":"doom","t":[12,{"s":"doom","t":[1,2,3]}]}
val thing1 = Json.parse(str).as[Thing]
// Bar(doom,List(Foo(12), Bar(doom,List(Foo(1), Foo(2), Foo(3)))))
thing1 == thing // true

Другие вопросы по теме