Jakub Kutrzeba
Jakub Kutrzeba

Reputation: 1003

Matching Array Index to a Supplied Object Variable

I have external object like this:

var obj = {
    a: 2,
    b: 0,
    c: 1
}

Also I have collection with field "ref" that is referenced to obj. Values in obj object are treated as indexes for array field in collection.

{
    ref: "a",
    array: ["xyz", "abc", "def"]
}
{
    ref: "b",
    array: ["sde", "xda", "era"]
}
{
    ref: "c",
    array: ["wwe", "aea", "fre"]
}

I want to aggregate collection to get only one value from array depending on index that is specified in obj.

.aggregate([
{
    $project: {
        code: $arrayElemAt: ["$array", { $let: {
            vars: { obj: obj },
            in: "$$obj.$ref" //Field path used as variable field name of object
        }}]
    }
}])

But I get following error:

MongoError: FieldPath field names may not start with '$'.

It is related I think for following stuff: in: "$$obj.$ref"

Desired result for aggregation is:

{
    ref: "a",
    code: "def"
}
{
    ref: "b",
    code: "sde"
}
{
    ref: "c",
    code: "aea"
}

Upvotes: 1

Views: 602

Answers (1)

Neil Lunn
Neil Lunn

Reputation: 151112

You were sort of going down the right track but you are using $let in the wrong place. So the general idea is to apply the $let "around" the expressions to evaluate rather than "inside" them.

Also, you really should make the object to match an "array" before feeding to the pipeline as an input value. The operators work with "arrays" and don't handle looking up "objects" by variable names very well.

So this is is probably shortest by using $indexOfArray along with $arrayElemAt where both are available in order to match and extract the values:

var obj = {
    a: 2,
    b: 0,
    c: 1
};

var input = Object.keys(obj).map(k => ({ k: k, v: obj[k] }));
// Outputs as
// [{ "k" : "a", "v" : 2 }, { "k" : "b", "v" : 0  }, { "k" : "c", "v" : 1 }]

db.collection.aggregate([
  { "$addFields": {
    "array": {
      "$let": {
        "vars": { "obj": input },
        "in": {
          "$arrayElemAt": [
            "$array",
            { "$arrayElemAt": [
              "$$obj.v",
              { "$indexOfArray": [ "$$obj.k", "$ref" ] }
            ]}
          ]
        }
      }
    }  
  }}
])

If you actually have MongoDB 3.4.4 or greater, then you "could" apply $objectToArray internally instead, but it does not really add any value ( it's just showing off really ):

var obj = {
    a: 2,
    b: 0,
    c: 1
};

//var input = Object.keys(obj).map(k => ({ k: k, v: obj[k] }));

db.collection.aggregate([
  { "$addFields": {
    "array": {
      "$let": {
        "vars": { "obj": { "$objectToArray": obj } },
        "in": {
          "$arrayElemAt": [
            "$array",
            { "$arrayElemAt": [
              "$$obj.v",
              { "$indexOfArray": [ "$$obj.k", "$ref" ] }
            ]}
          ]
        }
      }
    }  
  }}
])

If your MongoDB does not support $indexOfArray, then you can always apply $map and $filter with $arrayElemAt extracting the "first match" from those combined statements instead:

var obj = {
    a: 2,
    b: 0,
    c: 1
};

var input = Object.keys(obj).map(k => ({ k: k, v: obj[k] }));

db.collection.aggregate([
  { "$addFields": {
    "array": {
      "$arrayElemAt": [
        "$array",
        { "$arrayElemAt": [
          { "$map": {
            "input": {
              "$filter": {
                "input": input,
                "cond": { "$eq": [ "$$this.k", "$ref" ] }
              }
            },
            "in": "$$this.v"
          }},
          0
        ]}
      ]
    }  
  }}
])

Which also removes any usage of $let since we aren't notating against the supplied variable for anything other than the "input" to $filter directly.

No matter which is applied, all come to the same output which is using the supplied index positions from the input obj ( coerced to input as an array ) and returning that array entry:

/* 1 */
{
    "_id" : ObjectId("59a76d6fad465e105d9136dc"),
    "ref" : "a",
    "array" : "def"
}

/* 2 */
{
    "_id" : ObjectId("59a76d6fad465e105d9136dd"),
    "ref" : "b",
    "array" : "sde"
}

/* 3 */
{
    "_id" : ObjectId("59a76d6fad465e105d9136de"),
    "ref" : "c",
    "array" : "aea"
}

Upvotes: 1

Related Questions