0

I have a nested tree level structure of item->items that looks something like this

{ "id":"1",
  "type":"panel",
  "items": [
    { "id":"2", "type":"input", },
    { "id":"4", "type":"group", "items": [ 
        { "id":"5", "type":"input" },
        { "id":"6", "type":"panel", "items":[...] },
      ]
    }
  ]}

I'm looking to flatten the tree and get a single array list of all items like this:

[ { "id":"1", "type":"panel", },
  { "id":"2", "type":"input", },
  { "id":"4", "type":"panel", },
  { "id":"5", "type":"input", },
  ...
]

Is there a generic way to flatten the tree (that would work for any depth level)?

All answers I found here just manually $unwind each child level (I can't predict the number of levels) nor do I have reference to parent to use traverse with $graphLookup.

Or something like {'$*.items'}?

0

2 Answers 2

1

MQL doesn't have functions, so we can't recur, if we find a array.
Maybe there is a way to do it with MQL and 1 query.

But there is way to do it fast with more than 1 query. The bellow example is 1 level/query.

With small change it can do 10 level/query or 100 level/query etc so only 1 query will be needed, but we will do some redadent attempts to flatten arrays even if they are empty.

First 1 small modification.
Add 1 field on all documents "all-items": [{"id": "$id","type": "$type"}] and removed the top level "id" and "type". Like bellow

aggregate(
[ {
  "$project" : {
    "all-items" : [ {
      "id" : "$id",
      "type" : "$type"
    } ],
    "items" : 1
  }
} ]
)

Modified data

[
  {
    "all-items": [
      {
        "id": "1",
        "type": "panel"
      }
    ],
    "items": [...like it was...]
  }
]

And now we can do it with multiple queries 1 per/level

First call, code example
Second call, code example, with the result of first call

Third call we dont need, while will be false.

In each call we do $out, and we aggregate on the result of previous call.

while(there_is_1_document_with_not_empty_items[]) (send 1 find query)

db.collection.aggregate([
  {
    "$addFields": {
      "level-nlevel": {
        "$reduce": {
          "input": "$items",
          "initialValue": [
            [],
            []
          ],
          "in": {
            "$let": {
              "vars": {
                "info": "$$value",
                "i": "$$this"
              },
              "in": {
                "$let": {
                  "vars": {
                    "level": {
                      "$arrayElemAt": [
                        "$$info",
                        0
                      ]
                    },
                    "nlevel": {
                      "$arrayElemAt": [
                        "$$info",
                        1
                      ]
                    }
                  },
                  "in": [
                    {
                      "$concatArrays": [
                        "$$level",
                        [
                          {
                            "id": "$$i.id",
                            "type": "$$i.type"
                          }
                        ]
                      ]
                    },
                    {
                      "$cond": [
                        {
                          "$isArray": [
                            "$$i.items"
                          ]
                        },
                        {
                          "$concatArrays": [
                            "$$nlevel",
                            "$$i.items"
                          ]
                        },
                        "$$nlevel"
                      ]
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  {
    "$project": {
      "all-items": {
        "$concatArrays": [
          "$all-items",
          {
            "$arrayElemAt": [
              "$level-nlevel",
              0
            ]
          }
        ]
      },
      "items": {
        "$arrayElemAt": [
          "$level-nlevel",
          1
        ]
      }
    }
  }
])

This flattens per document(no $unwind is used), if you want to flatten all collection, $unwind one time after the while ends the $all-items.

Sign up to request clarification or add additional context in comments.

1 Comment

i wrote this, total forgotten about the $function option which is much better, dont use this use the other solution described above, i just leave it here as a possible way to do it in MQL.
1

There is not a mongodb query language aggregation sage that supports flatting to an unknown depth, but $function would allow you to execute a method against the document

here is a javascript example:

var fn = function(items) {
    var ret = [];
    var toCheck = [...items];
    while (toCheck.length) {
        var nxtToCheck = [];
        for (var item of toCheck) {
            ret.push({ id: item.id, type: item.type });
            nxtToCheck.push(...(item.items || []));
        }
        toCheck = nxtToCheck;
    }
    return ret;
}

db.myCol.aggregate([
    { $match: {} },
    { $addFields: { allItems: { $function: { body: fn, args: ["$items"], lang: "js" } } } }
]);
    

Comments

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.