6

Saving JSON to Scala model - Part 2

 2 years ago
source link: https://pedrorijo.com/blog/scala-json-part2/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Following up my previous post on mapping json objects to Scala models, it is time to present some more advanced use cases.

In the past weeks I have found myself in cases where a little more ‘magic’ was needed:

  1. Read dates from the json (org.joda.DateTime)
  2. Mapping primitive types (as Long) to a custom case class
  3. Nested objects

Reading dates from Json objects

Imagine you have a Json object, with a Unix timestamp field:

{
  "field": "example field",
  "date": 1459014762000
}

How to map it?

  • Define the corresponding case class
  • Define a custom mapper. To read a DateTime one can use the default adapter provided by the library:
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 _)
}

Just test it:

scala> val jsonV1 = """{ "field": "example field", "date": 1459014762000 }"""
jsonV1: String = { "field": "example field", "date": 1459014762000 }

scala> Json.parse(jsonV1).as[JsonExampleV1]
res0: JsonExampleV1 = JsonExampleV1(example field,2016-03-26T17:52:42.000Z)

Reading custom case classes

Now, if you do wrap your object identifiers for type safety, you will enjoy this:

{
  "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 _)
}

Again, let’s test it:

scala> val jsonV2 = """ { "id": 91, "data": "String data" }"""
jsonV2: String = " { "id": 91, "data": "String data" }"

scala> Json.parse(jsonV2).as[JsonExampleV2]
res1: JsonExampleV2 = JsonExampleV2(MyIdentifier(91),String data)

Reading nested objects

This one was motivated from a Stackoverflow question.

Basically, the json answer contains an id field, and a json array with friends, where each friend object is composed of another id and a since field.

I will present two options:

  • using a case class to save all the information of each friend
  • extract only the id (as the author pretends)

Using case classes

Not very difficult, but remember that the Friends json mapper definition needs to come before the Response json mapper:

case class Friends(id: Long, since: String)
object Friends {
  implicit val fmt = Json.format[Friends]
}

case class Response(id: Long, friend_ids: Seq[Friends])

object Response {

  implicit val userReads: Reads[Response] = (
    (JsPath \ "id").read[Long] and
      (JsPath \ "friends").read[Seq[Friends]]
    ) (Response.apply _)
}

Extract only the ids

This solution was presented by another user and it’s a little bit more elaborated:

case class Response(id: Long, friend_ids: Seq[Long])

object Response {

  implicit val userReads: Reads[Response] = (
    (__ \ "id").read[Long] and
      (__ \ "friends").read[Seq[Long]](Reads.seq((__ \ "id").read[Long]))
    )(Response.apply _)
}

I want to learn more

If you are stuck with some error, check the source code at GitHub.

If you want to read a little more about solutions from other authors, check these useful links:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK