Skip to content

Instantly share code, notes, and snippets.

@nlinker
Last active December 27, 2015 07:49
Show Gist options
  • Save nlinker/7291436 to your computer and use it in GitHub Desktop.
Save nlinker/7291436 to your computer and use it in GitHub Desktop.
Play 2.1 JSON combinators with Either type (generalized format for Either)
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