[英]Play [Scala]: How to flatten a JSON object
鑒於以下JSON ......
{
"metadata": {
"id": "1234",
"type": "file",
"length": 395
}
}
...我該如何將其轉換為
{
"metadata.id": "1234",
"metadata.type": "file",
"metadata.length": 395
}
TX。
你可以用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
在實際的代碼,但它可能是你所需要的。
這絕對不是微不足道的,但可以通過嘗試遞歸地展平它。 我沒有對此進行徹底的測試,但它適用於您的示例以及我使用數組提出的其他一些基本功能:
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
有一個fieldSet
方法,它返回一個Set[(String, JsValue)]
,我將其映射,與JsValue
子類進行匹配,並從那里繼續遞歸消耗。
您可以通過傳遞JsValue
來apply
此示例:
val json = Json.parse("""
{
"metadata": {
"id": "1234",
"type": "file",
"length": 395
}
}
"""
JsFlattener(json)
我們將它作為練習留給讀者,使代碼看起來更漂亮。
這是我對這個問題的看法,基於@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"
}
謝謝你,這非常有幫助。 (我對Scala不太熟悉。)
我想添加一行“flatten”,使用原始的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))
}
}
}
@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)
}
}
基於以前的解決方案,嘗試簡化代碼
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])
})
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.