@PropertyBag Annotation
Maps an open Map<String, *> field to a set of flat, prefixed node properties — round-tripped on
save and load. Unlike @JsonPacked (one opaque JSON string), each entry becomes a real property, so
it’s visible, filterable, and indexable. Mirrors Spring Data Neo4j’s @CompositeProperty (available
as an alias).
@NodeFragment(labels = ["Proposition"])data class PropositionNode( @NodeId val id: String, val text: String, @PropertyBag val metadata: Map<String, Any?> = emptyMap(), // -> metadata.<key> properties)metadata = {"source": "wiki", "score": 3} persists as metadata.source = "wiki",
metadata.score = 3 alongside id/text. Use prefix to decouple the graph namespace from the
field name and delimiter to change the separator; a fragment may carry several bags.
- Values must be storable Neo4j primitives or homogeneous arrays (String, Number, Boolean,
temporal, or arrays/lists thereof) — a nested map/object throws an
IllegalArgumentExceptionnaming the key. - Stale keys (removing an entry then saving) are removed under
NullPolicy.CLEAR: the keys the node holds are read, and those the map no longer has are removed. The defaultIGNOREis a merge-patch and leaves them. - Read asymmetry:
Map<String, Any?>reads back driver-mapped types (anIntwritten returns asLong). - Filter by key in the type-safe DSL — composes on the load path and inside
loadNearest/loadMatching:
loadAll<PropositionView> { where { proposition.metadata.key("source") eq "wiki" } }// -> WHERE proposition.`metadata.source` = $pFor a runtime key (not known at compile time), use the dynamic
property(path) / field(key) predicates instead of .key(...).
Works across Neo4j, FalkorDB, and Memgraph.
A flat bag has no prefix: each entry is stored under its bare key, and the bag reads back every property that no declared field and no other bag accounts for. It fits a node whose properties are open-ended and were never namespaced. A fragment may have one, and an entry whose key is a declared field’s property is rejected at save.
@NodeFragment(labels = ["Thing"])data class ThingNode( @NodeId val id: String, val name: String, @PropertyBag(flat = true) val properties: Map<String, Any?> = emptyMap(), // -> <key> properties)
