Skip to content

Commit 51e247a

Browse files
committed
Merge pull request #15 from zalando/issue/9/add-play-json-support
Play-JSON support
2 parents 8cf7b13 + 978383c commit 51e247a

11 files changed

+985
-472
lines changed

build.sbt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ scalacOptions ++= Seq("-feature", "-unchecked", "-deprecation")
1414

1515
crossScalaVersions := Seq("2.11.7", "2.10.6")
1616

17-
resolvers += "spray" at "http://repo.spray.io/"
17+
resolvers ++= Seq(
18+
"spray" at "http://repo.spray.io/",
19+
"Typesafe Repo" at "http://repo.typesafe.com/typesafe/releases/"
20+
)
1821

1922
libraryDependencies ++= Seq(
20-
"io.spray" %% "spray-json" % "1.3.1",
23+
"io.spray" %% "spray-json" % "1.3.1" % "provided",
24+
"com.typesafe.play" %% "play-json" % "2.3.8" % "provided",
2125
"org.scalatest" %% "scalatest" % "2.2.4" % "test"
2226
)
2327

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
package org.zalando.jsonapi.json.playjson
2+
3+
import org.zalando.jsonapi.json.FieldNames
4+
import org.zalando.jsonapi.model.JsonApiObject.Value
5+
import org.zalando.jsonapi.model._
6+
import org.zalando.jsonapi.model.RootObject._
7+
import play.api.libs.json._
8+
import play.api.libs.functional.syntax._
9+
10+
import collection.immutable.{ Seq ImmutableSeq }
11+
12+
trait PlayJsonJsonapiFormat {
13+
14+
/**
15+
* Play-JSON format for serializing and deserializing Jsonapi [[RootObject]].
16+
*/
17+
implicit lazy val rootObjectFormat: Format[RootObject] = (
18+
(JsPath \ FieldNames.`data`).formatNullable[Data] and
19+
(JsPath \ FieldNames.`links`).formatNullable[Links] and
20+
(JsPath \ FieldNames.`errors`).formatNullable[Errors] and
21+
(JsPath \ FieldNames.`meta`).formatNullable[Meta] and
22+
(JsPath \ FieldNames.`included`).formatNullable[Included] and
23+
(JsPath \ FieldNames.`jsonapi`).formatNullable[JsonApi]
24+
)(RootObject.apply, unlift(RootObject.unapply))
25+
26+
/**
27+
* Play-JSON format for serializing and deserializing Jsonapi [[Data]].
28+
*/
29+
implicit lazy val dataFormat: Format[Data] = new Format[Data] {
30+
override def writes(data: Data): JsValue = data match {
31+
case ro: ResourceObject resourceObjectFormat writes ro
32+
case ResourceObjects(ros)
33+
val values: ImmutableSeq[JsValue] = ros map (resourceObjectFormat writes _)
34+
JsArray(values)
35+
}
36+
37+
override def reads(json: JsValue): JsResult[Data] = {
38+
json.asOpt[ResourceObject] match {
39+
case Some(ro) JsSuccess(ro)
40+
case None JsSuccess(ResourceObjects(json.as[ImmutableSeq[ResourceObject]]))
41+
}
42+
}
43+
}
44+
45+
/**
46+
* Play-JSON format for serializing and deserializing Jsonapi [[ResourceObject]].
47+
*/
48+
implicit lazy val resourceObjectFormat: Format[ResourceObject] = (
49+
(JsPath \ FieldNames.`type`).format[String] and
50+
(JsPath \ FieldNames.`id`).format[String] and
51+
(JsPath \ FieldNames.`attributes`).formatNullable[Attributes] and
52+
(JsPath \ FieldNames.`relationships`).formatNullable[Relationships] and
53+
(JsPath \ FieldNames.`links`).formatNullable[Links] and
54+
(JsPath \ FieldNames.`meta`).formatNullable[Meta]
55+
)(ResourceObject.apply, unlift(ResourceObject.unapply))
56+
57+
/**
58+
* Play-JSON format for serializing and deserializing Jsonapi [[Attributes]].
59+
*/
60+
implicit lazy val attributesFormat: Format[Attributes] = new Format[Attributes] {
61+
override def writes(attributes: Attributes): JsValue = {
62+
val fields = attributes.map(a a.name -> Json.toJson(a.value))
63+
JsObject(fields)
64+
}
65+
66+
override def reads(json: JsValue): JsResult[Attributes] = json match {
67+
case JsObject(fields)
68+
fields.foldLeft[JsResult[Attributes]](JsSuccess(Vector.empty)) {
69+
case (acc, (name, jsValue)) (acc, jsValue.validate[JsonApiObject.Value]) match {
70+
case (JsSuccess(attrs, _), JsSuccess(value, _))
71+
JsSuccess(attrs :+ Attribute(name, value))
72+
case (JsSuccess(_, _), JsError(errors))
73+
JsError(Seq(JsPath \ name -> errors.flatMap(_._2)))
74+
case (e: JsError, s: JsSuccess[_])
75+
e
76+
case (e: JsError, JsError(errors))
77+
e ++ JsError(Seq(JsPath \ name -> errors.flatMap(_._2)))
78+
}
79+
}
80+
case _ JsError("error.expected.jsobject")
81+
}
82+
}
83+
84+
/**
85+
* Play-JSON format for serializing and deserializing Jsonapi [[Meta]].
86+
*/
87+
implicit lazy val metaFormat: Format[Meta] = new Format[Meta] {
88+
override def writes(meta: Meta): JsValue = {
89+
val fields = meta.map(mp (mp.name, Json.toJson(mp.value)))
90+
JsObject(fields)
91+
}
92+
93+
override def reads(json: JsValue): JsResult[Meta] = json match {
94+
case JsObject(fields)
95+
fields.foldLeft[JsResult[Meta]](JsSuccess(Vector.empty)) {
96+
case (acc, (name, jsValue)) (acc, jsValue.validate[JsonApiObject.Value]) match {
97+
case (JsSuccess(metaProps, _), JsSuccess(value, _))
98+
JsSuccess(metaProps :+ MetaProperty(name, value))
99+
case (JsSuccess(_, _), JsError(errors))
100+
JsError(Seq(JsPath \ name -> errors.flatMap(_._2)))
101+
case (e: JsError, s: JsSuccess[_])
102+
e
103+
case (e: JsError, JsError(errors))
104+
e ++ JsError(Seq(JsPath \ name -> errors.flatMap(_._2)))
105+
}
106+
}
107+
case _ JsError("error.expected.jsobject")
108+
}
109+
}
110+
111+
/**
112+
* Play-JSON format for serializing and deserializing Jsonapi [[JsonApi]].
113+
*/
114+
implicit lazy val jsonApiFormat: Format[JsonApi] = new Format[JsonApi] {
115+
override def writes(jsonApi: JsonApi): JsValue = {
116+
val fields = jsonApi.map(jap (jap.name, Json.toJson(jap.value)))
117+
JsObject(fields)
118+
}
119+
120+
override def reads(json: JsValue): JsResult[JsonApi] = json match {
121+
case JsObject(fields)
122+
fields.foldLeft[JsResult[JsonApi]](JsSuccess(Vector.empty)) {
123+
case (acc, (name, jsValue)) (acc, jsValue.validate[JsonApiObject.Value]) match {
124+
case (JsSuccess(jsonApiProps, _), JsSuccess(value, _))
125+
JsSuccess(jsonApiProps :+ JsonApiProperty(name, value))
126+
case (JsSuccess(_, _), JsError(errors))
127+
JsError(Seq(JsPath \ name -> errors.flatMap(_._2)))
128+
case (e: JsError, s: JsSuccess[_])
129+
e
130+
case (e: JsError, JsError(errors))
131+
e ++ JsError(Seq(JsPath \ name -> errors.flatMap(_._2)))
132+
}
133+
}
134+
case _ JsError("error.expected.jsobject")
135+
}
136+
}
137+
138+
/**
139+
* Play-JSON format for serializing and deserializing Jsonapi [[Errors]].
140+
*/
141+
implicit lazy val errorsFormat: Format[Errors] = new Format[Errors] {
142+
override def writes(errors: Errors): JsValue = {
143+
val fields = errors.map(Json.toJson(_))
144+
JsArray(fields)
145+
}
146+
147+
override def reads(json: JsValue): JsResult[Errors] = json match {
148+
case JsArray(a) a.foldLeft[JsResult[Errors]](JsSuccess(Vector.empty)) {
149+
case (acc, jsValue) (acc, jsValue.validate[Error]) match {
150+
case (JsSuccess(errs, _), JsSuccess(value, _))
151+
JsSuccess(errs :+ value)
152+
case (JsSuccess(_, _), JsError(errors))
153+
JsError(errors)
154+
case (e: JsError, s: JsSuccess[_])
155+
e
156+
case (e: JsError, JsError(errors))
157+
e ++ JsError(errors)
158+
}
159+
}
160+
case _ JsError("error.expected.jsarray")
161+
}
162+
}
163+
164+
/**
165+
* Play-JSON format for serializing and deserializing Jsonapi [[Error]].
166+
*/
167+
implicit lazy val errorFormat: Format[Error] = (
168+
(JsPath \ FieldNames.`id`).formatNullable[String] and
169+
(JsPath \ FieldNames.`links`).formatNullable[Links] and
170+
(JsPath \ FieldNames.`status`).formatNullable[String] and
171+
(JsPath \ FieldNames.`code`).formatNullable[String] and
172+
(JsPath \ FieldNames.`title`).formatNullable[String] and
173+
(JsPath \ FieldNames.`detail`).formatNullable[String] and
174+
(JsPath \ FieldNames.`source`).formatNullable[ErrorSource] and
175+
(JsPath \ FieldNames.`meta`).formatNullable[Meta]
176+
)(Error.apply, unlift(Error.unapply))
177+
178+
/**
179+
* Play-JSON format for serializing and deserializing Jsonapi [[ErrorSource]].
180+
*/
181+
implicit lazy val errorSourceFormat: Format[ErrorSource] = (
182+
(JsPath \ FieldNames.`pointer`).formatNullable[String] and
183+
(JsPath \ FieldNames.`parameter`).formatNullable[String]
184+
)(ErrorSource.apply, unlift(ErrorSource.unapply))
185+
186+
/**
187+
* Play-JSON format for serializing and deserializing Jsonapi [[Relationship]].
188+
*/
189+
implicit lazy val relationshipFormat: Format[Relationship] = (
190+
(JsPath \ FieldNames.`links`).formatNullable[Links] and
191+
(JsPath \ FieldNames.`data`).formatNullable[Data]
192+
)(Relationship.apply, unlift(Relationship.unapply))
193+
194+
/**
195+
* Play-JSON format for serializing and deserializing Jsonapi [[JsonApiObject]].
196+
*/
197+
implicit lazy val jsonApiObjectValueFormat: Format[JsonApiObject.Value] = new Format[JsonApiObject.Value] {
198+
override def writes(value: JsonApiObject.Value): JsValue = value match {
199+
case JsonApiObject.StringValue(s) JsString(s)
200+
case JsonApiObject.NumberValue(n) JsNumber(n)
201+
case JsonApiObject.BooleanValue(b) JsBoolean(b)
202+
case JsonApiObject.JsObjectValue(o) JsObject(o.map (a a.name -> Json.toJson(a.value)))
203+
case JsonApiObject.JsArrayValue(a) JsArray(a.map(v Json.toJson[JsonApiObject.Value](v)))
204+
case JsonApiObject.NullValue JsNull
205+
}
206+
207+
override def reads(json: JsValue): JsResult[Value] = json match {
208+
case JsString(s) JsSuccess(JsonApiObject.StringValue(s))
209+
case JsNumber(n) JsSuccess(JsonApiObject.NumberValue(n))
210+
case JsBoolean(b) JsSuccess(JsonApiObject.BooleanValue(b))
211+
case JsObject(o) {
212+
val attrs = o.map(keyValue {
213+
val (key, jsValue) = keyValue
214+
val read = reads(jsValue).getOrElse(JsonApiObject.NullValue)
215+
Attribute(key, read)
216+
}).toList
217+
JsSuccess(JsonApiObject.JsObjectValue(attrs))
218+
}
219+
case JsArray(a) {
220+
val arrayValues = a.map(jsValue {
221+
reads(jsValue).getOrElse(JsonApiObject.NullValue)
222+
}).toList
223+
JsSuccess(JsonApiObject.JsArrayValue(arrayValues))
224+
}
225+
case JsNull JsSuccess(JsonApiObject.NullValue)
226+
case _ JsError("error.expected.jsonapivalue")
227+
}
228+
}
229+
230+
/**
231+
* Play-JSON format for serializing and deserializing Jsonapi [[Links]].
232+
*/
233+
implicit lazy val linksFormat: Format[Links] = new Format[Links] {
234+
override def writes(links: Links): JsValue = {
235+
val fields = links.map {
236+
_ match {
237+
case Links.About(u) (FieldNames.`about`, JsString(u))
238+
case Links.First(u) (FieldNames.`first`, JsString(u))
239+
case Links.Last(u) (FieldNames.`last`, JsString(u))
240+
case Links.Next(u) (FieldNames.`next`, JsString(u))
241+
case Links.Prev(u) (FieldNames.`prev`, JsString(u))
242+
case Links.Related(u) (FieldNames.`related`, JsString(u))
243+
case Links.Self(u) (FieldNames.`self`, JsString(u))
244+
}
245+
}
246+
JsObject(fields)
247+
}
248+
249+
override def reads(json: JsValue): JsResult[Links] = json match {
250+
case JsObject(o) JsSuccess(o.map { keyValue
251+
keyValue match {
252+
case (FieldNames.`about`, JsString(u)) Links.About(u)
253+
case (FieldNames.`first`, JsString(u)) Links.First(u)
254+
case (FieldNames.`last`, JsString(u)) Links.Last(u)
255+
case (FieldNames.`next`, JsString(u)) Links.Next(u)
256+
case (FieldNames.`prev`, JsString(u)) Links.Prev(u)
257+
case (FieldNames.`related`, JsString(u)) Links.Related(u)
258+
case (FieldNames.`self`, JsString(u)) Links.Self(u)
259+
}
260+
}.toVector)
261+
case _ JsError("error.expected.links")
262+
}
263+
}
264+
265+
/**
266+
* Play-JSON format for serializing and deserializing Jsonapi [[Included]].
267+
*/
268+
implicit lazy val includedFormat: Format[Included] = new Format[Included] {
269+
override def writes(included: Included): JsValue = {
270+
val objects = included.resourceObjects.array.map(resourceObjectFormat writes _)
271+
JsArray(objects)
272+
}
273+
274+
override def reads(json: JsValue): JsResult[Included] =
275+
JsSuccess(Included(ResourceObjects(json.as[ImmutableSeq[ResourceObject]])))
276+
}
277+
}

0 commit comments

Comments
 (0)