Scala Play Framework JSON JsNull using json4s

2019-09-17 08:01发布

问题:

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.

回答1:

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)))