MongoDB $indexOfArray

In MongoDB, the $indexOfArray aggregation pipeline operator searches an array for an occurrence of a specified value and returns the array index of the first occurrence.

Syntax

The syntax goes like this:

{ $indexOfArray: [ <array expression>, <search expression>, <start>, <end> ] }

Where:

  • <array expression> is the array to search.
  • <search expression> is the value you want to find in the array.
  • <start> is an optional argument that specifies a starting point for which to search in the array. Can be any valid expression that resolves to a non-negative integral number.
  • <end> is an optional argument that specifies an ending index position for the search. Can be any valid expression that resolves to a non-negative integral number.

In MongoDB, arrays are zero-based, so the index count starts at zero (0).

If the specified value isn’t found, $indexOfArray returns -1.

If there are multiple instances of the specified value, just the first one is returned.

Example

Suppose we have a collection called products with the following documents:

{ "_id" : 1, "prod" : "Bat", "sizes" : [ "XS", "M", "L" ] }
{ "_id" : 2, "prod" : "Hat", "sizes" : [ "XS", "S", "L", "XL" ] }
{ "_id" : 3, "prod" : "Cap", "sizes" : [ "XXS", "XS", "M", "XL" ] }
{ "_id" : 4, "prod" : "Zap", "sizes" : [ 10, 12, 15 ] }
{ "_id" : 5, "prod" : "Tap", "sizes" : [ 15, 16, 20 ] }

Here’s an example of applying $indexOfArray to those documents:

db.products.aggregate(
   [
     { $match: { _id: { $in: [ 1, 2, 3, 4, 5 ] } } },
     {
       $project:
          {
            _id: 0,
            sizes: 1,
            result: { $indexOfArray: [ "$sizes", "XS" ] }
          }
     }
   ]
)

Result:

{ "sizes" : [ "XS", "M", "L" ], "result" : 0 }
{ "sizes" : [ "XS", "S", "L", "XL" ], "result" : 0 }
{ "sizes" : [ "XXS", "XS", "M", "XL" ], "result" : 1 }
{ "sizes" : [ 10, 12, 15 ], "result" : -1 }
{ "sizes" : [ 15, 16, 20 ], "result" : -1 }

In the first two documents, the search value was found at position 0 (arrays are zero-based).

In the third document, it was found at position 1. Notice that the search was for an exact match. It didn’t return position 0, even though the value at position 0 contains the search value (i.e. XXS contains XS).

The search value wasn’t found in the last two documents, and so -1 was returned.

Here’s another example, except this time we search for a numeric value:

db.products.aggregate(
   [
     { $match: { _id: { $in: [ 1, 2, 3, 4, 5 ] } } },
     {
       $project:
          {
            _id: 0,
            sizes: 1,
            result: { $indexOfArray: [ "$sizes", 15 ] }
          }
     }
   ]
)

Result:

{ "sizes" : [ "XS", "M", "L" ], "result" : -1 }
{ "sizes" : [ "XS", "S", "L", "XL" ], "result" : -1 }
{ "sizes" : [ "XXS", "XS", "M", "XL" ], "result" : -1 }
{ "sizes" : [ 10, 12, 15 ], "result" : 2 }
{ "sizes" : [ 15, 16, 20 ], "result" : 0 }

Specify a Starting Position

You can provide a third argument to specify a starting index position for the search.

Example:

db.products.aggregate(
   [
     { $match: { _id: { $in: [ 4, 5 ] } } },
     {
       $project:
          {
            _id: 0,
            sizes: 1,
            result: { $indexOfArray: [ "$sizes", 15, 1 ] }
          }
     }
   ]
)

Result:

{ "sizes" : [ 10, 12, 15 ], "result" : 2 }
{ "sizes" : [ 15, 16, 20 ], "result" : -1 }

In this case, the search expression wasn’t found in the second document (document 5). This is because we started the search at position 1, and although that document contains the search expression, it’s at position 0 (before the starting position for the search).

Specify an Ending Position

You can also provide a fourth argument to specify the ending index position for the search.

If you provide this argument, you also need to provide a starting position. Failing to do so will result in this argument being interpreted as the starting point.

Example:

db.products.aggregate(
   [
     { $match: { _id: { $in: [ 1, 2, 3 ] } } },
     {
       $project:
          {
            _id: 0,
            sizes: 1,
            result: { $indexOfArray: [ "$sizes", "XS", 0, 1 ] }
          }
     }
   ]
)

Result:

{ "sizes" : [ "XS", "M", "L" ], "result" : 0 }
{ "sizes" : [ "XS", "S", "L", "XL" ], "result" : 0 }
{ "sizes" : [ "XXS", "XS", "M", "XL" ], "result" : -1 }

The third document returned -1 which means the search expression wasn’t found.

Here’s what happens if we increment the ending index position by 1:

db.products.aggregate(
   [
     { $match: { _id: { $in: [ 1, 2, 3 ] } } },
     {
       $project:
          {
            _id: 0,
            sizes: 1,
            result: { $indexOfArray: [ "$sizes", "XS", 0, 2 ] }
          }
     }
   ]
)

Result:

{ "sizes" : [ "XS", "M", "L" ], "result" : 0 }
{ "sizes" : [ "XS", "S", "L", "XL" ], "result" : 0 }
{ "sizes" : [ "XXS", "XS", "M", "XL" ], "result" : 1 }

This time the value was included and its index position returned.

Empty Arrays

Searching an empty array returns -1.

db.products.aggregate(
   [
     { $match: { _id: { $in: [ 6 ] } } },
     {
       $project:
          {
            _id: 0,
            sizes: 1,
            result: { $indexOfArray: [ "$sizes", "XS" ] }
          }
     }
   ]
)

Result:

{ "sizes" : [ ], "result" : -1 }

Missing Fields

If the field is not in the document, $indexOfArray returns null.

Suppose we have the following document:

{ "_id" : 8, "prod" : "Map" }

Here’s what happens when we apply $indexOfArray:

db.products.aggregate(
   [
     { $match: { _id: { $in: [ 8 ] } } },
     {
       $project:
          {
            _id: 0,
            sizes: 1,
            result: { $indexOfArray: [ "$sizes", "XS" ] }
          }
     }
   ]
)

Result:

{ "result" : null }

Null Values

If the array expression is null (instead of an array), $indexOfArray returns null.

Suppose we have the following document:

{ "_id" : 7, "prod" : "Lap", "sizes" : null }

Here’s what happens when we apply $indexOfArray:

db.products.aggregate(
   [
     { $match: { _id: { $in: [ 7 ] } } },
     {
       $project:
          {
            _id: 0,
            sizes: 1,
            result: { $indexOfArray: [ "$sizes", "XS" ] }
          }
     }
   ]
)

Result:

{ "sizes" : null, "result" : null }

However, when the search expression is null, the result is -1, unless the array expression is also null or its field is missing:

db.products.aggregate(
   [
     { $match: { _id: { $in: [ 1, 2, 3, 4, 5, 6, 7, 8 ] } } },
     {
       $project:
          {
            _id: 0,
            sizes: 1,
            result: { $indexOfArray: [ "$sizes", null ] }
          }
     }
   ]
)

Result:

{ "sizes" : [ "XS", "M", "L" ], "result" : -1 }
{ "sizes" : [ "XS", "S", "L", "XL" ], "result" : -1 }
{ "sizes" : [ "XXS", "XS", "M", "XL" ], "result" : -1 }
{ "sizes" : [ 10, 12, 15 ], "result" : -1 }
{ "sizes" : [ 15, 16, 20 ], "result" : -1 }
{ "sizes" : [ ], "result" : -1 }
{ "sizes" : null, "result" : null }
{ "result" : null }

Wrong Data Type

If the array expression is the wrong data type, $indexOfArray returns an error.

Suppose we have the following document:

{ "_id" : 9, "prod" : "Box", "sizes" : "XXL" }

Here’s what happens when we apply $indexOfArray to that document:

db.products.aggregate(
   [
     { $match: { _id: { $in: [ 9 ] } } },
     {
       $project:
          {
            _id: 0,
            sizes: 1,
            result: { $indexOfArray: [ "$sizes", "XXL" ] }
          }
     }
   ]
)

Result:

uncaught exception: Error: command failed: {
	"ok" : 0,
	"errmsg" : "$indexOfArray requires an array as a first argument, found: string",
	"code" : 40090,
	"codeName" : "Location40090"
} : aggregate failed :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
doassert@src/mongo/shell/assert.js:18:14
_assertCommandWorked@src/mongo/shell/assert.js:639:17
assert.commandWorked@src/mongo/shell/assert.js:729:16
DB.prototype._runAggregate@src/mongo/shell/db.js:266:5
DBCollection.prototype.aggregate@src/mongo/shell/collection.js:1058:12
@(shell):1:1

As the error message states, $indexOfArray requires an array as a first argument.