Data constructors are JSON objects with # prefixed keys that construct typed data values in J-Expr.
| Special Form | Purpose | Value Type |
|---|---|---|
#literal |
Primitive values | JSON primitive |
#struct |
Named fields (record) | JSON object |
#tuple |
Fixed-size ordered collection | JSON array |
#list |
Variable-size ordered collection | JSON array |
#dict |
Key-value map | JSON object or array |
#type |
Type annotation (modifier) | String (type DSL) |
Wraps JSON primitives (numbers, strings, booleans, null) as typed expressions.
Syntax:
Accepted primitives:
- Numbers: integers and floats
- Strings: quoted text
- Booleans:
true/false - Null:
null
Examples:
{"#literal": 42}
{"#literal": 3.14}
{"#literal": "hello world"}
{"#literal": true}
{"#literal": null}
{"#literal": 42, "#type": "Int64"}
{"#literal": "2024-01-01", "#type": "Date"}Important: Never use bare JSON primitives. Always wrap in #literal:
// ✗ Wrong - bare number
["add", 1, 2]
// ✓ Correct - wrapped literals
["add", {"#literal": 1}, {"#literal": 2}]Creates a record/struct with named fields. Keys are parsed as identifiers.
Syntax:
{"#struct": {<field>: <expr>, ...}}
{"#struct": {<field>: <expr>, ...}, "#type": "<type>"}Field names: Must be valid identifiers (parsed by parse_ident). Snake_case and camelCase are valid.
Examples:
// Simple struct
{"#struct": {"name": {"#literal": "Alice"}, "age": {"#literal": 30}}}
// Empty struct
{"#struct": {}}
// With type annotation
{"#struct": {"x": {"#literal": 10}, "y": {"#literal": 20}}, "#type": "Point"}
// Nested structs
{"#struct": {
"user": {"#struct": {"name": {"#literal": "Bob"}}},
"role": {"#literal": "admin"}
}}
// With function call values
{"#struct": {
"sum": ["add", {"#literal": 1}, {"#literal": 2}]
}}Invalid field names:
// ✗ Wrong - numeric prefix
{"#struct": {"123field": {"#literal": 1}}}
// ✗ Wrong - hyphenated
{"#struct": {"my-field": {"#literal": 1}}}Creates a fixed-size ordered collection. Each position has a distinct type.
Syntax:
{"#tuple": [<expr>, ...]}
{"#tuple": [<expr>, ...], "#type": "<type>"}Examples:
// Empty tuple (unit type)
{"#tuple": []}
// Pair
{"#tuple": [{"#literal": 1}, {"#literal": "text"}]}
// Triple
{"#tuple": [{"#literal": 1}, {"#literal": "a"}, {"#literal": true}]}
// With type annotation
{"#tuple": [{"#literal": 10}, {"#literal": "hello"}], "#type": "(Int, String)"}
// Nested tuples
{"#tuple": [
{"#tuple": [{"#literal": 1}, {"#literal": 2}]},
{"#literal": 3}
]}Use cases:
- Return multiple values of different types
- Function parameters
- Coordinates/pairs
- Type parameters (often empty
{"#tuple": []})
Creates a homogeneous, variable-size collection. All elements share the same type.
Syntax:
{"#list": [<expr>, ...]}
{"#list": [<expr>, ...], "#type": "<type>"}Examples:
// Empty list
{"#list": []}
// Numbers
{"#list": [{"#literal": 1}, {"#literal": 2}, {"#literal": 3}]}
// Empty list with type
{"#list": [], "#type": "List<Int>"}
// List of structs
{"#list": [
{"#struct": {"id": {"#literal": 1}}},
{"#struct": {"id": {"#literal": 2}}}
]}#list vs #tuple:
#list: Variable size, homogeneous types#tuple: Fixed size, heterogeneous types
// List of integers (any length, all Int)
{"#list": [{"#literal": 1}, {"#literal": 2}]}
// Tuple of (Int, String) - exactly 2 elements, different types
{"#tuple": [{"#literal": 1}, {"#literal": "a"}]}Creates a dictionary/map. Supports two formats: object format and array format.
Keys are always strings. Convenient for string-keyed maps.
Syntax:
{"#dict": {"<key>": <expr>, ...}}Examples:
// Empty dict
{"#dict": {}}
// String keys
{"#dict": {
"key1": {"#literal": "value1"},
"key2": {"#literal": 42}
}}
// With type
{"#dict": {"name": {"#literal": "Alice"}}, "#type": "Dict<String, String>"}Allows arbitrary expressions as keys. Each entry is [key, value].
Syntax:
{"#dict": [[<key-expr>, <value-expr>], ...]}Examples:
// Empty dict
{"#dict": []}
// Key-value pairs
{"#dict": [
[{"#literal": "key1"}, {"#literal": "value1"}],
[{"#literal": "key2"}, {"#literal": 42}]
]}
// Complex keys (computed)
{"#dict": [
[["concat", {"#literal": "pre"}, {"#literal": "fix"}], {"#literal": "value"}]
]}When to use each:
- Object format: Simple string keys, more readable
- Array format: Non-string keys, computed keys, complex key expressions
Modifies other special forms to specify an explicit type. Uses the embedded type DSL.
Syntax: Always paired with another special form:
{"#literal": <value>, "#type": "<type>"}
{"#struct": {...}, "#type": "<type>"}
{"#tuple": [...], "#type": "<type>"}
{"#list": [...], "#type": "<type>"}
{"#dict": ..., "#type": "<type>"}Examples:
{"#literal": 42, "#type": "Int64"}
{"#literal": 3.14, "#type": "Float"}
{"#struct": {"x": {"#literal": 0}}, "#type": "Origin"}
{"#list": [], "#type": "List<String>"}
{"#tuple": [], "#type": "()"}
{"#dict": {}, "#type": "Dict<String, Int>"}See type-dsl.md for complete type syntax.
Empty tuple for type params, struct for named params:
["fn", {"#tuple": []}, {"#struct": {"x": "_"}}, "_", body_expr]["let", "varName", {"#literal": 10}, body_expr]["if", condition, {"#literal": "then"}, {"#literal": "else"}][
"filter",
"entities",
[
"fn",
{ "#tuple": [] },
{ "#struct": { "e": "_" } },
"_",
["==", "e.archived", { "#literal": false }],
],
]Only one primary # key allowed:
// ✗ Error - duplicate primary key
{ "#literal": 42, "#literal": 24 }Empty objects are invalid:
// ✗ Error - must have a # key
{}Regular keys not allowed in special form objects:
// ✗ Error - unknown key
{ "#literal": 42, "extra": "value" }Each special form expects specific value types:
// ✗ Error - #literal expects primitive, not object
{"#literal": {"nested": "object"}}
// ✗ Error - #struct expects object, not array
{"#struct": ["not", "an-object"]}- Parser entry:
libs/@local/hashql/syntax-jexpr/src/parser/object/initial.rs - Literal:
libs/@local/hashql/syntax-jexpr/src/parser/object/literal.rs - Struct:
libs/@local/hashql/syntax-jexpr/src/parser/object/struct.rs - Tuple:
libs/@local/hashql/syntax-jexpr/src/parser/object/tuple.rs - List:
libs/@local/hashql/syntax-jexpr/src/parser/object/list.rs - Dict:
libs/@local/hashql/syntax-jexpr/src/parser/object/dict.rs - Type:
libs/@local/hashql/syntax-jexpr/src/parser/object/type.rs
{"#literal": <json-primitive>} {"#literal": <json-primitive>, "#type": "<type>"}