Output Formats
Fluree supports multiple output formats for query results, each optimized for different use cases. You can choose the format that best fits your application's needs.
Supported Formats
JSON-LD Format
Default format for JSON-LD Query. Provides compact, context-aware JSON with IRI expansion/compaction.
Characteristics:
- Uses
@contextfor IRI compaction - Compact IRIs (e.g.,
ex:aliceinstead of full IRIs) - Inferable datatypes (string, long, double, boolean) rendered as bare values
- Language tags preserved
Example (graph crawl):
[
{
"@id": "ex:alice",
"schema:name": "Alice",
"schema:age": 30,
"schema:knows": {"@id": "ex:bob"}
}
]
Example (tabular SELECT):
[
["Alice", 30],
["Bob", 25]
]
SPARQL JSON Format
Standard SPARQL 1.1 result format for SPARQL queries.
Characteristics:
- W3C SPARQL 1.1 compliant
- Standard
resultsandbindingsstructure - Datatype information included
- Language tags included
Example:
{
"head": {
"vars": ["name", "age"]
},
"results": {
"bindings": [
{
"name": {
"type": "literal",
"value": "Alice"
},
"age": {
"type": "literal",
"value": "30",
"datatype": "http://www.w3.org/2001/XMLSchema#integer"
}
}
]
}
}
Typed JSON Format
Type-preserving JSON format with explicit datatype information on every value. Works with both tabular SELECT queries and graph crawl (entity-centric) queries.
Characteristics:
- Every literal includes
{"@value": ..., "@type": "..."}— even inferable types - References use
{"@id": "..."} - Language-tagged strings use
{"@value": ..., "@language": "..."} @jsonvalues use{"@value": <parsed>, "@type": "@json"}- Nested entities in graph crawl results are also fully typed
- IRIs compacted via
@context
Example (tabular SELECT):
[
{
"?name": {"@value": "Alice", "@type": "xsd:string"},
"?age": {"@value": 30, "@type": "xsd:long"}
}
]
Example (graph crawl):
[
{
"@id": "ex:alice",
"@type": ["schema:Person"],
"schema:name": {"@value": "Alice", "@type": "xsd:string"},
"schema:age": {"@value": 30, "@type": "xsd:long"},
"schema:knows": {
"@id": "ex:bob",
"schema:name": {"@value": "Bob", "@type": "xsd:string"}
},
"ex:data": {"@value": {"key": "val"}, "@type": "@json"}
}
]
Agent JSON Format
Optimized for LLM/agent consumption. Returns a self-describing envelope with a schema header, compact object rows using native JSON types, and built-in pagination support.
Request via HTTP:
Accept: application/vnd.fluree.agent+json
Fluree-Max-Bytes: 32768
Characteristics:
- Schema-once header: datatypes declared per variable, not repeated per value
- Native JSON types for values (strings, numbers, booleans — no wrappers for inferable types)
- Non-inferable datatypes annotated inline only where needed (
{"@value": ..., "@type": "..."}) - Byte-budget truncation with
hasMoreflag and resume query - Time-pinning metadata (
tfor single-ledger,isowallclock timestamp for cross-ledger)
Example (single-ledger, no truncation):
{
"schema": {
"?name": "xsd:string",
"?age": "xsd:integer",
"?s": "uri"
},
"rows": [
{"?name": "Alice", "?age": 30, "?s": "ex:alice"},
{"?name": "Bob", "?age": 25, "?s": "ex:bob"}
],
"rowCount": 2,
"t": 5,
"iso": "2026-03-26T14:30:00Z",
"hasMore": false
}
Example (truncated, with resume query):
{
"schema": {
"?name": "xsd:string",
"?age": "xsd:integer"
},
"rows": [
{"?name": "Alice", "?age": 30},
{"?name": "Bob", "?age": 25}
],
"rowCount": 2,
"t": 5,
"iso": "2026-03-26T14:30:00Z",
"hasMore": true,
"message": "Response truncated due to size limit of 32768 bytes. Use the query below to retrieve the next batch.",
"resume": "SELECT ?name ?age FROM <mydb:main@t:5> WHERE { ?s ex:name ?name ; ex:age ?age } OFFSET 2 LIMIT 100"
}
Schema types:
- Single type → string:
"?name": "xsd:string" - Mixed types → array:
"?value": ["xsd:string", "xsd:integer"] - IRI references →
"uri"
Envelope fields:
| Field | Present | Description |
|---|---|---|
schema | Always | Per-variable datatype map |
rows | Always | Array of {variable: value} objects |
rowCount | Always | Number of rows included |
t | Single-ledger only | Transaction number used for the query |
iso | Always | ISO-8601 wallclock timestamp at query time |
hasMore | Always | Whether more rows exist beyond the byte budget |
message | When truncated | Human-readable truncation explanation |
resume | When truncated, single-FROM only | Ready-to-execute SPARQL with @t: pinning and OFFSET |
Multi-ledger queries: The t field is omitted (each ledger has its own timeline). The resume field is also omitted; instead, the message instructs the caller to use @iso: on each FROM clause for time-pinning.
Byte budget: Set via the Fluree-Max-Bytes header. When the cumulative serialized size of rows exceeds this limit, the formatter stops adding rows and sets hasMore: true. The budget applies to row data only (schema and envelope overhead are excluded from the count).
Array Normalization
By default, graph crawl results return single-valued properties as bare scalars and multi-valued properties as arrays:
{"schema:name": "Alice", "ex:tags": ["rust", "wasm"]}
This can be problematic for typed struct deserialization (e.g., a Vec<String> field that receives a bare string when only one value exists).
normalize_arrays forces all property values into arrays regardless of cardinality:
{"schema:name": ["Alice"], "ex:tags": ["rust", "wasm"]}
This is orthogonal to typed JSON and can be combined with any format:
// Typed + normalized — most predictable for struct deserialization
let config = FormatterConfig::typed_json().with_normalize_arrays();
// JSON-LD + normalized — compact values but predictable shapes
let config = FormatterConfig::jsonld().with_normalize_arrays();
The @container: @set context annotation still forces arrays per-property and works regardless of the normalize_arrays setting.
Format Selection
JSON-LD Query
JSON-LD Query defaults to JSON-LD format. You can specify the format explicitly:
{
"@context": { "ex": "http://example.org/ns/" },
"select": ["?name", "?age"],
"where": [
{ "@id": "?person", "ex:name": "?name", "ex:age": "?age" }
],
"format": "jsonld"
}
SPARQL
SPARQL queries return SPARQL JSON format by default:
PREFIX ex: <http://example.org/ns/>
SELECT ?name ?age
WHERE {
?person ex:name ?name .
?person ex:age ?age .
}
Datatype Handling
String Types
JSON-LD:
"Hello"
Typed JSON:
{"@value": "Hello", "@type": "xsd:string"}
SPARQL JSON:
{"type": "literal", "value": "Hello"}
Numeric Types
JSON-LD:
42
Typed JSON:
{"@value": 42, "@type": "xsd:long"}
SPARQL JSON:
{"type": "literal", "value": "42", "datatype": "http://www.w3.org/2001/XMLSchema#integer"}
Language-Tagged Strings
All formats use the same representation:
{"@value": "Hello", "@language": "en"}
IRIs
JSON-LD / Typed JSON:
{"@id": "ex:alice"}
SPARQL JSON:
{"type": "uri", "value": "http://example.org/ns/alice"}
Rust API
Use FormatterConfig to control output format via the query builder API:
use fluree_db_api::FormatterConfig;
// Single-ledger query with explicit format
let db = fluree.db("mydb:main").await?;
let result = db.query(&fluree)
.sparql("SELECT ?name WHERE { ?s <schema:name> ?name }")
.format(FormatterConfig::typed_json())
.execute_formatted()
.await?;
// Dataset query with format
let result = dataset.query(&fluree)
.sparql("SELECT * WHERE { ?s ?p ?o }")
.format(FormatterConfig::sparql_json())
.execute_formatted()
.await?;
// Connection-level query with format
let result = fluree.query_from()
.jsonld(&query_with_from)
.format(FormatterConfig::jsonld())
.execute_formatted()
.await?;
// AgentJson with byte budget and resume support
use fluree_db_api::AgentJsonContext;
let config = FormatterConfig::agent_json()
.with_max_bytes(32768)
.with_agent_json_context(AgentJsonContext {
sparql_text: Some(sparql.to_string()),
from_count: 1,
iso_timestamp: Some(chrono::Utc::now().to_rfc3339()),
});
let result = db.query(&fluree)
.sparql("SELECT ?name ?age WHERE { ?s ex:name ?name ; ex:age ?age }")
.format(config)
.execute_formatted()
.await?;
// Or directly on QueryResult:
let json = result.to_agent_json(&snapshot)?; // no budget
let json = result.to_agent_json_with_config(&snapshot, &config)?; // with budget
Available format constructors:
FormatterConfig::jsonld()— JSON-LD (default for JSON-LD queries)FormatterConfig::sparql_json()— SPARQL 1.1 JSON Results (default for SPARQL queries)FormatterConfig::typed_json()— Typed JSON with explicit datatypes on every valueFormatterConfig::agent_json()— Agent JSON envelope for LLM/agent consumers
Builder methods:
.with_normalize_arrays()— Force array wrapping for all graph crawl properties.with_pretty()— Pretty-print JSON output.with_max_bytes(n)— Set byte budget for AgentJson truncation.with_agent_json_context(ctx)— Set SPARQL text, FROM count, and ISO timestamp for AgentJson resume queries
All three query paths (db.query(), dataset.query(), fluree.query_from()) support .format().
Direct formatting on QueryResult
For graph crawl queries (which require async DB access):
// Typed JSON with graph crawl support
let json = result.to_typed_json_async(db.as_graph_db_ref()).await?;
// Custom config (e.g., typed + normalize_arrays)
let config = FormatterConfig::typed_json().with_normalize_arrays();
let json = result.format_async(db.as_graph_db_ref(), &config).await?;
When no .format() is set:
- JSON-LD queries default to JSON-LD format
- SPARQL queries default to SPARQL JSON format
CLI Usage
The fluree query command supports format selection via --format:
# Default table output
fluree query "SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 5"
# JSON output
fluree query --format json '{"select": {"ex:alice": ["*"]}, "from": "mydb:main"}'
# Typed JSON output (explicit types on every value)
fluree query --format typed-json '{"select": {"ex:alice": ["*"]}, "from": "mydb:main"}'
# Normalize arrays (force all properties to arrays)
fluree query --format json --normalize-arrays '{"select": {"ex:alice": ["*"]}, "from": "mydb:main"}'
# Typed JSON + normalize arrays (most predictable for programmatic use)
fluree query --format typed-json --normalize-arrays '{"select": {"ex:alice": ["*"]}, "from": "mydb:main"}'
Performance Considerations
- JSON-LD is the most efficient format — inferable types skip the
@value/@typewrapper - Typed JSON adds a constant-factor overhead per literal value (one extra JSON object allocation). Query execution is unaffected — only the formatting phase is slower.
- normalize_arrays adds zero overhead when disabled (default). When enabled, it skips the
len() == 1check — no additional allocations beyond the array wrapper. - TSV/CSV bypass JSON DOM construction entirely for maximum throughput
Best Practices
- Use JSON-LD for human-facing apps: Compact and readable
- Use Typed JSON for struct deserialization: Unambiguous types prevent parsing surprises
- Use
normalize_arraysfor typed consumers: EnsuresVec<T>fields always get arrays - Use SPARQL JSON for standard tooling: Interoperable with SPARQL clients
- Use TSV/CSV for bulk export: Highest throughput, smallest memory footprint
- Use Agent JSON for LLM/agent integrations: Schema-once + pagination prevents context window overflow
Related Documentation
- JSON-LD Query: JSON-LD Query language
- SPARQL: SPARQL query language
- Datatypes: Type system details