Play [Scala]: How to smooth a JSON object

Given the following JSON ...

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

... how to convert it to

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

And I.

+4
source share
6 answers

This is definitely not trivial, but perhaps trying to smooth it out recursively. I have not tested this completely, but it works with your example and some other basic ones that I came 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

}

JsObjecthas a method fieldSetthat returns Set[(String, JsValue)]that I matched, matched with a subclass, JsValueand continued recursively recursively from there.

This example can be used by passing JsValuein apply:

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

We will leave this as an exercise for the reader to make the code more beautiful.

+7

Play JSON. - , , :

import play.api.libs.json._

val flattenMeta = (__ \ 'metadata).read[JsObject].flatMap(
  _.fields.foldLeft((__ \ 'metadata).json.prune) {
    case (acc, (k, v)) => acc andThen __.json.update(
      Reads.of[JsObject].map(_ + (s"metadata.$k" -> v))
    )
  }
)

:

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

scala> json.transform(flattenMeta).foreach(Json.prettyPrint _ andThen println)
{
  "metadata.id" : "1234",
  "metadata.type" : "file",
  "metadata.length" : 395
}

, metadata - .


, - ., , Pascal Voitot , :

(json \ "metadata").as[JsObject].fields.foldLeft(Json.obj()) {
  case (acc, (k, v)) => acc + (s"metadata.$k" -> v)
}

, as , , .

+8

, @Travis Brown.

json .

def flatten(js: JsValue, prefix: String = ""): JsObject = js.as[JsObject].fields.foldLeft(Json.obj()) {
    case (acc, (k, v: JsObject)) => {
        if(prefix.isEmpty) acc.deepMerge(flatten(v, k))
        else acc.deepMerge(flatten(v, s"$prefix.$k"))
    }
    case (acc, (k, v)) => {
        if(prefix.isEmpty) acc + (k -> v)
        else acc + (s"$prefix.$k" -> v)
    }
}

:

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

:

{
  "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"
}
+4

m-z, . ( Scala.)

"" JSON, "{metadata: [" aaa "," bob "]}".

  def flatten(js: JsValue, prefix: String = ""): Seq[JsValue] = {

    // JSON primitive array can't convert to JsObject
    if(!js.isInstanceOf[JsObject]) return Seq(Json.obj(prefix -> js))

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

@Trev , , . -, :

:

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

:

{
  "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"
}

, , , , . .

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

,

  def getNewKey(oldKey: String, newKey: String): String = {
    if (oldKey.nonEmpty) oldKey + "." + newKey else newKey
  }

  def flatten(js: JsValue, prefix: String = ""): JsObject = {
    if (!js.isInstanceOf[JsObject]) return Json.obj(prefix -> js)
    js.as[JsObject].fields.foldLeft(Json.obj()) {
      case (o, (k, value)) => {
        o.deepMerge(value match {
          case x: JsArray => x.as[Seq[JsValue]].zipWithIndex.foldLeft(o) {
            case (o, (n, i: Int)) => o.deepMerge(
              flatten(n.as[JsValue], getNewKey(prefix, k) + s"[$i]")
            )
          }
          case x: JsObject => flatten(x, getNewKey(prefix, k))
          case x => Json.obj(getNewKey(prefix, k) -> x.as[JsValue])
        })
      }
    }
  }
0

Source: https://habr.com/ru/post/1544989/


All Articles