2

Overview :

The documents, that I'm working upon, have two nested arrays in them - contentMetaData & text_content. Within contentMetaData, we have the text_content and content_flag. Based on the value of the content_flag, I need to hide specific field within the text_content.

Requirement :

  • If the content_flag is true, text_content should have a single child - the text_note.
  • If the content_flag is false, text_content should have a single child - the text_description.
  • The structure and other details need to be preserved.
  • Documents SHOULD NOT be updated; the values need to be only hidden during projection.

Version Used : Mongo 2.6

Sample Document :

{
  "_id": ObjectId("56f8dd19e4b0365115927b0f"),
  "contentId": "cbc91805-2faa-4eff-8f84-02547173c152",
  "contentMetaData": [
    {
      "_id": "1574b58f-b7fa-4cd5-b34f-98beeb657c97",
      "name": "text_content",
      "attributes": [],
      "children": [
        {
          "_id": "97340ecf-fdbd-41e5-a6b2-01cc542f16ee",
          "name": "text_note",
          "value": "abc",
          "type": "java.lang.String",
          "attributes": [],
          "children": [],
          "noOfChildren": 0,
          "positionIndex": 1
        },
        {
          "_id": "19c5a3fb-54a2-4368-a89d-ea1d2554402d",
          "name": "text_description",
          "value": "def",
          "type": "java.lang.String",
          "attributes": [],
          "children": [],
          "noOfChildren": 0,
          "positionIndex": 2
        }
      ],
      "noOfChildren": 2,
      "positionIndex": 1
    },
    {
      "_id": "4e8ef7c9-cffd-4b36-9109-89b263dff3c8",
      "name": "content_flag",
      "value": "true",
      "type": "java.lang.String",
      "attributes": [],
      "children": [],
      "noOfChildren": 0,
      "positionIndex": 2
    }
  ]
}

Sample Output :

{
  "_id":  ObjectId("56f8dd19e4b0365115927b0f"),
  "contentId": "cbc91805-2faa-4eff-8f84-02547173c152",
  "contentMetaData": [
    {
      "_id": "1574b58f-b7fa-4cd5-b34f-98beeb657c97",
      "name": "text_content",
      "attributes": [],
      "children": [
        {
          "_id": "97340ecf-fdbd-41e5-a6b2-01cc542f16ee",
          "name": "text_note",
          "value": "abc",
          "type": "java.lang.String",
          "attributes": [],
          "children": [],
          "noOfChildren": 0,
          "positionIndex": 1
        }
      ],
      "noOfChildren": 2,
      "positionIndex": 1
    },
    {
      "_id": "4e8ef7c9-cffd-4b36-9109-89b263dff3c8",
      "name": "content_flag",
      "value": "true",
      "type": "java.lang.String",
      "attributes": [],
      "children": [],
      "noOfChildren": 0,
      "positionIndex": 2
    }
  ]
}

I attempted using $map but it didn't work. I tried using $unwind, but was unable to $push the data back, in the desired format.

Sample Mongo Code :

db.content.aggregate([
{
    $project: {
        _id: 1,
        contentId: 1,
        contentMetaData: 1
        tempMetaData: "$contentMetaData"
    }
},
{
    $unwind: "$contentMetaData"
},
{
    $match: {
        "contentMetaData.name": "content_flag"
    }
},
{
    $project: {
        _id: 1,
        contentId: 1,
        contentMetaData: "$tempMetaData",
        content_flag_value: "$contentMetaData.value"
    }
},
{
    $project: {
        _id: 1,
        contentId: 1,
        contentMetaData: 1,
        tempMetaData: "$contentMetaData",
        content_flag_value: 1
    }
},
{
        $unwind: "$contentMetaData"
},
{
        $match: {
                "contentMetaData.name": "text_content"
        }
},
{
        $project: {
                _id: 1,
                contentId: 1,
                contentMetaData: 1,
                tempMetaData: "$contentMetaData",
                content_flag_value: 1,
                text_content : "$contentMetaData.children",
                temp_text_content: "$text_content"
        }
},
{
        $unwind: "$text_content"
},
{
        $group:{
            _id:"$_id",
            contentId:{$first:"$contentId"},
            text_content:
            {$max:
                {$cond: 
                    [
                        {$eq: ["$content_flag_value", "true"]},
                        {$cond:
                            [{$or:[
                                {$eq: ["$text_content.name","wk_link_url"]},
                                {$eq: ["$text_content.name","wk_link_description"]}
                            ]},
                            "$text_content",
                            null]
                        },
                        null
                    ]
                }
            },
            contentMetaData:{$first:"$contentMetaData"}
        }
},
{
        $group:{
            _id:"$_id",
            contentId:{$first:"$contentId"},
            contentMetaData:{$push:{"text_content":"$text_content"}}
        }
},
{
        $project: {
                _id: 0,
                contentId: 1,
                contentMetaData: 1
        }
}]).pretty()

I'm new to Mongo. Can somebody help me out with this?

3
  • I see $map in your post. Does it mean you can use 3.x mongo version ? Commented Jan 27, 2017 at 14:54
  • @Veeram $map is also available in 2.6 Commented Jan 27, 2017 at 15:09
  • @Veeram - As Chridam mentioned, it's available in 2.6 as well. Commented Jan 28, 2017 at 6:05

1 Answer 1

1

You can try the below aggregation.

$map in combination with $setDifference to extract text_content and content_flag array.

$unwind to content_flag document.

$map to keep the current values in text_content and $map in combination with $setDifference to filter the children on the criteria.

$setUnion to join back the text_content and content_flag array into contentMetaData

db.collection.aggregate({
    $project: {
        _id: 1,
        contentId: 1,
        text_content: {
            "$setDifference": [{
                    "$map": {
                        "input": "$contentMetaData",
                        "as": "text",
                        "in": {
                            "$cond": [{
                                    $eq: ['$$text.name', "text_content"]
                                },
                                "$$text",
                                false
                            ]
                        }
                    }
                },
                [false]
            ]
        },
        content_flag: {
            "$setDifference": [{
                    "$map": {
                        "input": "$contentMetaData",
                        "as": "content",
                        "in": {
                            "$cond": [{
                                    $eq: ['$$content.name', "content_flag"]
                                },
                                "$$content",
                                false
                            ]
                        }
                    }
                },
                [false]
            ]
        }
    }
}, {
    $unwind: "$content_flag"
}, {
    $project: {
        "_id": 1,
        contentId: 1,
        "contentMetaData": {
            $setUnion: [{
                    $map: {
                        input: "$text_content",
                        as: "text",
                        in: {
                            "_id": "$$text._id",
                            "name": "$$text.name",
                            "attributes": "$$text.attributes",
                            "noOfChildren": "$$text.noOfChildren",
                            "positionIndex": "$$text.positionIndex",
                            "children": {
                                "$setDifference": [{
                                        "$map": {
                                            "input": "$$text.children",
                                            "as": "child",
                                            "in": {
                                                "$cond": [{
                                                        "$cond": [{
                                                            $eq: ["$content_flag.value", "true"]
                                                        }, {
                                                            $eq: ["$$child.name", "text_note"]
                                                        }, {
                                                            $eq: ["$$child.name", "text_description"]
                                                        }]
                                                    },
                                                    "$$child",
                                                    false
                                                ]
                                            }
                                        }
                                    },
                                    [false]
                                ]
                            }
                        }
                    }
                },
                ["$content_flag"]
            ]
        }
    }
})

Update:

$map in combination with $setDifference to extract content_flag array.

$unwind to content_flag document.

$redact to go through a document level at a time and look for name field recursively and perform $$DESCEND and $$PRUNE on the criteria.

$project to format the final response.

db.collection.aggregate({
    $project: {
        _id: 1,
        contentId: 1,
        contentMetaData: 1,
        content_flag: {
            "$setDifference": [{
                    "$map": {
                        "input": "$contentMetaData",
                        "as": "content",
                        "in": {
                            "$cond": [{
                                    $eq: ['$$content.name', "content_flag"]
                                },
                                "$$content",
                                false
                            ]
                        }
                    }
                },
                [false]
            ]
        }
    }
}, {
    $unwind: "$content_flag"
}, {
    $redact: {
        $cond: [{
                $or: [{
                    $eq: ["$name", "text_content"]
                }, {
                    $not: "$name"
                }, {
                    $eq: ["$name", "content_flag"]
                }, {
                    $and: [{
                        $eq: ["$name", "text_note"]
                    }, {
                        $eq: ["$$ROOT.content_flag.value", "true"]
                    }]
                }, {
                    $and: [{
                        $eq: ["$name", "text_description"]
                    }, {
                        $eq: ["$$ROOT.content_flag.value", "false"]
                    }]
                }]
            },
            "$$DESCEND",
            "$$PRUNE"
        ]
    }
}, {
    $project: {
        _id: 1,
        contentId: 1,
        contentMetaData: 1
    }
});
Sign up to request clarification or add additional context in comments.

5 Comments

Thank you for your response; I appreciate your efforts. I tried your code, but I got an exception: invalid operator '$slice' in the shell. The use of $slice within the $project is not supported in Mongo 2.6. Kindly refer : docs.mongodb.com/v2.6/reference/operator/projection/slice . Moreover, I would suggest not using $slice, because we shouldn't assume the array length; it may vary in future. If you could just stick to the field names, that would be much helpful.
As per the updated answer, I'm able to get the desired text_content. But the rest of the fields present within contentMetadata is not getting updated. I found that the $setUnion is only meant for merging two arrays. Refer : docs.mongodb.com/v2.6/reference/operator/aggregation/setUnion/… . Since content_flag is just an object, this approach isn't working.
Looks like wrapping obj as [] only works from 3.2 version in project stage. Seems like it [false] is working in setDifference. Can you replace ["$content_flag"] with {"$setDifference": [["$content_flag"], [false]]} and see if it works ?
No, that didn't work as well. Instead of printing the entire object, it is printing "$content_flag".
Added an alternative.

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.