2

I have a collection that has objects of various random structures/depths. Within them, there is one object that I want to get as my results, which has a specific key/value pair that I can use to find them.

An example from my collection:

{
    "_id": ObjectId("123"),
    "someKey": "blue",
    "children": [
        {
            "foo": "bar",
            "anotherKey": "anotherValue",
            "whateverA": 111
        }
    ]
}
{
    "_id": ObjectId("456"),
    "blahKey": "dog",
    "children": [
        {
            "anotherRandom": "randomValue",
            "children": [
                {
                    "moreRandom": "stuffValue",
                    "children": [
                        {
                            "foo": "bar",
                            "animalKey": "legsValue",
                            "whateverB": 222
                        }
                    ]
                }
            ]
        }
    ]
}

I would like to search for subdocuments that contain "foo: bar", and get a result that looks like the following:

{
    "foo": "bar",
    "anotherKey": "anotherValue",
    "whateverA": 111
}
{
    "foo": "bar",
    "animalKey": "legsValue",
    "whateverB": 222
}

Then I can paginate the results. Is this even possible in MongoDB 5?

Thank you.

1
  • Please see this Commented May 9, 2022 at 14:58

1 Answer 1

2

If we use the solution of @rickhg12hs

You can do something like:

EDIT: fixed to handle case of not found (Thanks to a comment by @Jimba):

db.collection.aggregate([
  {
    $addFields: {
      res: {
        $function: {
                 body: "function drill(t, n) {if (n && n.length > 0){for (let elem of n){if(elem['foo'] && elem['foo'] === 'bar'){t.push(elem);}else {drill(t, elem.children)}}}return t}",
          args: [
            [],
            "$children"
          ],
          lang: "js"
        }
      }
    }
  },
  {
    $project: {
      res: {
        $cond: [
          {$gt: [{$size: "$res"}, 0]},
          {$arrayElemAt: ["$res", 0]},
          {res: null}
        ]
      },
      _id: 0
    }
  },
  {
    $match: {res: {$ne: null}}
  },
  {
    $replaceRoot: {newRoot: "$res"}
  }
])

As you can see on this playground example.

We can use $function to recursively look for this key named foo and return the object that contains it.

Edit according to a question in the comment:

You can use your code to manipulate it according to your needs: for example in js:

const key = 'foo';
const val = 'bar';
const body = `function drill(t, n) {if (n && n.length > 0){for (let elem of n){if(elem[${key}] && elem[${key}] === ${val}){t.push(elem);}else {drill(t, elem.children)}}}return t}`;


db.collection.aggregate([
  {
    $addFields: {res: {$function: {body, args: [[], "$children"], lang: "js"}}}
  },
  {
    $project: {
      res: {
        $cond: [
          {$gt: [{$size: "$res"}, 0]},
          {$arrayElemAt: ["$res", 0]},
          {res: null}
        ]
      },
      _id: 0
    }
  },
  {
    $match: {res: {$ne: null}}
  },
  {
    $replaceRoot: {newRoot: "$res"}
  }
])
Sign up to request clarification or add additional context in comments.

8 Comments

I have one quick question regarding the above code: - How do I incorporate my db.collection.find( { ... } ) search parameters in the above aggragate command? Thank you.
Your query is a json up until it is executed. You can edit this json in your code, according to your needs before you execute the query.
Updated the answer to contain this.
WARNING: the above javascript function (drill) does not work when the key is not found. It produces the following error: 'newRoot' expression must evaluate to an object, but resulting value was: MISSING. Type of resulting value: 'missing', Playground example: mongoplayground.net/p/jix1_LjIXv5
Updated the answer accordingly
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.