6

I have a mongo document that contains an array of Strings, and I need to convert this particular array of strings into an array of object containing a key-value pair. Below is my curent appraoch to it.

{
    "_id" : ObjectId("57e3720836e36f63695a2ef2"),
    "platform" : "A1",
    "available" : {
        "Community" : {
            "attributes" : {
                "type" : {
                    "values" : [
                        "well-known",
                        "simple",
                        "complex"
                    ],
                    "defaultValue" : "well-known"
                },
[......]


}

Current Query:

templateAttributes.find({platform:"V1"}).map(function(c){
  //instantiate a new array
  var optionsArray = [];
for (var i=0;i< c['available']['Community']['attributes']['type']['values'].length; i++){
    optionsArray[i] = {};              // creates a new object
    optionsArray[i].label = c['available']['Community']['attributes']['type']['values'][i];
    optionsArray[i].value = c['available']['Community']['attributes']['type']['values'][i];
    }
    return optionsArray;
})[0];

Result:

[{label:"well-known", value:"well-known"},
{label:"simple", value:"simple"},
{label:"complex", value:"complex"}]

Is my approach efficient enough, or is there a way to optimize the above query to get the same desired result?

chridam
  • 100,957
  • 23
  • 236
  • 235
blueren
  • 2,730
  • 4
  • 30
  • 47
  • Any specific reasons this has to be done on mongodb side ? – S.D. Sep 23 '16 at 07:02
  • No. There is no specific reason it needs to be done on the mongo side. Since I'm using this in Meteor, this query is run in memory (mini-mongo). – blueren Sep 23 '16 at 08:52
  • @bluereen Ok. Then aggregate is the solution. Just keep in mind that `$unwind` will multiply the result set size array.length times. So use a `$project` before to just keep required fields, perhaps only 'values'. – S.D. Sep 23 '16 at 09:05

3 Answers3

4

Not so sure what you want to do with the end result as the keys and the values are just the same. Nonetheless, you can use the aggregation framework where you can denormalise the embedded values array by using the $unwind operator which flattens it i.e. it produces a copy of each document per array entry.

After flattening the values array, you can apply the $group accumulation operators on the values to aggregate them. A final pipeline of the $project operator would shape the fields from the previous grouping into the desired format.

Follow this example to get the concept:

templateAttributes.aggregate([
    { "$match": { "platform": "V1" } },
    { "$unwind": "$available.Community.attributes.type.values" },
    {
        "$group": {
            "_id": "$available.Community.attributes.type.values",
            "value": { "$first": "$available.Community.attributes.type.values" }
        }
    },
    {
        "$project": {
            "_id": 0,
            "label": "$_id",
            "value": 1
        }
    }
])

Since you are using Meteor, meteor add meteorhacks:aggregate will add proper aggregation support for Meteor so that you can run the above aggregation pipeline on your collection.

chridam
  • 100,957
  • 23
  • 236
  • 235
  • I intend to pass them to a HTML menu. Thanks! – blueren Sep 23 '16 at 08:48
  • 1
    Thanks Chris. I'm still debating on the package and the whole aggegate query since the package only does things at server side. Added to that, I'm using autoforms and feeding the dropdown data from the mongo query on client side – blueren Sep 23 '16 at 10:46
3

Please use aggregation here.

db.templateAttributes.aggregate([
                                    {"$match":{platform:"A1"}}, {"$unwind": "$available.Community.attributes.type.values"}, 
                                    {$group: {"_id": null, "val":{"$push":{label:"$available.Community.attributes.type.values", 
                                                                            value:"$available.Community.attributes.type.values"}}}}
                                ]).toArray()[0].val

Output:

[
    {
            "label" : "well-known",
            "value" : "well-known"
    },
    {
            "label" : "simple",
            "value" : "simple"
    },
    {
            "label" : "complex",
            "value" : "complex"
    }
]
4J41
  • 5,005
  • 1
  • 29
  • 41
0

Use query

  templateAttributes.aggregate([{
$match: {
    "platform" : "A1"
    }},
    {
    $unwind: {path : "$available.Community.attributes.type.values"}},{   $group:{
    _id:"$_id",
    result:{
        $push: {
            label: "$available.Community.attributes.type.values", value: "$available.Community.attributes.type.values"
            }
        }
    }}])

It'll give you exact your expected answer.

Vaibhav Patil
  • 2,603
  • 4
  • 14
  • 22