Summary
Generalize the existing tuple ordering keyword into a single, format-agnostic
property-ordering primitive — proposed name propertyOrder — that MAY be applied to both
object and tuple. When present, it declares an explicit, deterministic ordering of a type's
properties. When absent on an object, a normative default ordering applies (sort by property
name). For tuple it preserves today's semantics; for object it adds a deterministic
serialization order without changing the object's representation or property optionality.
Motivation
Two facts in core are in tension for anyone building deterministic/canonical or binary
serializations:
object is "an unordered collection of key–value pairs." Correct for JSON semantics, but it
means a schema gives no canonical field order.
tuple already solves ordering — but only by forcing an array representation and making
all properties implicitly REQUIRED.
A deterministic, schema-driven field order is needed for:
- Canonical / hash-stable serialization (RFC 8785-style canonical JSON, signing, dedup).
- Positional binary bindings. The existing native AMQP 1.0 encoding already emits object
fields in declaration order, and an assessment of further native binary bindings (Avro,
Protobuf, Thrift, MessagePack, BSON) shows every one needs a single, language-independent rule
for "in what order do an object's fields appear on the wire?" Today that rule is re-invented
implicitly in each binding rather than stated once in core.
Rather than have each binding (and canonical-JSON) re-derive ordering, core should expose one
ordering primitive they all share.
Current behavior (for reference)
object: "represented as a JSON object, which is an unordered collection of key–value pairs."
tuple: elements are declared via a properties map (as with object); all are implicitly
REQUIRED; the REQUIRED tuple keyword is "a JSON array of strings, where each declared property
name MUST be an element of the array," and that array's order defines element order.
Representation is a JSON array.
So ordering already exists as a concept — it is just (1) welded to the array representation and
all-REQUIRED semantics of tuple, and (2) spelled with a keyword (tuple) identical to the type
name, which reads as a pun: "type": "tuple" + "tuple": [...].
Proposal
- Introduce
propertyOrder (working name; see Naming): a keyword whose value is a JSON
array of strings, each of which MUST be a declared property name of the type.
- Allow it on both
object and tuple:
- On
tuple: it replaces today's tuple keyword (same semantics — defines element order;
representation stays a JSON array; properties stay implicitly REQUIRED).
- On
object: it declares the canonical serialization order of properties. It does not
change the representation (still a JSON object), does not make properties REQUIRED, and
does not affect equality. object remains semantically unordered; propertyOrder
governs only the sequence in which properties are emitted by order-sensitive encoders
(canonical JSON, binary bindings).
- Default ordering when
propertyOrder is absent on an object: sort properties by name
(code-point / byte order). This is unambiguous because all property names MUST match
[A-Za-z_][A-Za-z0-9_]* (Identifier Rules) — pure ASCII, so code-point order = UTF-8 byte
order, identical on every platform, with no collation/locale ambiguity.
- Two-tier rule overall: explicit
propertyOrder if present, else the name-sort default.
Why a name-sort default (not declaration order)
Declaration order is fragile; a name-sort default has three concrete advantages:
$extends becomes trivial and unambiguous. Merge base + derived properties, then sort — no
"base-before-derived" rule and no diamond/precedence ambiguity.
- Immune to JSON member-order loss. Declaration order would require every JSON loader in every
language to preserve object key insertion order, which JSON does not guarantee. Name-sort depends
only on the names, not on parse order.
- Agrees with RFC 8785 (JCS), which already canonicalizes object members by sorted key — so
JSON Structure's canonical order and JCS coincide for the JSON encoding.
Scope / non-goals
- This concerns serialization order/determinism, not schema evolution. It does not replace
numeric field identifiers for tag-based formats (Protobuf/Thrift): inserting a property
mid-order shifts the positions of later properties, which is unsafe for independently-evolving
tag-based wire formats. Those formats still need explicit pinned numbers (a separate, per-format
concern). Avro is immune (it resolves by name). The value here is a single canonical order
primitive, not an evolution mechanism.
object equality/semantics are unchanged — it stays unordered; propertyOrder is purely a
sequencing annotation.
Naming
Recommend propertyOrder: self-documenting, consistent with core's "properties" vocabulary
(core says "properties"/"elements", not "fields"), and unambiguous that it governs only property
ordering, not representation. Alternatives considered:
order — terser, fits core's single-word keyword style and is a clean 1:1 rename of tuple,
but generic ("order of what, in what sense?").
sequence — avoid: it implies the property becomes a list, and structurally clashes with
XSD's xs:sequence, which is an ordered container of element declarations, not an array of
property names.
fieldOrder — inconsistent ("field" is not core's term).
Open questions
- Keyword name —
propertyOrder vs order.
- Total vs partial order on
object — must propertyOrder list all properties (total), or
may it list a prefix with the remainder name-sorted after? Recommendation: total when
present (mirrors today's tuple, which requires every property to appear); name-sort only
when the keyword is absent.
- Migration of
tuple — keep the tuple type but rename its ordering keyword to
propertyOrder. This is a breaking change to the keyword (and to validators + existing
tuple schemas). Option: accept the tuple keyword as a deprecated alias of propertyOrder on
the tuple type for one revision.
- Does the
tuple keyword name go away entirely, or remain a deprecated alias?
Backwards compatibility
Renaming the tuple ordering keyword is a breaking change for the tuple type and for the
validators. A deprecation alias (accept the tuple keyword as a synonym on tuple types) would
smooth migration. Adding propertyOrder to object is purely additive.
Summary
Generalize the existing
tupleordering keyword into a single, format-agnosticproperty-ordering primitive — proposed name
propertyOrder— that MAY be applied to bothobjectandtuple. When present, it declares an explicit, deterministic ordering of a type'sproperties. When absent on an
object, a normative default ordering applies (sort by propertyname). For
tupleit preserves today's semantics; forobjectit adds a deterministicserialization order without changing the object's representation or property optionality.
Motivation
Two facts in core are in tension for anyone building deterministic/canonical or binary
serializations:
objectis "an unordered collection of key–value pairs." Correct for JSON semantics, but itmeans a schema gives no canonical field order.
tuplealready solves ordering — but only by forcing an array representation and makingall properties implicitly REQUIRED.
A deterministic, schema-driven field order is needed for:
fields in declaration order, and an assessment of further native binary bindings (Avro,
Protobuf, Thrift, MessagePack, BSON) shows every one needs a single, language-independent rule
for "in what order do an object's fields appear on the wire?" Today that rule is re-invented
implicitly in each binding rather than stated once in core.
Rather than have each binding (and canonical-JSON) re-derive ordering, core should expose one
ordering primitive they all share.
Current behavior (for reference)
object: "represented as a JSON object, which is an unordered collection of key–value pairs."tuple: elements are declared via apropertiesmap (as withobject); all are implicitlyREQUIRED; the REQUIRED
tuplekeyword is "a JSON array of strings, where each declared propertyname MUST be an element of the array," and that array's order defines element order.
Representation is a JSON array.
So ordering already exists as a concept — it is just (1) welded to the array representation and
all-REQUIRED semantics of
tuple, and (2) spelled with a keyword (tuple) identical to the typename, which reads as a pun:
"type": "tuple"+"tuple": [...].Proposal
propertyOrder(working name; see Naming): a keyword whose value is a JSONarray of strings, each of which MUST be a declared property name of the type.
objectandtuple:tuple: it replaces today'stuplekeyword (same semantics — defines element order;representation stays a JSON array; properties stay implicitly REQUIRED).
object: it declares the canonical serialization order of properties. It does notchange the representation (still a JSON object), does not make properties REQUIRED, and
does not affect equality.
objectremains semantically unordered;propertyOrdergoverns only the sequence in which properties are emitted by order-sensitive encoders
(canonical JSON, binary bindings).
propertyOrderis absent on an object: sort properties by name(code-point / byte order). This is unambiguous because all property names MUST match
[A-Za-z_][A-Za-z0-9_]*(Identifier Rules) — pure ASCII, so code-point order = UTF-8 byteorder, identical on every platform, with no collation/locale ambiguity.
propertyOrderif present, else the name-sort default.Why a name-sort default (not declaration order)
Declaration order is fragile; a name-sort default has three concrete advantages:
$extendsbecomes trivial and unambiguous. Merge base + derived properties, then sort — no"base-before-derived" rule and no diamond/precedence ambiguity.
language to preserve object key insertion order, which JSON does not guarantee. Name-sort depends
only on the names, not on parse order.
JSON Structure's canonical order and JCS coincide for the JSON encoding.
Scope / non-goals
numeric field identifiers for tag-based formats (Protobuf/Thrift): inserting a property
mid-order shifts the positions of later properties, which is unsafe for independently-evolving
tag-based wire formats. Those formats still need explicit pinned numbers (a separate, per-format
concern). Avro is immune (it resolves by name). The value here is a single canonical order
primitive, not an evolution mechanism.
objectequality/semantics are unchanged — it stays unordered;propertyOrderis purely asequencing annotation.
Naming
Recommend
propertyOrder: self-documenting, consistent with core's "properties" vocabulary(core says "properties"/"elements", not "fields"), and unambiguous that it governs only property
ordering, not representation. Alternatives considered:
order— terser, fits core's single-word keyword style and is a clean 1:1 rename oftuple,but generic ("order of what, in what sense?").
sequence— avoid: it implies the property becomes a list, and structurally clashes withXSD's
xs:sequence, which is an ordered container of element declarations, not an array ofproperty names.
fieldOrder— inconsistent ("field" is not core's term).Open questions
propertyOrdervsorder.object— mustpropertyOrderlist all properties (total), ormay it list a prefix with the remainder name-sorted after? Recommendation: total when
present (mirrors today's
tuple, which requires every property to appear); name-sort onlywhen the keyword is absent.
tuple— keep thetupletype but rename its ordering keyword topropertyOrder. This is a breaking change to the keyword (and to validators + existingtuple schemas). Option: accept the
tuplekeyword as a deprecated alias ofpropertyOrderonthe tuple type for one revision.
tuplekeyword name go away entirely, or remain a deprecated alias?Backwards compatibility
Renaming the
tupleordering keyword is a breaking change for thetupletype and for thevalidators. A deprecation alias (accept the
tuplekeyword as a synonym on tuple types) wouldsmooth migration. Adding
propertyOrdertoobjectis purely additive.