In Gremlin, valueMap() is one of the most commonly used steps for projecting element properties into a structured form. It is frequently used when inspecting vertices and edges, debugging traversals, or shaping query results for downstream consumers such as APIs, analytics pipelines, or visualizations.
Historically, controlling what valueMap() returned especially when including element identifiers and labels required using constructs like valueMap(true). While functional, this approach offered limited flexibility and made it difficult to precisely tailor the output format as traversal requirements evolved.
Starting with Apache TinkerPop 3.4, a more expressive and extensible mechanism was introduced through the with() step and the WithOptions qualifiers. This enhancement allows developers to explicitly control whether tokens such as element IDs and labels are included, select specific tokens independently, and fine-tune how property values are represented in the result set.
In addition, TinkerPop 3.4 simplifies a long-standing pain point with valueMap() where, property values being returned as lists even when they are single-valued. By combining valueMap() with step modulators like by(unfold()), traversals can now produce cleaner, more natural result structures without requiring complex post-processing steps.
This post explores how to use WithOptions with valueMap() to:
· Include or exclude element tokens such as IDs and labels
· Select only specific tokens when needed
· Control which properties are returned
· Simplify result structures by unwrapping single-value properties
By the end, you’ll have a clear understanding of the modern, recommended patterns for shaping valueMap() output in Apache TinkerPop, and how to write traversals that are both more expressive and more maintainable going forward.
1. E-Commerce Domain Model Overview
To demonstrate the different ways valueMap() can be controlled using WithOptions, we will use a simple e-commerce domain model built using an in-memory TinkerGraph. This model is intentionally small but expressive enough to showcase real-world traversal patterns.
At a high level, the graph represents:
· Customers placing orders
· Orders containing products
· Products belonging to categories
· Orders being delivered to cities
This gives us multiple vertex labels, relationships, and property types to work with and ideal for exploring how valueMap() behaves across different scenarios.
Vertex Types
· City: Represents delivery locations
· Category: Represents product categories
· Product: Represents items that can be purchased
· Customer: Represents users of the platform
· Order: Represents purchase transactions
Each vertex label has a small and focused set of properties, which makes it easier to see how valueMap() returns property data and how WithOptions affects the output.
Edge Types and Relationships
· belongsTo: Connects a product to its category
· placed: Connects a customer to an order
· contains: Connects an order to one or more product vertices. An order may contain multiple products
· deliveredTo: Connects an order to a city, represents the delivery destination for the order
Gremlin Code to build the Graph
graph = TinkerGraph.open() g = graph.traversal() blr = g.addV('city').property('name', 'Bengaluru').next() hyd = g.addV('city').property('name', 'Hyderabad').next() chn = g.addV('city').property('name', 'Chennai').next() pne = g.addV('city').property('name', 'Pune').next() catElectronics = g.addV('category').property('name', 'Electronics').next() catBooks = g.addV('category').property('name', 'Books').next() catClothing = g.addV('category').property('name', 'Clothing').next() p1 = g.addV('product').property('sku', 'P1001').property('name', 'Laptop').next() p2 = g.addV('product').property('sku', 'P1002').property('name', 'Smartphone').next() p3 = g.addV('product').property('sku', 'P2001').property('name', 'Java Programming Book').next() p4 = g.addV('product').property('sku', 'P3001').property('name', 'T-Shirt').next() g.V(p1).addE('belongsTo').to(catElectronics).next() g.V(p2).addE('belongsTo').to(catElectronics).next() g.V(p3).addE('belongsTo').to(catBooks).next() g.V(p4).addE('belongsTo').to(catClothing).next() c1 = g.addV('customer').property('customerId', 'C101').property('name', 'Amit Sharma').next() c2 = g.addV('customer').property('customerId', 'C102').property('name', 'Priya Iyer').next() c3 = g.addV('customer').property('customerId', 'C103').property('name', 'Rohit Verma').next() o1 = g.addV('order').property('orderId', 'O9001').next() o2 = g.addV('order').property('orderId', 'O9002').next() o3 = g.addV('order').property('orderId', 'O9003').next() o4 = g.addV('order').property('orderId', 'O9004').next() o5 = g.addV('order').property('orderId', 'O9005').next() g.V(c1).addE('placed').to(o1).next() g.V(c1).addE('placed').to(o2).next() g.V(c2).addE('placed').to(o3).next() g.V(c3).addE('placed').to(o4).next() g.V(c3).addE('placed').to(o5).next() g.V(o1).addE('contains').to(p1).next() g.V(o1).addE('contains').to(p3).next() g.V(o2).addE('contains').to(p2).next() g.V(o3).addE('contains').to(p4).next() g.V(o4).addE('contains').to(p1).next() g.V(o4).addE('contains').to(p4).next() g.V(o5).addE('contains').to(p3).next() g.V(o1).addE('deliveredTo').to(blr).next() g.V(o2).addE('deliveredTo').to(blr).next() g.V(o3).addE('deliveredTo').to(hyd).next() g.V(o4).addE('deliveredTo').to(chn).next() g.V(o5).addE('deliveredTo').to(pne).next()
gremlin> g.V().valueMap(true) ==>[id:0,label:city,name:[Bengaluru]] ==>[id:33,label:customer,customerId:[C102],name:[Priya Iyer]] ==>[id:2,label:city,name:[Hyderabad]] ==>[id:4,label:city,name:[Chennai]] ==>[id:36,label:customer,customerId:[C103],name:[Rohit Verma]] ==>[id:6,label:city,name:[Pune]] ==>[id:39,label:order,orderId:[O9001]] ==>[id:8,label:category,name:[Electronics]] ==>[id:41,label:order,orderId:[O9002]] ==>[id:10,label:category,name:[Books]] ==>[id:43,label:order,orderId:[O9003]] ==>[id:12,label:category,name:[Clothing]] ==>[id:45,label:order,orderId:[O9004]] ==>[id:14,label:product,name:[Laptop],sku:[P1001]] ==>[id:47,label:order,orderId:[O9005]] ==>[id:17,label:product,name:[Smartphone],sku:[P1002]] ==>[id:20,label:product,name:[Java Programming Book],sku:[P2001]] ==>[id:23,label:product,name:[T-Shirt],sku:[P3001]] ==>[id:30,label:customer,customerId:[C101],name:[Amit Sharma]] gremlin> gremlin> gremlin> g.E().valueMap(true) ==>[id:64,label:deliveredTo] ==>[id:65,label:deliveredTo] ==>[id:49,label:placed] ==>[id:50,label:placed] ==>[id:51,label:placed] ==>[id:52,label:placed] ==>[id:53,label:placed] ==>[id:54,label:contains] ==>[id:55,label:contains] ==>[id:56,label:contains] ==>[id:57,label:contains] ==>[id:26,label:belongsTo] ==>[id:58,label:contains] ==>[id:27,label:belongsTo] ==>[id:59,label:contains] ==>[id:28,label:belongsTo] ==>[id:60,label:contains] ==>[id:29,label:belongsTo] ==>[id:61,label:deliveredTo] ==>[id:62,label:deliveredTo] ==>[id:63,label:deliveredTo]
2. Introducting WithOptions
WithOptions is a class provided by Apache TinkerPop to give developers precise control over what tokens (like IDs, labels, keys, etc.) are included when using traversal steps such as valueMap() or other steps that support the .with(...) modulator. Rather than relying on older patterns like valueMap(true), WithOptions makes it explicit and flexible to shape the results of your projections.
Under the hood, WithOptions defines a set of static tokens that can be passed into the .with() method of a Gremlin traversal. These tokens affect how maps (e.g., those produced by valueMap()) are constructed and what meta-information they include.
Here are the core WithOptions tokens you’ll commonly use with valueMap().
|
Token |
What it does? |
|
WithOptions.tokens |
Indicates that token values like ID and label should be considered in the value map. |
|
WithOptions.ids |
If used, includes the element ids in the map output. |
|
WithOptions.labels |
If used, includes the element label in the map. |
|
WithOptions.keys |
For property maps, includes the property keys. |
|
WithOptions.values |
For property maps, includes the property values. |
|
WithOptions.all |
Includes all possible tokens (IDs, labels, keys, values) in the result. |
|
WithOptions.none |
Includes no tokens at all. |
|
WithOptions.list and WithOptions.map |
Control how map entries are indexed (e.g., 2-item lists vs. map representation). |
WithOptions.tokens
Enables inclusion of element tokens (like id and label) in the valueMap() output.
g.V(). valueMap(). with(WithOptions.tokens)
gremlin> g.V(). ......1> valueMap(). ......2> with(WithOptions.tokens) ==>[id:0,label:city,name:[Bengaluru]] ==>[id:33,label:customer,customerId:[C102],name:[Priya Iyer]] ==>[id:2,label:city,name:[Hyderabad]] ==>[id:4,label:city,name:[Chennai]] ==>[id:36,label:customer,customerId:[C103],name:[Rohit Verma]] ==>[id:6,label:city,name:[Pune]] ==>[id:39,label:order,orderId:[O9001]] ==>[id:8,label:category,name:[Electronics]] ==>[id:41,label:order,orderId:[O9002]] ==>[id:10,label:category,name:[Books]] ==>[id:43,label:order,orderId:[O9003]] ==>[id:12,label:category,name:[Clothing]] ==>[id:45,label:order,orderId:[O9004]] ==>[id:14,label:product,name:[Laptop],sku:[P1001]] ==>[id:47,label:order,orderId:[O9005]] ==>[id:17,label:product,name:[Smartphone],sku:[P1002]] ==>[id:20,label:product,name:[Java Programming Book],sku:[P2001]] ==>[id:23,label:product,name:[T-Shirt],sku:[P3001]] ==>[id:30,label:customer,customerId:[C101],name:[Amit Sharma]]
By default, valueMap() returns only properties, adding WithOptions.tokens tells Gremlin that include element-level metadata (ID + label) in the result map.
WithOptions.ids
Includes only the element ID, not the label. Must be used together with WithOptions.tokens.
g.V(). hasLabel('product'). valueMap('name'). with(WithOptions.tokens)
Above snippet print id, label and name properties of vertices.
gremlin> g.V(). ......1> hasLabel('product'). ......2> valueMap('name'). ......3> with(WithOptions.tokens, WithOptions.ids) ==>[id:14,name:[Laptop]] ==>[id:17,name:[Smartphone]] ==>[id:20,name:[Java Programming Book]] ==>[id:23,name:[T-Shirt]]
Let's add 'WithOptions.ids' to it.
g.V(). hasLabel('product'). valueMap('name'). with(WithOptions.tokens, WithOptions.ids)
gremlin> g.V(). ......1> hasLabel('product'). ......2> valueMap('name'). ......3> with(WithOptions.tokens) ==>[id:14,label:product,name:[Laptop]] ==>[id:17,label:product,name:[Smartphone]] ==>[id:20,label:product,name:[Java Programming Book]] ==>[id:23,label:product,name:[T-Shirt]]
WithOptions.labels
Includes only the element label, not the ID, it is used with WithOptions.tokens
g.V(). hasLabel('product'). valueMap(). with(WithOptions.tokens, WithOptions.labels)
gremlin> g.V(). ......1> hasLabel('product'). ......2> valueMap(). ......3> with(WithOptions.tokens, WithOptions.labels) ==>[label:product,name:[Laptop],sku:[P1001]] ==>[label:product,name:[Smartphone],sku:[P1002]] ==>[label:product,name:[Java Programming Book],sku:[P2001]] ==>[label:product,name:[T-Shirt],sku:[P3001]]
WithOptions.all
Includes all possible tokens (IDs, labels, keys, values). It is essentially a shorthand for including everything available.
g.V(). hasLabel('product'). valueMap(). with(WithOptions.tokens, WithOptions.all)
gremlin> g.V(). ......1> hasLabel('product'). ......2> valueMap(). ......3> with(WithOptions.tokens, WithOptions.all) ==>[id:14,label:product,name:[Laptop],sku:[P1001]] ==>[id:17,label:product,name:[Smartphone],sku:[P1002]] ==>[id:20,label:product,name:[Java Programming Book],sku:[P2001]] ==>[id:23,label:product,name:[T-Shirt],sku:[P3001]]
WithOptions.none
g.V(). hasLabel('product'). valueMap(). with(WithOptions.tokens, WithOptions.none)
gremlin> g.V(). ......1> hasLabel('product'). ......2> valueMap(). ......3> with(WithOptions.tokens, WithOptions.none) ==>[name:[Laptop],sku:[P1001]] ==>[name:[Smartphone],sku:[P1002]] ==>[name:[Java Programming Book],sku:[P2001]] ==>[name:[T-Shirt],sku:[P3001]]
Previous Next Home
No comments:
Post a Comment