I need to check every single field type in every document on a given collection.
While I can write the individual $type commands for each field and match for each document separately, I am wondering if there is a more elegant and efficient way to just project all the field types.
Try this :
db.collection.aggregate([{
$addFields: {
types: {
$arrayToObject: {
$map:
{
input: { $objectToArray: "$$ROOT" },
as: "each",
in: { k: '$$each.k', v: { $type: '$$each.v' } }
}
}}
}
}])
Collection Data :
/* 1 */
{
"_id" : ObjectId("5e065992400289966eefb9a8"),
"username" : "myName",
"blog" : "myBlog",
"details" : "myBlogDetails",
"Object" : {
"inOneObject" : true
}
}
/* 2 */
{
"_id" : ObjectId("5e0659ae400289966eefbc3a"),
"username" : "othersName",
"blog" : "not a blog",
"details" : "useless"
}
Result :
/* 1 */
{
"_id" : ObjectId("5e065992400289966eefb9a8"),
"username" : "myName",
"blog" : "myBlog",
"details" : "myBlogDetails",
"Object" : {
"inObject" : true
},
"types" : {
"_id" : "objectId",
"username" : "string",
"blog" : "string",
"details" : "string",
"Object" : "object"
}
}
/* 2 */
{
"_id" : ObjectId("5e0659ae400289966eefbc3a"),
"username" : "othersName",
"blog" : "not a blog",
"details" : "useless",
"types" : {
"_id" : "objectId",
"username" : "string",
"blog" : "string",
"details" : "string"
}
}
Note : This given query would work only for top level fields in the document, it won't get you type of inObject from below field ::
"Object" : {
"inOneObject" : true
}
You'll need to enumerate all of the fields and handle each one individually. You can do this with a combination of $objectToArray and $unwind for the purposes of enumeration, and then $push and $arrayToObject for grouping everything back together:
db.collection.aggregate([
// Convert the root document into an array.
{$project: {
fields: {
$objectToArray: "$$ROOT"
}
}},
// Turn each array element into a separate document representing a field-value pair of the root document.
{$unwind: "$fields"},
// Apply the $type projection to the value portion of the field-value pair.
{$project: {
fields: {
k: "$fields.k",
v: {$type: "$fields.v"}
}
}},
// Grab the first instance of each unique field name.
{$group: {
_id: "$fields.k",
fields: {$first: "$fields"}
}},
// Take the unique field instances and recollect them all into an array.
{$group: {
_id: null,
fields: {$push: "$fields"}
}},
// Convert our array back into an object.
{$project: {
fields: {$arrayToObject: "$fields"}
}},
// Replace the root document with our nested "fields" sub-document.
{$replaceRoot: {
newRoot: "$fields"
}}
])
A small caveat: if the field may contain multiple types, e.g. "string" and "null", then this solution won't account for this case. To fix that problem, you'll want to modify the $group stages to utilize an $addToSet operator to collect the unique $type instances for each key before $pushing them into the fields array.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With