Play [Scala]: How to flatten a JSON object

后端 未结 6 1029
無奈伤痛
無奈伤痛 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:58

    @Trev has the best solution here, completely generic and recursive, but it's missing a case for array support. I'd like something that works in this scenario:

    turn this:

    {
      "metadata": {
        "id": "1234",
        "type": "file",
        "length": 395
      },
      "foo": "bar",
      "person": {
        "first": "peter",
        "last": "smith",
        "address": {
          "city": "Ottawa",
          "country": "Canada"
        },
        "kids": ["Bob", "Sam"]
      }
    }
    

    into this:

    {
      "metadata.id": "1234",
      "metadata.type": "file",
      "metadata.length": 395,
      "foo": "bar",
      "person.first": "peter",
      "person.last": "smith",
      "person.address.city": "Ottawa",
      "person.address.country": "Canada",
      "person.kids[0]": "Bob",
      "person.kids[1]": "Sam"
    }
    

    I've arrived at this, which appears to work, but seems overly verbose. Any help in making this pretty would be appreciated.

    def flatten(js: JsValue, prefix: String = ""): JsObject = js.as[JsObject].fields.foldLeft(Json.obj()) {
      case (acc, (k, v: JsObject)) => {
        val nk = if(prefix.isEmpty) k else s"$prefix.$k"
        acc.deepMerge(flatten(v, nk))
      }
      case (acc, (k, v: JsArray)) => {
        val nk = if(prefix.isEmpty) k else s"$prefix.$k"
        val arr = flattenArray(v, nk).foldLeft(Json.obj())(_++_)
        acc.deepMerge(arr)
      }
      case (acc, (k, v)) => {
        val nk = if(prefix.isEmpty) k else s"$prefix.$k"
        acc + (nk -> v)
      }
    }
    
    def flattenArray(a: JsArray, k: String = ""): Seq[JsObject] = {
      flattenSeq(a.value.zipWithIndex.map {
        case (o: JsObject, i: Int) =>
          flatten(o, s"$k[$i]")
        case (o: JsArray, i: Int) =>
          flattenArray(o, s"$k[$i]")
        case a =>
          Json.obj(s"$k[${a._2}]" -> a._1)
      })
    }
    
    def flattenSeq(s: Seq[Any], b: Seq[JsObject] = Seq()): Seq[JsObject] = {
      s.foldLeft[Seq[JsObject]](b){
        case (acc, v: JsObject) =>
          acc:+v
        case (acc, v: Seq[Any]) =>
          flattenSeq(v, acc)
      }
    }
    

提交回复
热议问题