play-json uses implicit formats as other json frameworks
SBT dependency: libraryDependencies += ""com.typesafe.play" %% "play-json" % "2.4.8"
import play.api.libs.json._
import play.api.libs.functional.syntax._ // if you need DSL
DefaultFormat
contains defaul formats to read/write all basic types. To provide JSON functionality for your own types, you can either use convenience builders for formats or write formats explicitly.
Read json
// generates an intermediate JSON representation (abstract syntax tree)
val res = Json.parse("""{ "foo": "bar" }""") // JsValue = {"foo":"bar"}
res.as[Map[String, String]] // Map(foo -> bar)
res.validate[Map[String, String]] //JsSuccess(Map(foo -> bar),)
Write json
val values = List("a", "b", "c")
Json.stringify(Json.toJson(values)) // ["a", "b", "c"]
DSL
val json = parse("""{ "foo": [{"foo": "bar"}]}""")
(json \ "foo").get //Simple path: [{"foo":"bar"}]
(json \\ "foo") //Recursive path:List([{"foo":"bar"}], "bar")
(json \ "foo")(0).get //Index lookup (for JsArrays): {"foo":"bar"}
As always prefer pattern matching against JsSuccess
/JsError
and try to avoid .get
, array(i)
calls.
Read and write to case class
case class Address(street: String, city: String)
case class Person(name: String, address: Address)
// create the formats and provide them implicitly
implicit val addressFormat = Json.format[Address]
implicit val personFormat = Json.format[Person]
// serialize a Person
val fred = Person("Fred", Address("Awesome Street 9", "SuperCity"))
val fredJsonString = Json.stringify(Json.toJson(Json.toJson(fred)))
val personRead = Json.parse(fredJsonString).as[Person] //Person(Fred,Address(Awesome Street 9,SuperCity))
Own Format
You can write your own JsonFormat if you require a special serialization of your type (e.g. name the fields differently in scala and Json or instantiate different concrete types based on the input)
case class Address(street: String, city: String)
// create the formats and provide them implicitly
implicit object AddressFormatCustom extends Format[Address] {
def reads(json: JsValue): JsResult[Address] = for {
street <- (json \ "Street").validate[String]
city <- (json \ "City").validate[String]
} yield Address(street, city)
def writes(x: Address): JsValue = Json.obj(
"Street" -> x.street,
"City" -> x.city
)
}
// serialize an address
val address = Address("Awesome Street 9", "SuperCity")
val addressJsonString = Json.stringify(Json.toJson(Json.toJson(address)))
//{"Street":"Awesome Street 9","City":"SuperCity"}
val addressRead = Json.parse(addressJsonString).as[Address]
//Address(Awesome Street 9,SuperCity)
Alternative
If the json doesn't exactly match your case class fields (isAlive
in case class vs is_alive
in json):
case class User(username: String, friends: Int, enemies: Int, isAlive: Boolean)
object User {
import play.api.libs.functional.syntax._
import play.api.libs.json._
implicit val userReads: Reads[User] = (
(JsPath \ "username").read[String] and
(JsPath \ "friends").read[Int] and
(JsPath \ "enemies").read[Int] and
(JsPath \ "is_alive").read[Boolean]
) (User.apply _)
}
Json with optional fields
case class User(username: String, friends: Int, enemies: Int, isAlive: Option[Boolean])
object User {
import play.api.libs.functional.syntax._
import play.api.libs.json._
implicit val userReads: Reads[User] = (
(JsPath \ "username").read[String] and
(JsPath \ "friends").read[Int] and
(JsPath \ "enemies").read[Int] and
(JsPath \ "is_alive").readNullable[Boolean]
) (User.apply _)
}
Reading timestamps from json
Imagine you have a Json object, with a Unix timestamp field:
{
"field": "example field",
"date": 1459014762000
}
solution:
case class JsonExampleV1(field: String, date: DateTime)
object JsonExampleV1{
implicit val r: Reads[JsonExampleV1] = (
(__ \ "field").read[String] and
(__ \ "date").read[DateTime](Reads.DefaultJodaDateReads)
)(JsonExampleV1.apply _)
}
Reading custom case classes
Now, if you do wrap your object identifiers for type safety, you will enjoy this. See the following json object:
{
"id": 91,
"data": "Some data"
}
and the corresponding case classes:
case class MyIdentifier(id: Long)
case class JsonExampleV2(id: MyIdentifier, data: String)
Now you just need to read the primitive type (Long), and map to your idenfier:
object JsonExampleV2 {
implicit val r: Reads[JsonExampleV2] = (
(__ \ "id").read[Long].map(MyIdentifier) and
(__ \ "data").read[String]
)(JsonExampleV2.apply _)
}
code at https://github.com/pedrorijo91/scala-play-json-examples