This is mainly a note to self on how to build inheritance hierarchies (e.g. Animal -> [Cat / Dog] ) with Play-JSON.
import play.api.libs.json._ | |
sealed trait Animal { | |
val `type`: String | |
} | |
case class Cat(name: String) extends Animal { | |
override val `type`: String = "cat" | |
} | |
case class Dog(name: String) extends Animal { | |
override val `type`: String = "dog" | |
} | |
trait AnimalJsonSupport | |
extends de.heikoseeberger.akkahttpplayjson.PlayJsonSupport { | |
implicit val catReads: Reads[Cat] = | |
(JsPath \ "name").read[String].map(Cat(_)) | |
implicit val dogReads: Reads[Dog] = | |
(JsPath \ "name").read[String].map(Dog(_)) | |
implicit val catWrites: Writes[Cat] = e => | |
JsObject(Seq("type" –> JsString("cat"), "name" –> JsString(e.name))) | |
implicit val dogWrites: Writes[Dog] = e => | |
JsObject(Seq("type" –> JsString("dog"), "name" –> JsString(e.name))) | |
implicit val animalReads: Reads[Animal] = Reads[Animal](jsValue => { | |
(jsValue \ "type").toEither | |
.map( | |
v => | |
v.as[String] match { | |
case "cat" => catReads.map(_.asInstanceOf[Animal]).reads(jsValue) | |
case "dog" => dogReads.map(_.asInstanceOf[Animal]).reads(jsValue) | |
case t => | |
JsError((JsPath \ "type"), "unkown animal type $t") | |
.asInstanceOf[JsResult[Animal]] | |
} | |
) | |
.fold(v => JsError(JsPath, v).asInstanceOf[JsResult[Animal]], v => v) | |
}) | |
implicit val animalWrites: Writes[Animal] = Json.writes[Animal] | |
} |
import org.scalatest.matchers.should.Matchers | |
import org.scalatest.wordspec.AnyWordSpec | |
import play.api.libs.json.Json | |
class AnimalJsonSupportSpec | |
extends AnyWordSpec | |
with Matchers | |
with AnimalJsonSupport | |
{ | |
val catJsonString = | |
""" | |
|{ | |
|"type":"cat", | |
|"name":"test" | |
|} | |
|""".stripMargin.replace("\n", "") | |
val dummyRequestResponse = Cat("test") | |
"The AnimalJsonSupport" when { | |
"handling the Cat-Type" should { | |
"correctly serialize the response" in { | |
Json.toJson(dummyRequestResponse).toString() should ===(catJsonString) | |
} | |
"correctly deserialize the cat response" in { | |
val parseResult = Json.fromJson[Animal](Json.parse(catJsonString)) | |
parseResult.get should ===(dummyRequestResponse) | |
} | |
} | |
"handling the Dog-Type" should { | |
"correctly deserialize the dog response" in { | |
val json = | |
""" | |
|{ | |
|"type":"dog", | |
|"name":"test2" | |
|} | |
|""".stripMargin.replace("\n", "") | |
val parseResult = Json.fromJson[Animal](Json.parse(json)) | |
parseResult.get should ===(Dog("test2")) | |
} | |
} | |
"handling other types" should { | |
"fail trying to deserialize a unknown type" in { | |
val json = """ | |
|{ | |
|"type":"unknown", | |
|"name":"test" | |
|} | |
|""".stripMargin.replace("\n", "") | |
val result = Json.fromJson[Animal](Json.parse(json)) | |
result.isError shouldBe(true) | |
} | |
} | |
} | |
} |
Documentation can be found here: