Azmisov
Azmisov

Reputation: 7233

Project field defined by another field's value

I have a document structured like so:

mode: "b",
a: [0,1,2],
b: [1,4,5],
c: [2,2]

And I want to project the field that equals mode. The end result should be something like:

data: [1,4,5] // since mode == "b", it returns b's value

I tried $$CURRENT[$mode], but it looks like you can't use brackets like that in mongo. I tried using a local variable like so:

$let: {
    vars: {mode: "$mode"},
    in: "$$CURRENT.$$mode"
}

but that doesn't work either. I'm considering using $switch and then manually putting in all the possible modes. But I'm wondering if there is a better way to do it.

Upvotes: 1

Views: 273

Answers (1)

Neil Lunn
Neil Lunn

Reputation: 151072

You are looking in the wrong place, but if you can use $switch then you have MongoDB 3.4 and you can use $objectToArray which is actually the correct thing to do. Your problem is you are trying to "dynamicaly" refer to a property by the "value" of it's "key name". You cannot do that, so $objectToArray makes the "key" a "value"

So given your document:

{ "mode": "a", "a": [0,1,2], "b": [1,4,5], "c": [2,2] }

Then you do the aggregate, using $map and $filter to work with the converted elements as an array:

db.sample.aggregate([
  { "$project": {
    "_id": 0,
    "mode": 1,
    "data": {
      "$arrayElemAt": [
        { "$map": {
          "input": {
            "$filter": {    
              "input": { "$objectToArray": "$$ROOT" },
              "cond": { "$eq": ["$$this.k","$mode"] }
            }
          },      
          "in": "$$this.v"
        }},
        0
      ]
    }  
  }}
])

Or using $let and $indexOfArray if that seems more sensible to you:

db.sample.aggregate([
  { "$project": {
    "_id": 0,    
    "mode": 1,
    "data": {
      "$let": {
        "vars": { "doc": { "$objectToArray": "$$ROOT" } },
        "in": {
          "$arrayElemAt": [
            "$$doc.v",
            { "$indexOfArray": [ "$$doc.k", "$mode" ] }
          ]    
        }   
      }
    }
  }}
])

Which matches the selected field:

{
    "mode" : "a",
    "data" : [ 
        0.0, 
        1.0, 
        2.0
    ]
}

If you look at "just" what $objectToArray is doing here, then the reasons should be self evident:

{
    "data" : [ 
        {
            "k" : "_id",
            "v" : ObjectId("597915787dcd6a5f6a9b4b98")
        }, 
        {
            "k" : "mode",
            "v" : "a"
        }, 
        {
            "k" : "a",
            "v" : [ 
                0.0, 
                1.0, 
                2.0
            ]
        }, 
        {
            "k" : "b",
            "v" : [ 
                1.0, 
                4.0, 
                5.0
            ]
        }, 
        {
            "k" : "c",
            "v" : [ 
                2.0, 
                2.0
            ]
        }
    ]
}

So now instead of there being an "object" with named properties, the "array" consistently contains "k" named as the "key" and "v" containing the "value". This is easy to $filter and obtain the desired results, or basically use any method that works with arrays to obtain the match.

Upvotes: 1

Related Questions