Map expressions

Map expressions allow you to manipulate and query MAP values in Cypher®.

The keys() function can be used to return a LIST of all the keys in a MAP.

Example graph

The following graph is used for the examples below:

Example graph featuring connections between person and movie nodes

To recreate the graph, run the following query against an empty Neo4j database:

CREATE (keanu:Person {name: 'Keanu Reeves', nationality: 'Canadian'}),
       (carrieAnne:Person {name: 'Carrie-Anne Moss'}),
       (theMatrixRevolutions:Movie {title: 'The Matrix Revolutions', released: 2003}),
       (theMatrixReloaded:Movie {title: 'The Matrix Reloaded', released: 2003}),
       (theMatrix:Movie {title: 'The Matrix', released: 1999}),
       (theDevilsAdvocate:Movie {title: 'The Devils Advocate', released: 1997}),
       (theMatrixResurrections:Movie {title: 'The Matrix Resurrections', released: 2021}),

       (keanu)-[:ACTED_IN]->(theMatrix),
       (keanu)-[:ACTED_IN]->(theMatrixRevolutions),
       (keanu)-[:ACTED_IN]->(theMatrixReloaded),
       (keanu)-[:ACTED_IN]->(theMatrixResurrections),
       (keanu)-[:ACTED_IN]->(theDevilsAdvocate),

       (carrieAnne)-[:ACTED_IN]->(theMatrix),
       (carrieAnne)-[:ACTED_IN]->(theMatrixRevolutions),
       (carrieAnne)-[:ACTED_IN]->(theMatrixReloaded),
       (carrieAnne)-[:ACTED_IN]->(theMatrixResurrections)

Map operators

Cypher contains two map operators:

  • Statically access a value by key: dot operator (.).

  • Dynamically access a value by key: subscript operator ([]).

Statically access a MAP value

MAP values can be accessed statically by specifying a key after the . operator.

Static MAP value access
WITH {a: 10, b: 20, c: 30} AS map
RETURN map.a AS firstValue,
       map.c AS lastValue
Result
firstValue lastValue

10

30

Rows: 1

To statically access a value in a nested MAP, use chained . operators. Each . operator traverses one level deeper into the nested structure.

Static access of a nested MAP value
WITH {a: 10, b: 20, c: 30, innerMap: {x: 100, y: 200, z: 300}} AS map
RETURN map.a AS firstOuterValue,
       map.innerMap.y AS secondInnerValue
Result
firstOuterValue secondInnerValue

10

200

Rows: 1

Node and relationship properties are stored as key-value pairs in a MAP. Properties can, therefore, be statically accessed using the . operator.

Statically access a node property
MATCH (p:Person)
RETURN p AS nodePropertyMaps,
       p.name AS accessedMapValue
Result
nodePropertyMaps accessedMapValue

(:Person {nationality: "Canadian", name: "Keanu Reeves"})

"Keanu Reeves"

(:Person {name: "Carrie-Anne Moss"})

"Carrie-Anne Moss"

Rows: 2

Dynamically access a MAP value

To dynamically access a MAP value, use the subscript operator, []. The key can be provided by a variable or a parameter.

Dynamically access a MAP value using a variable
WITH {a: 10, b: 20, c: 30} AS map,
     'a' AS dynamicKey
RETURN map[dynamicKey] AS dynamicValue
Result
dynamicValue

10

Rows: 1

Parameters
{
  "dynamicKey" : "a"
}
Dynamically access a MAP value using a parameter
WITH {a: 10, b: 20, c: 30} AS map
RETURN map[$dynamicKey] AS dynamicValue
Result
dynamicValue

10

Rows: 1

Dynamically access a nested MAP value
WITH {a: 10, b: 20, c: 30, innerMap: {x: 100, y: 200, z: 300}} AS map,
     'z' AS dynamicInnerKey
RETURN map.innerMap[dynamicInnerKey] AS dynamicInnerValue
Result
dynamicInnerValue

300

Rows: 1

It is possible to dynamically access multiple values from a MAP by using list comprehension. This query iterates over the LIST dynamicKeys and retrieves their corresponding values from the map.

Dynamic MAP value access and list comprehension
WITH {a: 10, b: 20, c: 30} AS map,
     ['a', 'c'] AS dynamicKeys
RETURN [key IN dynamicKeys | map[key]] AS dynamicValue
Result
dynamicValue

[10, 30]

Rows: 1

Dynamically access a node property value
MATCH (p:Person)
LET dynamicValue = 'name'
RETURN p[dynamicValue] AS names
Result
names

"Keanu Reeves"

"Carrie-Anne Moss"

Rows: 2

Referencing a non-existent key in the MAP with the [] operator will return null.

Dynamically referencing a non-existent MAP key
WITH {a: 10, b: 20, c: 30} AS map,
     'z' AS dynamicKey
RETURN map[dynamicKey] AS dynamicValue
Result
dynamicValue

null

Rows: 1

Map projection

Cypher supports map projections, which allow the creation of MAP values from nodes, relationships, and other MAP values.

A map projection begins with the variable bound to the MAP to be projected from, and contains a body of comma-separated MAP keys, enclosed by curly braces, {}. Each map element specifies a key-value pair that is included in the resulting map projection.

Syntax
mapVariable {mapElement [, ...n]}

mapElement can be one of the following:

  • An key selector that references a key in a MAP and pulls the corresponding value.

  • A literal entry that uses a custom key-value pair where the key is specified and the value is defined using an expression.

  • A variable selector that projects the value of a variable, using the variable name as the key, and the value it references as the value.

  • An all-map projection that projects all key-value pairs from mapVariable.

The key names in a map must be of type STRING.
If mapVariable is null, the projection evaluates to null.
Example 1. Map projection with a key selector

Map projections with a key selector are useful when you need to extract specific key-value pairs from a MAP.

Basic example
WITH {a: 10, b: 20, c: 30} AS map
RETURN map{.a, .c} AS projectedMap
Result
projectedMap

{a: 10, c: 30}

Rows: 1

In the below query, a map projection with property selectors is used to collect only the title and release year of each movie.

Map projection with a key selector
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
RETURN p.name AS actor, collect(m{.title, .released}) AS movies
Result
actor movies

"Keanu Reeves"

[{title: "The Matrix Resurrections", released: 2021}, {title: "The Matrix Revolutions", released: 2003}, {title: "The Matrix Reloaded", released: 2003}, {title: "The Matrix", released: 1999}, {title: "The Devils Advocate", released: 1997}]

"Carrie-Anne Moss"

[{title: "The Matrix Resurrections", released: 2021}, {title: "The Matrix Revolutions", released: 2003}, {title: "The Matrix Reloaded", released: 2003}, {title: "The Matrix", released: 1999}]

Rows: 2

Example 2. Map projection with a literal entry

Map projections with a literal entry are useful when you want to add custom values to a projected MAP value without modifying the original data structure.

Basic example
WITH {a: 10, b: 20, c: 30} AS map
RETURN map{a: map.a, valueSum: map.a + map.b + map.c} AS projectedMap
Result
projectedMap

{a: 10, valueSum: 60}

Rows: 1

This query uses a map projection with a literal entry, where the size(movies) expression calculates the total number of movies Keanu Reeves has acted in.

Map projection with a literal entry
MATCH (keanu:Person {name: 'Keanu Reeves'})-[:ACTED_IN]->(movie:Movie)
WITH keanu, collect(movie) AS movies
RETURN keanu {.name, totalMovies: size(movies)} AS keanuDetails
Result
keanuDetails

{name: "Keanu Reeves", totalMovies: 5}

Rows: 1

Example 3. Map projection with a variable selector

Map projection with a variable selector is useful when you want to project values based on a variable name.

Basic example
MATCH (keanu:Person {name: 'Keanu Reeves'})
LET dob = date('1964-09-02'), birthPlace =  'Beirut, Lebanon'
RETURN keanu{.name, dob, birthPlace} AS projectedKeanu
Result
projectedKeanu

{name: "Keanu Reeves", birthPlace: "Beirut, Lebanon", dob: 1964-09-02}

Rows: 1

The below query finds all Person nodes in the graph that have one or more relationships with the type ACTED_IN connected to Movie nodes. It uses the count() function to count how many Movie nodes are connected to each Person node in this way, and uses a variable selector to project the value of the count.

Map projection with a variable selector
MATCH (actor:Person)-[:ACTED_IN]->(movie:Movie)
WITH actor, count(movie) AS totalMovies
RETURN actor{totalMovies, .name} AS nameAndMovies
Result
nameAndMovies

{name: "Keanu Reeves", totalMovies: 5}

{name: "Carrie-Anne Moss", totalMovies: 4}

Rows: 2

Example 4. Map projection with an all-map projection

Map projection with an all-map projection is useful when you want to project all key-value pairs from a MAP without explicitly listing them.

Basic example
WITH {a: 10, b: 20, c: 30} AS map
RETURN map{.*} AS projectedMap
Result
projectedMap

{a: 10, b: 20, c: 30}

Rows: 1

The below query returns all properties from the Keanu Reeves node. An all-map selector is used to project all the node properties.

Map projection with all-map selector
MATCH (keanu:Person {name: 'Keanu Reeves'})
RETURN keanu{.*} AS allKeanuProperties
Result
AllKeanuProperties

{nationality: "Canadian", name: "Keanu Reeves"}

Rows: 1

Map comprehension

Map comprehension is used to create new MAP values by iterating over the elements of an existing MAP or LIST, and transforming them into key-value pairs. The result is a new MAP where each entry is generated by the specified key and value expressions.

Map comprehension follows a similar syntax to list comprehension.

Syntax (map variant)
{key: value IN map [WHERE predicate] | keyExpression : valueExpression}

The iteration step (key: value IN map) ensures that each key, value pair in a map is accessed one by one, while the keyExpression and valueExpression optionally applies a transformation to each item, creating new MAP of key, value pairs containing the modified elements.

Syntax (list variant)
{item IN list [WHERE predicate] | keyExpression : valueExpression}

The iteration step (item IN list) ensures that each element of a list is accessed one by one, while the keyExpression and valueExpression apply a transformation to each item, creating new MAP of key-value pairs containing the modified elements.

Considerations

Variables introduced inside a map comprehension (such as key, value, or item) shadow any outer variables with the same name and are not accessible outside the map comprehension.

As MAP values cannot have duplicate keys, map comprehension follows a "last update wins" logic. If multiple elements result in the same key, the value from the last element processed will overwrite any previous value for that key. However, the order in which elements are processed is not guaranteed, so which value is treated as "last" should not be relied upon.

Unlike list comprehension, the keyExpression and valueExpression (and the | separator) are not optional.

The keyExpression must evaluate to a STRING value or an error will be thrown.

Simple map comprehension
WITH {a: 1, b: 2, c: 3} AS map
RETURN {k: v IN map | k : v * 10} AS result
Result
result

{a: 10, b: 20, c: 30}

Rows: 1

Filter a MAP
WITH {a: 1, b: 2, c: 3, d: null, e: null} AS map
RETURN {k: v IN map WHERE v IS NOT NULL | k : v} AS result
Result
result

{a: 1, b: 2, c: 3}

Rows: 1

Last write wins
WITH {a: 1, b: 2, c: 3} AS map
RETURN {k: v IN map | 'a' : v * 10} AS result
Result
result

{a: 30}

Rows: 1

The value in the result comes from whichever MAP entry is processed last (in this case, {a:30}). That entry is not guaranteed, however, because Cypher does not define an order in which MAP entries are processed.

Nested map comprehension
WITH {a: {aa: 1, ab: 2}, b: {ba: 1, bb: 2}} AS nestedMap
RETURN {k: v IN nestedMap | k : {innerK: innerV IN v | innerK : innerV * 10}} AS result
Result
result

{a: {aa: 10, ab: 20}, b: {ba: 10, bb: 20}}

Rows: 1

Simple map comprehension (list variant)
WITH ['a', 'b', 'c'] AS list
RETURN {item IN list | item : item + '!' } AS result
Result
result

{a: "a!", b: "b!", c: "c!"}

Rows: 1

Filter a LIST and transform into a MAP
WITH range(0, 10) AS list
RETURN {i IN list WHERE i % 2 = 0 | toString(i) : i * 10} AS result
Result
result

{"0": 0, "2": 20, "4": 40, "6": 60, "8": 80, "10": 100}

Rows: 1