Как сериализовать классы case с чертами с jsonspray

Я понимаю, что если у меня есть:

case class Person(name: String)

Я могу использовать

object PersonJsonImplicits extends DefaultJsonProtocol {
  implicit val impPerson = jsonFormat1(Person)
}

и таким образом сериализуйте его с помощью:

import com.example.PersonJsonImplicits._
import spray.json._
new Person("somename").toJson

однако что, если у меня есть

trait Animal
case class Person(name: String) extends Animal

и у меня есть где-то в моем коде

val animal = ???

и мне нужно сериализовать его, и я хочу использовать json spray

какой сериализатор я должен добавить, я надеялся что-то вроде:

object AnimalJsonImplicits extends DefaultJsonProtocol {
  implicit val impAnimal = jsonFormat???(Animal)
}

где, возможно, мне нужно было добавить некоторый матчи, чтобы проверить, какой тип является Animal, чтобы, если бы его лицо я направил его человеку, но ничего не нашел... читал https://github.com/spray/spray-json и не понимают, как это сделать.

так как я могу сериализовать набор

trait Animal
case class Person(name: String) extends Animal

с помощью json spray?

Ответы

Ответ 1

У вас есть несколько вариантов:

Вариант 1

Расширьте RootJsonFormat[Animal] и поместите свою собственную логику для соответствия различным типам Animal:

import spray.json._
import DefaultJsonProtocol._

trait Animal   
case class Person(name: String, kind: String = "person") extends Animal

implicit val personFormat = jsonFormat2(Person.apply)   
implicit object AnimalJsonFormat extends RootJsonFormat[Animal] {
  def write(a: Animal) = a match {
    case p: Person => p.toJson
  }
  def read(value: JsValue) = 
    // If you need to read, you will need something in the 
    // JSON that will tell you which subclass to use
    value.asJsObject.fields("kind") match {
      case JsString("person") => value.convertTo[Person]
    }
}

val a: Animal = Person("Bob")
val j = a.toJson
val a2 = j.convertTo[Animal]

Если вы вставляете этот код в Scala REPL, вы получаете этот результат:

a: Animal = Person(Bob,person)
j: spray.json.JsValue = {"name":"Bob","kind":"person"}
a2: Animal = Person(Bob,person)

Источник

Вариант 2

Другой вариант - предоставить неявный jsonFormat для Person и любые другие подклассы Animal, а затем записать код сериализации следующим образом:

def write(a: Animal) = a match {
  case p: Person => p.toJson
  case c: Cat => c.toJson
  case d: Dog => d.toJson
}

Источник

Ответ 2

Это можно сделать с помощью расширения RootJsonFormat. Примеры можно найти в здесь.

Ответ 3

Вы можете добавить дополнительное поле type при сериализации и использовать его для определения реализации черты при десериализации:

trait Animal
case class Person(name: String) extends Animal
case class Lion(age: Int) extends Animal

implicit def personFormat = jsonFormat1(Person)
implicit def lionFormat = jsonFormat1(Lion)

implicit def animalFormat = 
  new RootJsonFormat[Animal] {
    override def read(json: JsValue): Animal = 
      json.asJsObject.fields.get("type") match {
        case Some(JsString("person")) => json.convertTo[Person]
        case Some(JsString("lion")) => json.convertTo[Lion]
        case t => deserializationError(s"Unable to deserialize Animal of type $t")
      }

    override def write(obj: Animal): JsValue =
      obj match {
        case e: Person => toJson(e, "person")
        case e: Lion => toJson(e, "lion")
      }

    def toJson[T](obj: T, objType: String)(implicit w: JsonWriter[T]): JsObject = {
      val o = obj.toJson.asJsObject
      o.copy(fields = o.fields + ("type" -> JsString(objType)))
    }
  }