| name | writing-hashql-jexpr |
| description | HashQL J-Expr syntax for writing queries. Use when writing J-Expr code, using #literal/#struct/#list constructs, understanding function call syntax, or working with HashQL query files (.jsonc). |
| license | AGPL-3.0 |
| metadata | {"triggers":{"type":"domain","enforcement":"suggest","priority":"high","keywords":["J-Expr","jexpr","hashql query","hashql syntax","#literal","#struct","#list","#tuple"],"intent-patterns":["\\b(write|read|create|parse)\\b.*?\\b(j-?expr|hashql)\\b","\\b(hashql|jexpr)\\b.*?\\b(query|syntax|expression)\\b"]}} |
Writing HashQL J-Expr
J-Expr is a JSON-based expression syntax for HashQL. It represents typed expressions using JSON primitives.
Expression Types
J-Expr has three expression types:
| JSON Type | J-Expr Meaning |
|---|
| String | Path/identifier/symbol |
| Array | Function call |
| Object | Data constructor (with # keys) |
Paths (Strings)
Strings are parsed as paths or identifiers:
"x"
"vertex.id.entity_id"
"::core::types::String"
"::graph::head::entities"
Function Calls (Arrays)
Arrays represent function calls: [function, arg1, arg2, ...]
["add", {"#literal": 1}, {"#literal": 2}]
["::graph::head::entities", ["::graph::tmp::decision_time_now"]]
["greet", {":name": {"#literal": "Alice"}}]
["func", ":name"]
Data Constructors (Objects with # Keys)
Objects with special # keys construct data:
| Key | Purpose | Example |
|---|
#literal | Primitive values | {"#literal": 42} |
#struct | Named fields | {"#struct": {"x": ...}} |
#list | Variable-size ordered | {"#list": [...]} |
#tuple | Fixed-size ordered | {"#tuple": [...]} |
#dict | Key-value map | {"#dict": {"k": ...}} |
#type | Type annotation | Used with other keys |
Literals
{"#literal": 42}
{"#literal": "hello"}
{"#literal": true}
{"#literal": null}
{"#literal": 3.14, "#type": "Float"}
Struct
{"#struct": {"name": {"#literal": "Alice"}, "age": {"#literal": 30}}}
{"#struct": {"x": {"#literal": 1}}, "#type": "Point"}
List and Tuple
{"#list": [{"#literal": 1}, {"#literal": 2}]}
{"#tuple": [{"#literal": 1}, {"#literal": "text"}]}
Dict
{"#dict": {"key": {"#literal": "value"}}}
Common Patterns
Let Binding
["let", "varName", {"#literal": 10}, ["add", "varName", {"#literal": 5}]]
Function Definition
["fn", {"#tuple": []}, {"#struct": {"vertex": "_"}}, "_", body_expr]
Conditionals
["if", condition_expr, then_expr, else_expr]
Comparison
["==", "left", "right"]
[">", {"#literal": 5}, {"#literal": 3}]
Do
- Use
#literal for all primitive values (numbers, strings, booleans, null)
- Use
:: prefix for namespaced paths
- Use
: prefix for labeled arguments
- Combine
#type with other constructors for type annotations
Don't
- Don't use bare JSON numbers/booleans - wrap in
{"#literal": ...}
- Don't confuse
#list (variable-size) with #tuple (fixed-size)
- Don't use
# prefix for labeled arguments (use :)
- Don't nest
# keys incorrectly - each object should have one primary # key
Examples
Entity query:
["::graph::head::entities", ["::graph::tmp::decision_time_now"]]
Filtering with comparison:
["filter", "entities",
["fn", {"#tuple": []}, {"#struct": {"entity": "_"}}, "_",
["==", "entity.draft_id", {"#literal": null}]]]
Struct with type:
{"#struct": {"value": {"#literal": 100}}, "#type": "Amount"}
References
- Syntax Reference - Paths, function calls, operators
- Special Forms - Language constructs (
if, let, fn, type, use, etc.)
- Data Constructors - Typed data (
#literal, #struct, #tuple, #list, #dict, #type)
- Type DSL - Embedded type annotation syntax
- Parser:
libs/@local/hashql/syntax-jexpr/src/parser/
- Object forms:
libs/@local/hashql/syntax-jexpr/src/parser/object/
- Type DSL:
libs/@local/hashql/syntax-jexpr/src/parser/string/type.rs