Façade-X mapping for JSON
Copyright
©
2026
the Contributors to the Façade-X mapping for JSON
Specification, published by the
Data Façades Community Group under the
W3C Community Contributor License Agreement (CLA). A human-readable
summary
is available.
This document specifies how a JSON resource is represented as a Façade-X data source.
This specification was published by the Data Façades Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.
This is version 0.1 of this document, released on 2026-10-06, at https://w3c-facade-x.github.io/facade-x-specs/0.1/mapping-json.html. This version will not change. The latest release is always at https://w3c-facade-x.github.io/facade-x-specs/mapping-json.html. The editor's draft, which may include changes made after this release, is at https://w3c-facade-x.github.io/facade-x-specs/dev/mapping-json.html. The issues addressed in this release, and those still open, are listed in the release notes.
This document is a first draft. It follows the behaviour of the reference implementation, SPARQL Anything, and the test cases in facade-x-tests, except where the Issues section says otherwise.
GitHub Issues are preferred for discussion of this specification.
This document is listed in Façade-X Mappings, one of the seven Façade-X specification documents produced by the Data Façades Community Group:
This section is non-normative.
A JSON text is a single value: an
object, an array, a
string, a number,
true, false, or null [RFC8259]. Objects hold members by name, and arrays hold
elements by position.
In Façade-X, objects and arrays are containers. Object members are held by string slots and
array elements by numbered slots. Strings, numbers and booleans are values.
This document applies the common principles of the format mappings.
| Prefix | Namespace |
|---|---|
| fx | http://sparql.xyz/facade-x/ns/ |
| fxe | http://sparql.xyz/facade-x/engine/ |
| xyz | http://sparql.xyz/facade-x/data/ |
| rdf | http://www.w3.org/1999/02/22-rdf-syntax-ns# |
| xsd | http://www.w3.org/2001/XMLSchema# |
The starting point is the idea that Facade-X should not modify the lexical form of data values. Previously, we mapped JSON numerals to xsd:decimal (see also SPARQL-Anything/sparql.anything#620)
However, JSON numerals and xsd:decimal lexical forms are not the same set: JSON allows exponents (1.5e3) and xsd:decimal does not. Keeping the source text, therefore, produces ill-typed literals, so the mappings need a shared rule for when to keep a value's surface form and when to rewrite it as the canonical form. Cases of mismatches between format specifications and XSD datatypes may arise. We should discuss this issue more generally and then make a decision that can guide future mappings.
We review first what SPARQL Anything does, then discuss potential solutions.
SPARQL Anything types every JSON number as xsd:decimal and copies the numeral from the source as the lexical form. From tests/json/2-features:
| JSON | SPARQL Anything output |
|---|---|
42 |
"42"^^xsd:decimal |
3.0 |
"3.0"^^xsd:decimal |
-0 |
"0"^^xsd:decimal |
1.5e3 |
"1.5e3"^^xsd:decimal |
2E-4 |
"2E-4"^^xsd:decimal |
12345678901234567890 |
"12345678901234567890"^^xsd:decimal |
The last two exponent rows are ill-typed literals. The lexical space of xsd:decimal is (\+|-)?([0-9]+(\.[0-9]*)?|\.[0-9]+), with no exponent notation (XSD 1.1 Part 2, §3.3.3.1). A SPARQL engine cannot compute a value for them, so numeric comparisons and arithmetic on them fail. The behaviour is also inconsistent with itself: 3.0 keeps its surface form, but -0 is normalised to 0.
JSON and XSD agree on the values but not on the numerals:
xsd:decimal: numbers of the form i / 10^n (§3.3.3). xsd:decimal can represent every JSON number exactly.xsd:decimal lexical space does not. Conversely, XSD allows forms JSON forbids (+1, .5, 5., 007), but those don't matter here. Every JSON numeral without an exponent is a valid xsd:decimal lexical form; every numeral with an exponent is not.xsd:double. The xsd:double lexical space accepts exponent notation, but its value space is m × 2^e with |m| < 2^53 (§3.3.5). 2E-4 and 12345678901234567890 have no exact double value, so switching to xsd:double would lose information.xsd:decimal.3.0 → "3.0", -0 → "-0").xsd:decimal representation of its value (1.5e3 → "1500", 2E-4 → "0.0002").Every literal produced is well-typed and exactly equal in value to the JSON number. The surface form is kept wherever XSD allows it.
| JSON | SPARQL Anything | Draft |
|---|---|---|
1.5e3 |
"1.5e3"^^xsd:decimal (ill-typed) |
"1500"^^xsd:decimal |
2E-4 |
"2E-4"^^xsd:decimal (ill-typed) |
"0.0002"^^xsd:decimal |
-0 |
"0"^^xsd:decimal |
"-0"^^xsd:decimal |
All other numbers are unchanged. The expected output of tests/json/2-features needs updating to match, and SPARQL Anything needs a fix.
Façade-X aims to represent the source as it is, which argues for keeping the surface form of values. In RDF, though, a typed literal ties the surface form to a datatype's lexical space, and source grammars rarely match XSD lexical spaces exactly. Surface form also matters beyond values: "3.0"^^xsd:decimal and "3"^^xsd:decimal are equal values but different RDF terms, so sameTerm, DISTINCT and joins on literals depend on the lexical form.
This will come up in every mapping whose source format has typed values: YAML (numbers, booleans, dates), spreadsheets (numbers stored as doubles, dates as serial numbers), and possibly XML if schema-aware typing is considered. CSV and plain XML avoid it only because all their values are strings.
Options for a principle shared by all mappings:
3.0 → 3).The source text of every value is its lexical form, and no value is rewritten. The datatype is chosen so that the literal is well-typed. In the case of JSON numerals, we first map to xsd:decimal (design choice) and then verify conformance with the surface form:
42 → "42"^^xsd:decimal, 3.0 → "3.0"^^xsd:decimal, -0 → "-0"^^xsd:decimal);1.5e3 → "1.5e3"^^D): xsd:double in the example.xsd:string.If agreed, this becomes a common principle in the format mappings page: "The lexical form of a value is its source form. The datatype is the natural XML Schema datatype for the value when the source form is in its lexical space; otherwise it is a datatype whose lexical space accepts the source form, as defined by the mapping." The JSON draft and tests/json/2-features would then be updated accordingly. Opinions welcome.
The typing of numbers follows the decision in
issue #41. It differs from the
reference implementation, which types every number as xsd:decimal (giving ill-typed literals for
numbers with an exponent) and writes -0 as "0".
When fxe:json.include-null-values is true, the reference implementation represents null with
the IRI xyz:null. Whether null should have a term in the Schema vocabulary is open.
The representation of a top-level string, number or boolean, and the treatment of duplicate member names (which RFC 8259 §4 allows, although names SHOULD be unique), are not yet specified.
An engine MUST apply this mapping to a resource whose media type is
application/json, or any media type with the
structured syntax suffix +json
[RFC6839] (e.g. application/problem+json).
When the media type is not given, an engine SHOULD apply this mapping to resources with the file
extension json.
A JSON resource yields exactly one data source. Its triples are placed in the named graph whose IRI
is the value of fxe:root.
The top-level value is represented by the root container, typed fx:Root:
An object is represented by a container. Each member is held by a string slot whose IRI is minted
from the member name, as defined in the common principles. With the
default namespace, the member "name" gives the slot xyz:name.
Characters that are not allowed in an IRI are percent-encoded as UTF-8; other characters, including
non-ASCII letters, are kept. The member "key with spaces" gives xyz:key%20with%20spaces, and the
empty member name "" gives the namespace IRI itself.
A member whose value is an object or an array holds the container that represents it. An empty object gives a container with no slots.
An array is represented by a container. Each element is held by a numbered slot, in the order of the
array: the first element in rdf:_1, the second in rdf:_2, and so on.
A null element keeps its position: no triple is produced for it, and the following element keeps
its own index. For example, in [1, null, 3] the value 3 is held by rdf:_3.
An element that is an object or an array holds the container that represents it. An empty array gives a container with no slots.
JSON defines value types. Following the common principles, the lexical form of every value is its source form, and the datatype is chosen so that the literal is well-typed [XMLSCHEMA11-2]:
| JSON value | Literal |
|---|---|
| string | xsd:string; the lexical form is the sequence of Unicode characters the string denotes, after unescaping |
| number without an exponent | xsd:decimal, with the numeral as written in the source |
number with an exponent (exp) |
xsd:double, with the numeral as written in the source |
true, false |
xsd:boolean |
null |
no triple, unless fxe:json.include-null-values is true |
Numbers are typed as follows:
frac) and exponent (exp). The natural datatype for a JSON number is xsd:decimal, whose
value space contains every JSON number exactly.
A numeral without an exponent is always in the
lexical space of xsd:decimal,
so it is typed xsd:decimal (e.g. 3.0 gives "3.0"^^xsd:decimal, and -0 gives "-0"^^xsd:decimal).xsd:decimal has no exponent notation. A numeral with an exponent is in the
lexical space of xsd:double, so it is
typed xsd:double (e.g. 1.5e3 gives "1.5e3"^^xsd:double).xsd:string. This does not happen for a
well-formed JSON text.The value of an xsd:double literal is the nearest double-precision value to the number, so it may
differ from the number written in the source (e.g. 2E-4). The lexical form keeps the number as
written.
These options are terms of the Engine vocabulary and apply only to this mapping.
| Term | Range | Default | Description |
|---|---|---|---|
fxe:json.include-null-values |
xsd:boolean |
false | Produce a triple for members and elements whose value is null. |
fxe:json.path |
xsd:string |
— | A JSONPath query [RFC9535]. The root container holds the nodes selected by the query in numbered slots, in the order they are selected. |
fxe:json.literalize |
xsd:string |
— | A member name. The values of members with this name are represented as a single literal holding their JSON text, instead of as containers. |
The reference implementation accepts several values for json.path and json.literalize as
numbered options (json.path.1, json.path.2, …). Whether this form is kept is open.
This section is non-normative.
Input:
{
"name": "Alice",
"age": 30
}
Façade-X representation:
@prefix fx: <http://sparql.xyz/facade-x/ns/> .
@prefix xyz: <http://sparql.xyz/facade-x/data/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
[] a fx:Root ;
xyz:name "Alice" ;
xyz:age "30"^^xsd:decimal .
Input:
{
"object": { "nested": { "deep": "value" } },
"array": [1, "two", 3.0, true, null, {"key": "value"}, ["nested", "array"], [], {}],
"emptyObject": {},
"key with spaces": "spaces in key"
}
Façade-X representation:
@prefix fx: <http://sparql.xyz/facade-x/ns/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix xyz: <http://sparql.xyz/facade-x/data/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
[] a fx:Root ;
xyz:object [ xyz:nested [ xyz:deep "value" ] ] ;
xyz:array [
rdf:_1 "1"^^xsd:decimal ;
rdf:_2 "two" ;
rdf:_3 "3.0"^^xsd:decimal ;
rdf:_4 true ;
rdf:_6 [ xyz:key "value" ] ;
rdf:_7 [ rdf:_1 "nested" ; rdf:_2 "array" ] ;
rdf:_8 [ ] ;
rdf:_9 [ ]
] ;
xyz:emptyObject [ ] ;
<http://sparql.xyz/facade-x/data/key%20with%20spaces> "spaces in key" .
The null element produces no triple, so there is no rdf:_5.
Input:
{
"integer": 42,
"negativeZero": -0,
"decimal": 3.0,
"exponent": 1.5e3,
"negativeExponent": 2E-4,
"bigInteger": 12345678901234567890
}
Façade-X representation:
@prefix fx: <http://sparql.xyz/facade-x/ns/> .
@prefix xyz: <http://sparql.xyz/facade-x/data/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
[] a fx:Root ;
xyz:integer "42"^^xsd:decimal ;
xyz:negativeZero "-0"^^xsd:decimal ;
xyz:decimal "3.0"^^xsd:decimal ;
xyz:exponent "1.5e3"^^xsd:double ;
xyz:negativeExponent "2E-4"^^xsd:double ;
xyz:bigInteger "12345678901234567890"^^xsd:decimal .
Listing each element of the array member, with its position:
PREFIX fx: <http://sparql.xyz/facade-x/ns/>
PREFIX fxe: <http://sparql.xyz/facade-x/engine/>
PREFIX xyz: <http://sparql.xyz/facade-x/data/>
SELECT ?slot ?value WHERE {
SERVICE <x-sparql-anything:> {
fx:properties fxe:location "features.json" .
?root a fx:Root ;
xyz:array ?array .
?array ?slot ?value .
}
}
This section is non-normative.