If you test $isArray: [42]
in MongoDB, it returns false and that's the right answer. If you test $isArray: [42,42,42]
you get an error telling you that Expression $isArray takes exactly 1 arguments
, but 3 were passed in
. Documentation about $isArray explains that the input is interpreted as multiple arguments rather than a single argument that is an array:
Aggregation expressions accept a variable number of arguments. These arguments are normally passed as an array. However, when the argument is a single value, you can simplify your code by passing the argument directly without wrapping it in an array.
The documentation about the Expression Operator clarifies that when passing a single argument that is an array, you must use $literal
or wrap the array in an additional array that will be interpreted by the language:
Here are a few reproducible examples (also available here) to explain it.
Expression Operator with literals
I use a collection with one document to run my expressions in an aggregation pipeline:
db.dual.insertOne( { "dummy": "x" } )
{
acknowledged: true,
insertedId: ObjectId('687d09913b111f172ebaa8ba')
}
$isNumber
takes one argument and returns true if it is a number:
db.dual.aggregate([
{
$project: {
" is 42 a number?": {
$isNumber: [ 42 ] // one argument -> number
}, "_id": 0
}
}
])
[ { ' is 42 a number?': true } ]
The array that you see in $isNumber: [ 42 ]
is interpreted as a list of arguments for the expression operator. Text-based languages would use isNumber(42)
but MongoDB query language is structured into BSON to better integrate with application languages and be easily parsed by the drivers.
Trying to pass two arguments raises an error:
db.dual.aggregate([
{
$project: {
" is 42 a number?": {
$isNumber: [ 42 , 42 ] // two arguments -> error
}, "_id": 0
}
}
])
MongoServerError[Location16020]: Invalid $project :: caused by :: Expression $isNumber takes exactly 1 arguments. 2 were passed in.
For expression operators that take one argument, you can pass it as a value rather than as an array, but this is just a syntactic shortcut and doesn't change the data type of the argument:
db.dual.aggregate([
{
$project: {
" is 42 a number?": {
$isNumber: 42 // one argument -> number
}, "_id": 0
}
}
])
[ { ' is 42 a number?': true } ]
$isArray
takes a single argument and returns true if that argument is an array. However, in the examples above, the array syntax is used to structure the list of arguments rather than define a literal array data type:
db.dual.aggregate([
{
$project: {
" is 42 an array?": {
$isArray: [ 42 ] // one argument -> number
}, "_id": 0
}
}
])
[ { ' is 42 an array?': false } ]
db.dual.aggregate([
{
$project: {
" is 42 an array?": {
$isArray: [ 42 , 42 ] // two arguments -> error
}, "_id": 0
}
}
])
MongoServerError[Location16020]: Invalid $project :: caused by :: Expression $isArray takes exactly 1 arguments. 2 were passed in.
db.dual.aggregate([
{
$project: {
" is 42 an array?": {
$isArray: 42 // one argument -> number
}, "_id": 0
}
}
])
[ { ' is 42 an array?': false } ]
If you want to pass an array as an argument, you must nest the data array inside another array, which structures the list of arguments:
db.dual.aggregate([
{
$project: {
" is 42 an array?": {
$isArray: [ [ 42 ] ] // one argument -> array
}, "_id": 0
}
}
])
[ { ' is 42 an array?': true } ]
db.dual.aggregate([
{
$project: {
" is 42 a number?": {
$isNumber: [ [ 42 ] ] // one argument -> array
}, "_id": 0
}
}
])
[ { ' is 42 a number?': false } ]
Another possibility is using $literal
, which doesn't take a list of arguments, but rather avoids parsing an array as a list of arguments:
db.dual.aggregate([
{
$project: {
" is 42 an array?": {
$isArray: { $literal: [ 42 ] } // one argument -> array
}, "_id": 0
}
}
])
[ { ' is 42 an array?': true } ]
db.dual.aggregate([
{
$project: {
" is 42 a number?": {
$isNumber: { $literal: [ 42 ] } // one argument -> array
}, "_id": 0
}
}
])
[ { ' is 42 a number?': false } ]
This can be confusing. I wrote this while answering a question on Bluesky:
#MongoDB seriously, why? $isNumber: 5 β true $isArray: [] β false $isArrau: [[5]] β true
β Ivan βCLOVISβ Canet (@ivcanet.bsky.social) 2025-07-08T18:54:24.735Z
All languages exhibit certain idiosyncrasies where specific elements must be escaped to be interpreted literally rather than as language tokens. For instance, in PostgreSQL, a literal array must follow a specific syntax:
postgres=> SELECT pg_typeof(42) AS "is 42 a number?";
is 42 a number?
-----------------
integer
postgres=> SELECT pg_typeof(42, 42);
ERROR: function pg_typeof(integer, integer) does not exist
LINE 1: SELECT pg_typeof(42, 42);
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
postgres=> SELECT pg_typeof(ARRAY[42]);
pg_typeof
-----------
integer[]
postgres=> SELECT pg_typeof('{42}'::int[]);
pg_typeof
-----------
integer[]
main=> SELECT pg_typeof('{''42'',''42''}'::text[]);
pg_typeof
-----------
text[]
Additionally, in SQL, using double quotes serves to escape language elements, so that during parsing, they are interpreted as data or as part of the language syntax. In PostgreSQL the ambiguity is between text and array, in MongoDB it is between arguments and arrays.
You typically shouldn't encounter issues with MongoDB in common situations because expression operators are designed for expressions rather than literals. For instance, you don't need to call the database to know that 42
is a number and []
is an array. If this was generated by a framework, it likely uses $literal
for clarity. If it is coded for expressions, there's no ambiguity.
Expression Operator with expressions
I use a collection containing one document with a field that is an array:
db.arrays.insertOne( { "field": [42] } )
{
acknowledged: true,
insertedId: ObjectId('687d1c413e05815622d4b0c2')
}
I can pass the "$field"
expression argument to the operator and it remains an array:
db.arrays.aggregate([
{
$project: {
field: 1,
" is $field a number?": {
$isNumber: "$field" // one argument -> expression
},
" is $field an array?": {
$isArray: "$field" // one argument -> expression
},
"_id": 0
}
}
])
[
{
field: [ 42 ],
' is $field a number?': false,
' is $field an array?': true
}
]
In this case, using the one argument shortcut ("$field"
) or an array (["$field"]
) doesn't matter because there's a clear distinction between language elements and data.
In short, with expressions that return arrays when executed, there's no problem, and for array literal, use $literal
.
Top comments (0)