I have the following structure for a collection in MongoDB
{
'_id': 45
'tags': [ 'tag 1', 'tag 3' ]
'active': true
'fields': [
{ 'name': 'common field 1', 'type': 'text', 'value': 'some text', ... },
{ 'name': 'common field 2', ... },
{ 'name': 'multivalued field 1',
'type': 'multifield',
'valueCount': 5,
'value': [
{ 'name': 'subfield1', ..., 'value': [1, 2, 3, 4, 5]},
{ 'name': 'subfield2', ..., 'value': ["one", "two", "three", "four", "five"]},
{ 'name': 'subfield3', ..., 'value': ["here", "there", "", "", ""]}
], ... }
]
}
and I am trying to implement projection in my API: for example, if the user requests
api/collection/?fields=id,fields{common field 2, multifield{subfield1}}
The result should be
{
'_id': 45
'fields': [
{ 'name': 'common field 1', 'type': 'text', 'value': 'some text', ... },
{ 'name': 'multivalued field 1',
'type': 'multifield',
'valueCount': 5,
'value': [
{ 'name': 'subfield1', ..., 'value': [1, 2, 3, 4, 5]},
], ... }
]
}
Since the 'fields' names are not actual keys, I cannot use mongo projection, say
db.collection.find({},{_id: 1, tags: 1, fields.'common field 1': 1})
So I must instead search within the array for the fields whose "name" property matches my projection parameter. I achieved that for the first level array with aggregation and $redact, as suggested in this answer https://stackoverflow.com/a/24032549/5418731
db.points.aggregate([
{ $match: {}},
{
$project: {_id :1, fields: 1}
},
{ $redact : {
$cond: {
if: { $or : [{ $not : "$name" }, { $eq: ["$name", "common field 1"] }]},
then: "$$DESCEND",
else: "$$PRUNE"
}
}}])
However, I cannot use $redact to select subfields from the inner arrays in multivalued fields. The $or parameters would have to be something like
[{ $not : "$name" }, { $eq: ["$name", "common field 1"] }, { $eq: ["$name", "subfield1"] }]
which means a first level field with the same name of the subfield specified would also pass.
After upgrading MongoDB to 3.2, I attempted the $filter solution in this answer https://stackoverflow.com/a/12241930/5418731, which also works fine for the first level array:
db.points.aggregate([
{$project: {
fields: {$filter: {
input: '$fields',
as: 'field',
cond: {$eq: ['$$field.name', 'multivalued field 1']}
}}
}}
])
but I couldn't find a way to use it "nested" and filter the second level array items. Adding {$eq: ['$$field.value.name', 'subfield1']}
doesn't work.
Last, I tried the $map solution presented here https://stackoverflow.com/a/24156418/5418731:
db.points.aggregate([
{ "$project": {
"_id": 1,
"fields": {
"$map": {
"input": "$fields",
"as": "f",
"in": {
"$ifNull": [
{
"name": "$multivalued field 1",
"type": "$multifield", //attempt to restrict search to fields with arrays as values
"value": {
"$map": {
"input": "$$f.value",
"as": "v",
"in": {
"$ifNull": [
{ "name": "$subfield1"},
false
]
}
}
}
},
false
]
}
}
}
}}
])
But this one won't work because the "value" property of each "fields" item is not necessarily an array, and when it isn't the whole query fails.
I'm about to give up and mask the results in JS. Is there a good solution for that with Mongo?