Search code examples
jsonscalaplayframeworkjson4s

Scala Play Framework JSON JsNull using json4s


I'm new to Scala. How do I handle the JsNull value in my code?

I'm using json4s to convert the JSON to a map. Should I somehow be converting JsNull to an Option?

Example:

Play JSON : creating json

val jsonA: JsValue = Json.obj(
      "name" -> "Bob",
      "location" -> "Irvine",
      "resident" -> "No",
      "nick-name" -> "Bigwig",
      "age" -> "6",
      "role" -> JsNull,
      "car" -> "BMW",
      "multiple-residents" -> JsArray(Seq(
        JsObject(Seq(
          "name" -> JsString("Fiver"),
          "age" -> JsNumber(4),
          "role" -> JsObject(Seq(
                      "position" -> JsString("Fiver"),
                      "" -> JsNumber(4),
                      "role" -> JsString("janitor")
                    ))
        ))
      ))
)

json4s : parsing the json

var jsonAMap:Map[String, Any] = Map()
  val jsonAString: String = Json.stringify(jsonA)
  jsonAMap = jsonStrToMap(jsonAString) 

After the JsValue is converted to a Map using json4s it looks like this:

Map(name -> Bob, location -> Irvine, role -> null, resident -> No, car -> BMW, multiple-residents -> List(Map(name -> Fiver, age -> 4, role -> Map(position -> Fiver,  -> 4, role -> janitor))), age -> 6, nick-name -> Bigwig)

When I create a create a List of the values, I end up with a null value in my list. Once I'm pattern matching all the values of the list I end up trying to patter match a null which is not possible (I'm sure I'm not supposed to use a will card for all my cases, but I'm learning) :

for(i <- 0 to beforeValsList.length - 1){
  beforeValsList(i) match { 
    case _ : Map[_,_] => 
      compareJson(
        beforeValsList(i).asInstanceOf[Map[String,Any]], 
        afterValsList(i).asInstanceOf[Map[String,Any]], 
        rdeltaBefore, rdeltaAfter, sameKeyList(i).toString()
      )
    case _ if (beforeValsList(i) != afterValsList(i)) => 
      // if i'm from a recursion, build a new map and add me 
      // to the deltas as a key->value pair
      rdeltaBefore += sameKeyList(i).toString -> beforeValsList(i)
      rdeltaAfter += sameKeyList(i).toString -> afterValsList(i)
    case _ => 
      println("catch all: " + beforeValsList(i).toString 
        + " " + afterValsList(i).toString)
  } 
}

json4s converts JsNull to a null. Should I do a null check:

if(!beforeValsList(i) == null){
      beforeValsList(i) match{...}
}

Or is there a way for me to change the null to an Option when I'm putting the values from the Map to a List?

I'm not sure what best practices are and why jsno4s changes JsNull to null instead of an Option, and whether or not that's possible.

Cheers.


Solution

  • I'm still not sure how you would like to handle JsNull, null (or None if you use Option), but you function to get the difference between the Map[String, Any] before and after can be simplified :

    type JsonMap = Map[String, Any]
    
    def getMapDiffs(mapBefore: JsonMap, mapAfter: JsonMap) : (JsonMap, JsonMap) = {
      val sameKeys = mapBefore.keySet intersect mapAfter.keySet
      val startAcc = (Map.empty[String, Any], Map.empty[String, Any])
      sameKeys.foldLeft(startAcc){ case (acc @ (deltaBefore, deltaAfter), key) =>
        (mapBefore(key), mapAfter(key)) match {
          // two maps -> add map diff recursively to before diff and after diff
          case (beforeMap: Map[_, _], afterMap: Map[_, _]) =>
            val (deltaB, deltaA) = 
              getMapDiffs(beforeMap.asInstanceOf[JsonMap], afterMap.asInstanceOf[JsonMap])
            (deltaBefore + (key -> deltaB), deltaAfter + (key -> deltaA))
          // values before and after are different
          // add values to before diff and after diff
          case (beforeValue, afterValue) if beforeValue != afterValue =>
            (deltaBefore + (key -> beforeValue), deltaAfter + (key -> afterValue))
          // keep existing diff  
          case _ => acc
        }  
      }
    }
    

    Which can be used as:

    val (mapBefore, mapAfter) = (
      Map("a" -> "alpha", "b" -> "beta", "c" -> "gamma", "d" -> Map("e" -> "epsilon")),
      Map("a" -> "alpha", "b" -> List("beta"), "c" -> null, "d" -> Map("e" -> 3))
    )
    
    val (deltaBefore, deltaAfter) = getMapDiffs(mapBefore, mapAfter)
    // deltaBefore: JsonMap = Map(b -> beta, c -> gamma, d -> Map(e -> epsilon))
    // deltaAfter: JsonMap = Map(b -> List(beta), c -> null, d -> Map(e -> 3))
    
    deltaBefore.toList
    // List[(String, Any)] = List((b,beta), (c,gamma), (d,Map(e -> epsilon)))