问题
Before storing any JSON document in MongoDB, I need to transform all the string id
s in the document into BSON id
s and, vice-versa, when reading any document from MongoDB, I need to transform all the BSON id
s 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 id
s into string id
s when reading the document from MongoDB.
Here below is the code I've tried to convert BSON id
s into string id
s (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