Working with JSON in Scala using the Json4s library (part two)

In this second part (this is the link to the first part) we are going to delve deeper in the function offered by the library Json4s. Let me start by saying that I’m not a developer or a contributor of Json4s, I’m just an user that wanted to report this library on an article, in order to share my experience with it. As a consequence I may use the library not in the right way. If so, I apologize and I would kindly ask you to comment under this post, in the specific area.

In the previous article we saw simple operations, in this one we will see just few of the more advanced features available:

  1. how the selection works with nested objects
  2. how to properly retrieve and print a value (and a string in particular String)
  3. how to filter fields and return a “pruned” JSON
  4. merging two JSONs
  5. the diff function

Like in the previous example, let’s create a Json4sTest.scala class with a JSON variable:

package com.nosqlnocry.test
import org.json4s._
import org.json4s.JsonDSL._
import org.json4s.jackson.JsonMethods._
object Json4sTest {  
  def main(args: Array[String]) {
    val JSONString = """
      {
         "name":"luca",
         "id": "1q2w3e4r5t",
         "age": 26,
         "url":"http://www.nosqlnocry.wordpress.com",
         "url":"https://nosqlnocry.wordpress.com",
         "loginTimeStamps": [1434904257,1400689856,1396629056],
         "messages": [
          {"id":1,"content":"Please like this post!"},
          {"id":2,"content":"Forza Roma!"}
         ],               
         "profile": { "id":"my-nickname", "score":123, "avatar":"path.jpg" }
      }
      """
      val JSON = parse(JSONString)
      // ...
  }
}

The JSON contains few elements: simple key-values, an array with numeric values (time-stamps in long), an array of object and another object.

(1) Now let’s investigate how the commands (or better, the functions) “\” and “\\” behave in this case:

println(JSON\\"id")
// prints: JObject(List((id,JString(1q2w3e4r5t)), (id,JInt(1)), (id,JInt(2)), ...
println(JSON\"id")
// prints: JString(1q2w3e4r5t)

In the first case we will have as a result ALL those pairs with key “id” in a JList, in the latter, we have only the not nested key-value pair of the JSON. However pay attention, because in case you are asking for an element occurring more than one time in the first level of the JSON, you will have a JArray as a result:

println(JSON\"url")
// prints: JArray(List(JString(http://www...), JString(https://nosqlnocry...

Therefor if you are interested in having only the ids of the array message you can either select the array and extract the ids:

val messagesIds = (JSON \ "messages") \ "id"
println(messageIds.values)
// prints: List(1,2)

or using the yield function, leveraging the fact that those are the only JInts called “id”:

val messageIds = for { JInt(x) 
println(messageIds)
// prints: List(1,2)

(2) When you want to retrieve a value, you may use the “compact(render(_))” function like showed in the previous article. That function (or the function “pretty” instead of “compact”) is used to print every JObject without the notations due to the Json4s objects, for example, say you want to print all the “messages”:

println(compact(render(JSON \ "messages")))
// prints: [{"id":1,"content":"Please like this post!"},{"id":2,"content":"Forza Roma!"}]
println(pretty(render((JSON \ "messages")\"content")))
// prints: [ "Please like this post!", "Forza Roma!" ] // note it is not compacted anymore

But if you want to extract and print a single value, if it is not a string, there aren’t any issues

println(pretty(render(JSON \ "age")))
// prints: 26

on the other hand, if it is a String, you have some trouble with apostrophes:

println(compact(render(JSON \ "name")))
// prints: "luca" // note the apostrophes

then, what you can do is to use the yield method

var name = for { JString(x) <- (JSON \\ "name") } yield x
println(name(0))
// prints: luca

or the “values” property

var name = (JSON \ "name")
println(name.values)
// prints: luca

or to use the extract function, that in general can be used to load an instance of a Scala class from the JSON:

implicit val formats = DefaultFormats
val name = (JSON \ "name").extract[String]
println(name)
// prints: luca

Attention once again, because most of these function are changing behavior if there is more than one occurrence of “name”.

(3) To find fields in a JSON and retrieve just those of interest (keeping the JSON format), you can use the function findField of filterField. The former is going to return the first matching key-value pair found (in a Scala Option[JValue]), the latter will return all matching results (as a List):

val URL = JSON findField {
case JField("url", _) => true
case _ => false
}
println(URL)
// prints: Some((url,JString(http://www.nosqlnocry.wordpress.com)))
val URLs = JSON filterField {
case JField("url", _) => true
case _ => false
}
println(URLs)
// prints: List((url,JString(http://www.nosqlnocry...)), (url,JString(https://nosqlnocry...)

(4) Let’s see how the merge function behaves. Say we have the previous JSON in a valiable, and a new JSON parsed in another one (called “secondJSON”), that contains:

val secondJSON = parse("""{
  "messages" : [ {
    "id" : 3,
    "content" : "how to merge?"
  } ],
  "url" : "anotherURL",
  "loginTimeStamps" : 1400689856,
  "profile" : {
    "avatar" : "new.jpg"
  },
  "new": "new value"
}""")

Note as the “loginTimeStamps” is no longer an array, meanwhile the “messages” continues to be an array of objects. To perform the merge operation is sufficient to

val mergedJSON = JSON merge secondJSON
println(pretty(render(mergedJSON)))
/* prints: 
{
  "name" : "luca",
  "id" : "1q2w3e4r5t",
  "age" : 26,
  "url" : "anotherURL",
  "url" : "https://nosqlnocry.wordpress.com",
  "loginTimeStamps" : 1400689856,
  "messages" : [ {
    "id" : 1,
    "content" : "Please like this post!"
  }, {
    "id" : 2,
    "content" : "Forza Roma!"
  }, {
    "id" : 3,
    "content" : "how to merge?"
  } ],
  "profile" : {
    "id" : "my-nickname",
    "score" : 123,
    "avatar" : "new.jpg"
  },
  "new": "new value"
}
*/

As you can easily understand, those lines that have same key are update ONLY IF they maintain the same structure (like with “message” and “profile”), otherwise they are replaced totally (like with the time stamps array). If they are new key-values they are simply added. If there are more than one occurrence (like with “url”) only one will be replaced unless we have more then one occurrence in the second JSON (but with different values, otherwise only one occurrence in the first will be replaces).

As a consequence, one can use the merge as an alternative to update. Say we want to update the “id”

val idJSON = ("id" -> "newId")
println(compact(render(idJSON)))
// prints: { "id" : "newId" }
println(pretty(render(JSON merge render(idJSON))))
/* prints:
{
  "name" : "luca",
  "id" : "newId", <--- only this value is updated
  ...

the merge will not go deeper in the nested object.

(5) For the diff operation, let’s reuse as example the previous JSON and a new one representing a new user. The function diff is going to return a Diff object in which you can three variables that are going to report the differences between the two JSONs.

val newUserJSON = """
 {
   "name":"luca",
   "id": "anotherID",
   "age": 26,
   "url":"http://www.nosqlnocry.wordpress.com",               
   "profile":{"id":"another-nickname", "score":99999, "avatar":"path.jpg"}
}
"""    
val Diff(changed, added, deleted) = JSON diff parse(newUserJSON)

The diff operation is respect to the second JSON (newUserJSON): in the “changed” variable you will find the key-values that were in the first JSON and now modified, in the “added” variable the new ones and in the last one the key-value pairs removed. Since there are no new pairs, the second will be empty, the first one will contains just the elements in bold, and the last one all those elements that are disappeared.

println(compact(render(changed)))
println(added)
println(pretty(render(deleted)))
/* print:
{"id":"anotherID","profile":{"id":"another-nickname","score":99999}}
JNothing
{
  "url" : "https://nosqlnocry.wordpress.com",
  "loginTimeStamps" : [ 1434904257, 1400689856, 1396629056 ],
  "messages" : [ {
    "id" : 1,
    "content" : "Please like this post!"
  }, {
    "id" : 2,
    "content" : "Forza Roma!"
  } ]
}*/

Please write a comment if you find some error!

Do not forget to like and share the post.
Peace!

4 comments

Leave a comment