问题
I have a collection of next objects:
{
"_did": "213412323234",
"metadata": [
{
"id": "Language",
"value": "EN"
},
{
"id": "City",
"value": "New York"
},
{
"id": "Gender",
"value": "Male"
}
]
}
After using some pipeline operators (maybe $project), I expect something like this:
{
"id": "213412323234",
"metadata": {
"Language": "EN",
"City": "New York",
"Gender": "Male"
}
}
回答1:
Try this query script
var results = db.meta.aggregate([{
$project: {
id: "$_did",
_id: 0,
keys: "$metadata.id",
values: "$metadata.value"
}
}])
function object(keys, values) {
var obj = {};
for (var index in keys) {
obj[keys[index]] = values[index];
}
return obj;
}
results = results.map(function (res) {
return {
id: res.id,
metadata: object(res.keys, res.values)
}
})
回答2:
If the position of elements in the metadata
field and the keys of embedded documens is known and then you can use aggregation.
db.foo.aggregate([
{
$project : {
data : {
"Language" : {
$arrayElemAt : ["$metadata", 0]
},
"City" : {
$arrayElemAt : ["$metadata", 1]
},
"Gender" : {
$arrayElemAt : ["$metadata", 2]
}
}
}
},
{
$project : {
metadata : {
Language : "$data.Language.value",
City : "$data.City.value",
Gender : "$data.Gender.value"
}
}
}
])
Result :
{ "_id" : "213412323234", "metadata" : { "Language" : "EN", "City" : "New York", "Gender" : "Male" } }
If any of the above two preconditions mentioned above are not known, you can use mapReduce.
db.foo.mapReduce(function () {
var key = this._id;
var metadata = this.metadata;
var valueEmit = {};
for (var i = 0; i < metadata.length; i++) {
valueEmit[metadata[i].id] = metadata[i].value;
}
emit(key, valueEmit)
},
function(key, values) {
// do nothing as it won't be called
// for if value is only one
// for a key
},
{
out : {replace : "foobar"}
})
Result :
> db.foobar.find()
{ "_id" : "213412323234", "value" : { "Language" : "EN", "City" : "New York", "Gender" : "Male" } }
Notice that the key has been changed to value
instead of metadata. This can be easily fixed using a $project
in aggregation
on foobar
collection.
回答3:
According to this feature request and its solution, the new feature will be added for this functionality - Function called arrayToObject
.
db.foo.aggregate([
{$project: {
metadata: {
$arrayToObject: {
$map: {
input: "$metadata",
as: "pair",
in: ["$$pair.id", "$$pair.value"]
}
}
}
}}
])
But at the moment, no solution. I suggest you to change your data structure. As far as I see there are many feature requests labeled as Major but not done for years.
来源:https://stackoverflow.com/questions/41632950/reformat-collection-using-aggregation-pipeline-in-mongodb