Play [Scala]: How to flatten a JSON object

后端 未结 6 1096
無奈伤痛
無奈伤痛 2021-02-06 13:07

Given the following JSON...

{
  \"metadata\": {
    \"id\": \"1234\",
    \"type\": \"file\",
    \"length\": 395
  }
}

... how do I convert it

6条回答
  •  刺人心
    刺人心 (楼主)
    2021-02-06 13:54

    This is definitely not trivial, but possible by trying to flatten it recursively. I haven't tested this thoroughly, but it works with your example and some other basic one's I've come up with using arrays:

    object JsFlattener {
    
        def apply(js: JsValue): JsValue = flatten(js).foldLeft(JsObject(Nil))(_++_.as[JsObject])
    
        def flatten(js: JsValue, prefix: String = ""): Seq[JsValue] = {
            js.as[JsObject].fieldSet.toSeq.flatMap{ case (key, values) =>
                values match {
                    case JsBoolean(x) => Seq(Json.obj(concat(prefix, key) -> x))
                    case JsNumber(x) => Seq(Json.obj(concat(prefix, key) -> x))
                    case JsString(x) => Seq(Json.obj(concat(prefix, key) -> x))
                    case JsArray(seq) => seq.zipWithIndex.flatMap{ case (x, i) => flatten(x, concat(prefix, key + s"[$i]")) }  
                    case x: JsObject => flatten(x, concat(prefix, key))
                    case _ => Seq(Json.obj(concat(prefix, key) -> JsNull))
                }
            }
        }
    
        def concat(prefix: String, key: String): String = if(prefix.nonEmpty) s"$prefix.$key" else key
    
    }
    

    JsObject has the fieldSet method that returns a Set[(String, JsValue)], which I mapped, matched against the JsValue subclass, and continued consuming recursively from there.

    You can use this example by passing a JsValue to apply:

    val json = Json.parse("""
        {
          "metadata": {
            "id": "1234",
            "type": "file",
            "length": 395
          }
        }
    """
    JsFlattener(json)
    

    We'll leave it as an exercise to the reader to make the code more beautiful looking.

提交回复
热议问题