Skip to content

Commit

Permalink
Merge pull request #1 from dwestheide/add-play-json-serialization
Browse files Browse the repository at this point in the history
Add play-json serialization
  • Loading branch information
zmeda committed Sep 30, 2014
2 parents 7ab898f + 2801d69 commit 62f7087
Show file tree
Hide file tree
Showing 8 changed files with 507 additions and 260 deletions.
10 changes: 6 additions & 4 deletions build.sbt
Expand Up @@ -18,11 +18,13 @@ scalacOptions ++= Seq("-feature", "-unchecked", "-deprecation")

resolvers += "spray" at "http://repo.spray.io/"

resolvers += "Typesafe Repo" at "http://repo.typesafe.com/typesafe/releases/"

libraryDependencies ++= Seq(
"com.chuusai" %% "shapeless" % "1.2.4",
"org.scalaz" %% "scalaz-core" % "7.1.0",
"io.spray" %% "spray-json" % "1.2.6",
"org.scalatest" %% "scalatest" % "2.2.1" % "test"
"org.scalaz" %% "scalaz-core" % "7.1.0",
"io.spray" %% "spray-json" % "1.2.6" % "provided",
"com.typesafe.play" %% "play-json" % "2.3.4" % "provided",
"org.scalatest" %% "scalatest" % "2.2.1" % "test"
)

scalariformSettings ++ Seq(
Expand Down
20 changes: 20 additions & 0 deletions src/main/scala/com/yetu/siren/json/FieldNames.scala
@@ -0,0 +1,20 @@
package com.yetu.siren.json

/**
* Constants for all the JSON field names used in Siren.
*/
private[json] object FieldNames {
val `class` = "class"
val `properties` = "properties"
val `entities` = "entities"
val `actions` = "actions"
val `links` = "links"
val `title` = "title"
val `rel` = "rel"
val `href` = "href"
val `name` = "name"
val `method` = "method"
val `type` = "type"
val `fields` = "fields"
val `value` = "value"
}
13 changes: 13 additions & 0 deletions src/main/scala/com/yetu/siren/json/package.scala
@@ -0,0 +1,13 @@
package com.yetu.siren

package object json {

/**
* Collects only those of the given options that are defined, removing the others.
* @param opts one or more options of type A
* @tparam A the type of the given options
*/
private[json] def collectSome[A](opts: Option[A]*): List[A] =
(opts collect { case Some(field) field }).toList

}
160 changes: 160 additions & 0 deletions src/main/scala/com/yetu/siren/json/playjson/PlayJsonSirenFormat.scala
@@ -0,0 +1,160 @@
package com.yetu.siren.json
package playjson

import com.yetu.siren.model
import com.yetu.siren.model.Entity.{ RootEntity, EmbeddedRepresentation, EmbeddedLink }

import scalaz.NonEmptyList

trait PlayJsonSirenFormat {

import model._
import scalaz.std.option._
import play.api.libs.json._

/**
* Play-JSON writer for a Siren root entity.
*/
implicit val rootEntityWriter: Writes[Entity.RootEntity] = new Writes[Entity.RootEntity] {
override def writes(entity: RootEntity): JsValue = jsObject(
optField(FieldNames.`class`, entity.classes),
optField(FieldNames.`properties`, entity.properties),
optField(FieldNames.`entities`, entity.entities),
optField(FieldNames.`actions`, entity.actions),
optField(FieldNames.`links`, entity.links),
optField(FieldNames.`title`, entity.title)
)
}

/**
* Play-JSON writer for a Siren embedded link.
*/
implicit val embeddedLinkWriter: Writes[Entity.EmbeddedLink] = new Writes[Entity.EmbeddedLink] {
override def writes(entity: EmbeddedLink): JsValue = jsObject(
optField(FieldNames.`class`, entity.classes),
field(FieldNames.`rel`, entity.rel),
field(FieldNames.`href`, entity.href)
)
}

/**
* Play-JSON writer for a Siren embedded representation.
*/
implicit val embeddedRepresentationWriter: Writes[Entity.EmbeddedRepresentation] =
new Writes[Entity.EmbeddedRepresentation] {
override def writes(entity: EmbeddedRepresentation): JsValue = jsObject(
optField(FieldNames.`class`, entity.classes),
optField(FieldNames.`properties`, entity.properties),
optField(FieldNames.`entities`, entity.entities),
optField(FieldNames.`actions`, entity.actions),
optField(FieldNames.`links`, entity.links),
optField(FieldNames.`title`, entity.title),
field(FieldNames.`rel`, entity.rel)
)
}

/**
* Play-JSON writer for a Siren embedded entity.
*/
implicit val embeddedEntityWriter: Writes[EmbeddedEntity] = new Writes[EmbeddedEntity] {
override def writes(entity: EmbeddedEntity): JsValue = entity match {
case e: Entity.EmbeddedLink embeddedLinkWriter writes e
case e: Entity.EmbeddedRepresentation embeddedRepresentationWriter writes e
}
}

/**
* Play-JSON writer for a Siren property value.
*/
implicit val propertyValueWriter: Writes[Property.Value] = new Writes[Property.Value] {
override def writes(value: Property.Value): JsValue = value match {
case Property.StringValue(s) JsString(s)
case Property.NumberValue(n) JsNumber(n)
case Property.BooleanValue(b) JsBoolean(b)
case Property.NullValue JsNull
}
}

/**
* Play-JSON writer for Siren properties.
*/
implicit val propertiesWriter: Writes[Properties] = new Writes[Properties] {
override def writes(properties: Properties): JsValue = {
val fields = properties.list.map (p p.name -> Json.toJson(p.value))
JsObject(fields)
}
}

/**
* Play-JSON writer for [[NonEmptyList]]s.
*/
implicit def nonEmptyListWriter[A: Writes]: Writes[NonEmptyList[A]] = Writes {
(xs: NonEmptyList[A]) Json.toJson(xs.list)
}

/**
* Play JSON writer for Siren action methods.
*/
implicit val methodWriter: Writes[Action.Method] = Writes {
(method: Action.Method) JsString(method.name)
}

/**
* Play-JSON writer for Siren action encodings.
*/
implicit val encodingWriter: Writes[Action.Encoding] = Writes {
(encoding: Action.Encoding) JsString(encoding.name)
}

/**
* Play-JSON writer for Siren action field types.
*/
implicit val fieldTypeWriter: Writes[Action.Field.Type] = Writes {
(fieldType: Action.Field.Type) JsString(fieldType.name)
}

/**
* Play-JSON writer for an action field.
*/
implicit val fieldWriter: Writes[Action.Field] = new Writes[Action.Field] {
override def writes(f: Action.Field): JsValue = jsObject(
field(FieldNames.`name`, f.name),
field(FieldNames.`type`, f.`type`),
optField(FieldNames.`value`, f.value),
optField(FieldNames.`title`, f.title)
)
}

/**
* Play-JSON writer for a Siren action.
*/
implicit val actionWriter: Writes[Action] = new Writes[Action] {
override def writes(action: Action): JsValue = jsObject(
field(FieldNames.`name`, action.name),
optField(FieldNames.`class`, action.classes),
optField(FieldNames.`title`, action.title),
field(FieldNames.`href`, action.href),
optField(FieldNames.`method`, action.method),
optField(FieldNames.`type`, action.`type`),
optField(FieldNames.`fields`, action.fields)
)
}

/**
* Play-JSON writer for a Siren link.
*/
implicit val linkWriter: Writes[Link] = new Writes[Link] {
override def writes(link: Link): JsValue = jsObject(
field(FieldNames.`rel`, link.rel),
field(FieldNames.`href`, link.href),
optField(FieldNames.`title`, link.title)
)
}

private def jsonField[A: Writes](name: String)(value: A) = name -> Json.toJson(value)
private def optField[A: Writes](name: String, value: Option[A]): Option[(String, JsValue)] =
value map jsonField(name)
private def field[A: Writes](name: String, value: A) = some(name -> Json.toJson(value))
private def jsObject(fields: Option[(String, JsValue)]*) = JsObject(collectSome(fields: _*))

}
28 changes: 0 additions & 28 deletions src/main/scala/com/yetu/siren/json/sprayjson/SirenJsonFormat.scala
Expand Up @@ -30,7 +30,6 @@ import spray.json._
import com.yetu.siren.model.Action.{ Fields, Encoding }
import com.yetu.siren.model.Action.Field.Type
import scala.util.Try
import com.yetu.siren.model

/**
* JSON serialization and deserialization of Siren entities.
Expand All @@ -44,25 +43,6 @@ trait SirenJsonFormat { self: DefaultJsonProtocol ⇒
import scalaz.std.option._
import scalaz.NonEmptyList

/**
* Constants for all the JSON field names used in Siren.
*/
private object FieldNames {
val `class` = "class"
val `properties` = "properties"
val `entities` = "entities"
val `actions` = "actions"
val `links` = "links"
val `title` = "title"
val `rel` = "rel"
val `href` = "href"
val `name` = "name"
val `method` = "method"
val `type` = "type"
val `fields` = "fields"
val `value` = "value"
}

/**
* Spray-JSON format for serializing and deserializing Siren entities.
*/
Expand Down Expand Up @@ -339,12 +319,4 @@ trait SirenJsonFormat { self: DefaultJsonProtocol ⇒
}
}

/**
* Collects only those of the given options that are defined, removing the others.
* @param opts one or more options of type A
* @tparam A the type of the given options
*/
private def collectSome[A](opts: Option[A]*): List[A] =
(opts collect { case Some(field) field }).toList

}

0 comments on commit 62f7087

Please sign in to comment.