How to replace all the values with the same key in a JSON tree

假装没事ソ 提交于 2020-01-04 20:30:20

问题


Before storing any JSON document in MongoDB, I need to transform all the string ids in the document into BSON ids and, vice-versa, when reading any document from MongoDB, I need to transform all the BSON ids into string id. That said, given the following JSON...

{
  "id" : "52fe942b790000790079b7d0",
  "email" : "joe@domain.com",
  "username" : "joe",
  "subscriptions" : [
    {
      "accountId" : "72fe942b790000790079b755",
      "name" : "test 1",
      "isDefault" : true
    },
    {
      "accountId" : "72fe942b796850790079b743",
      "name" : "test 2",
      "isDefault" : false
    }
  ]
}

... I need to transform it as described here below before storing it in MongoDB...

{
  "_id" : {"$oid" : "52fe942b790000790079b7d0"},
  "email" : "joe@domain.com",
  "username" : "joe",
  "subscriptions" : [
    {
      "accountId" : {"$oid" : "72fe942b790000790079b755"},
      "name" : "test 1",
      "isDefault" : true
    },
    {
      "accountId" : {"$oid" : "72fe942b796850790079b743"},
      "name" : "test 2",
      "isDefault" : false
    }
  ]
}

... and of course I need to convert back all the BSON ids into string ids when reading the document from MongoDB.

Here below is the code I've tried to convert BSON ids into string ids (using the JsZipper library):

def toPublic(json: JsValue, key: String) = json.updateAllKeyNodes {
  case ((__ \ key), value) => (key -> value \ "$oid")
}

Given that this method does not transform id into _id, it doesn't work at all and always returns res0: play.api.libs.json.JsValue = {"accounts":null}; on the other hand, if I hardcode the key like this...

def toPublic(json: JsValue) = json.updateAllKeyNodes {
  case ((__ \ "accountId"), value) => ("accountId" -> value \ "$oid")
}

... it works as expected and I get back the JSON in the second example. I'm a bit lost, so any help would be really appreciated.


回答1:


This answer assumes you're using play-json-zipper as per this question.

Given the inconsistencies (id -> _id, etc) in your data I don't think it's going to be easy to handle this without some hard-coding:

Here's a start, which handles the case you've given in a to/from manner:

def toPublic(json: JsValue) = json.updateAllKeyNodes {
  case ((_ \ "_id"), value) => "id" -> value \ "$oid"
  case ((_ \ "accountId"), value) => "accountId" -> value \ "$oid"
}

def fromPublic(json: JsValue) = json.updateAllKeyNodes {
  case ((_ \ "id"), JsString(value)) => "_id" -> Json.obj("$oid" -> value)
  case ((_ \ "accountId"), JsString(value)) => "accountId" -> Json.obj("$oid" -> value)
}

You can probably abstract this a fair bit to handle your special cases more nicely. For instance you could apply it multiply with the to/from key rules as a map:

def fromPublicWithKeys(json: JsValue, keys: Map[String,String]): JsValue = {
  def fromPublic(json: JsValue, keys: (String,String)) = json.updateAllKeyNodes {
    case ((_ \ key), JsString(value)) if key == keys._1 => keys._2 -> Json.obj("$oid" -> value)
  }
  keys.foldLeft(json)(fromPublic)
}

Usage:

fromPublicWithKeys(stdJson, Map("id" -> "_id", "accountId" -> "accountId"))
// play.api.libs.json.JsValue = {"_id":{"$oid":"52fe942b790000790079b7d0"},"email":"joe@domain.com","username":"joe","subscriptions":[{"accountId":{"$oid":"72fe942b790000790079b755"},"name":"test 1","isDefault":true},{"accountId":{"$oid":"72fe942b796850790079b743"},"name":"test 2","isDefault":false}]}

Note: one reason your first example doesn't work is that you're trying to pattern match the key value, but it's instead creating a new shadowed variable binding called key (something I often do wrong in Scala.) Instead you need to use a pattern match guard, like case ((__ \ path), _) if path == key => ....



来源:https://stackoverflow.com/questions/21801071/how-to-replace-all-the-values-with-the-same-key-in-a-json-tree

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!