Last active
December 27, 2015 07:49
-
-
Save nlinker/7291436 to your computer and use it in GitHub Desktop.
Play 2.1 JSON combinators with Either type
(generalized format for Either)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.tapad.dfe | |
| import play.api.libs.json._ | |
| import play.api.libs.functional.syntax._ | |
| import play.api.libs.json.JsSuccess | |
| import play.api.libs.json.JsString | |
| import play.api.libs.json.JsBoolean | |
| import play.api.data.validation.ValidationError | |
| import play.api.libs.json.JsNumber | |
| object Goblins extends App { | |
| class EitherFormat[M](implicit format: Format[M]) extends Format[Either[Int, M]] { | |
| def reads(json: JsValue): JsResult[Either[Int, M]] = { | |
| json match { | |
| case JsNumber(n) => JsSuccess(Left(n.toInt)) | |
| case JsBoolean(_) | | |
| JsString(_) | | |
| JsArray(_) => JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.jsnumber")))) | |
| case _ => format.reads(json).map(o => Right(o)) | |
| } | |
| } | |
| def writes(o: Either[Int, M]): JsValue = { | |
| o match { | |
| case Left(id) => JsNumber(id) | |
| case Right(dw: M) => format.writes(dw) | |
| } | |
| } | |
| } | |
| class Def[A](jsPath: JsPath)(implicit format: Format[A]) { | |
| def format(default: A): OFormat[A] = { | |
| val f = jsPath.format[A] | |
| new OFormat[A] { | |
| def reads(json: JsValue) = f.reads(json) orElse JsSuccess(default) | |
| def writes(o: A) = f.writes(o) | |
| } | |
| } | |
| } | |
| // object Def { | |
| // def format[A](default: A)(implicit format: Format[A]): OFormat[A] = { | |
| // new OFormat[A] { | |
| // def reads(json: JsValue): JsResult[A] = format.reads(json) orElse JsSuccess(default) | |
| // def writes(o: A): JsObject = format.writes(o) <<< error here | |
| // } | |
| // } | |
| // } | |
| implicit val jsonDwellingFormat = | |
| (new Def[Int](__ \ "did").format(0) and | |
| (__ \ "name").format[String]).apply( | |
| Dwelling.apply, unlift(Dwelling.unapply)) | |
| implicit val jsonEitherDwelling = new EitherFormat()(jsonDwellingFormat) | |
| implicit val jsonCreatureFormat = { | |
| ((__ \ "cid").format[Int] and | |
| (__ \ "dwelling").format[Either[Int, Dwelling]] and | |
| (__ \ "name").format[String] and | |
| (__ \ "carnivorous").format[Boolean] and | |
| (__ \ "details").formatNullable[String]).apply( | |
| Creature.apply, unlift(Creature.unapply)) | |
| } | |
| implicit val jsonEitherCreature = new EitherFormat()(jsonCreatureFormat) | |
| implicit val jsonStackFormat = { | |
| ((__ \ "sid").format[Int] and | |
| (__ \ "creature").format[Either[Int, Creature]] and | |
| (__ \ "count").format[Int]).apply( | |
| Stack.apply, unlift(Stack.unapply)) | |
| } | |
| def run() = { | |
| // http://might-and-magic.ubi.com/heroes-6/en-gb/game/creatures/stronghold/index.aspx | |
| val hutJ = Json.obj( | |
| "did" -> 11, | |
| "name" -> "Goblin's Hut" | |
| ) | |
| println(Json.fromJson[Dwelling](hutJ)) | |
| val goblinLeftJ = Json.obj( | |
| "cid" -> 12, | |
| "dwelling" -> 11, | |
| "name" -> "Goblin", | |
| "carnivorous" -> true | |
| ) | |
| val goblin = Json.fromJson[Creature](goblinLeftJ).get | |
| println(goblin) | |
| println(Json.toJson(goblin)) | |
| val goblinRightJ = Json.obj( | |
| "cid" -> 13, | |
| "dwelling" -> Json.obj( | |
| "did" -> 22, | |
| "name" -> "Goblin's House"), | |
| "name" -> "Goblin hunter", | |
| "carnivorous" -> true, | |
| "details" -> "Aggressive, sneaky attack" | |
| ) | |
| val goblinHunter: Creature = Json.fromJson[Creature](goblinRightJ).get | |
| println(goblinHunter) | |
| println(Json.toJson(goblinHunter)) | |
| val goblinErr1 = Json.obj( | |
| "cid" -> 13, | |
| "dwelling" -> Json.obj("did" -> 22), | |
| "name" -> "Goblin hunter", | |
| "carnivorous" -> true, | |
| "details" -> "Aggressive, sneaky attack" | |
| ) | |
| val goblinErr2 = Json.obj( | |
| "cid" -> 13, | |
| "dwelling" -> Json.obj("name" -> "Goblin's House"), | |
| "name" -> "Goblin hunter", | |
| "carnivorous" -> true, | |
| "details" -> "Aggressive, sneaky attack" | |
| ) | |
| val goblinErr3 = Json.obj( | |
| "cid" -> 13, | |
| "dwelling" -> Json.arr("Goblin's Hut"), | |
| "name" -> "Goblin hunter", | |
| "carnivorous" -> true, | |
| "details" -> "Aggressive, sneaky attack" | |
| ) | |
| val goblinErr4 = Json.obj( | |
| "cid" -> 13, | |
| "dwelling" -> "Goblin's Hut", | |
| "name" -> "Goblin hunter", | |
| "carnivorous" -> true, | |
| "details" -> "Aggressive, sneaky attack" | |
| ) | |
| val goblinErr5 = Json.obj( | |
| "cid" -> 13, | |
| "dwelling" -> Json.arr(JsString("abc"), JsString("def")), | |
| "name" -> "Goblin hunter", | |
| "carnivorous" -> true, | |
| "details" -> "Aggressive, sneaky attack" | |
| ) | |
| println(Json.fromJson[Creature](goblinErr1)) | |
| println(Json.fromJson[Creature](goblinErr2)) | |
| println(Json.fromJson[Creature](goblinErr3)) | |
| println(Json.fromJson[Creature](goblinErr4)) | |
| println(Json.fromJson[Creature](goblinErr5)) | |
| val stack1 = Json.obj( | |
| "sid" -> 30, | |
| "creature" -> 13, | |
| "count" -> 123 | |
| ) | |
| println(Json.fromJson[Stack](stack1)) | |
| val hut2 = Json.obj( | |
| "name" -> "Goblin's Hut" | |
| ) | |
| println(Json.fromJson[Dwelling](hut2)) | |
| } | |
| run() | |
| } | |
| case class Dwelling(did: Int, name: String) | |
| case class Creature(cid: Int, | |
| dwelling: Either[Int, Dwelling], | |
| name: String, | |
| carnivorous: Boolean, | |
| details: Option[String]) | |
| case class Stack(sid: Int, | |
| creature: Either[Int, Creature], | |
| count: Int) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment