Skip to content

Proposal: generalize tuple ordering into a propertyOrder keyword usable on object (canonical, schema-driven field order) #27

@clemensv

Description

@clemensv

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

  1. 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.
  2. 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).
  3. 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.
  4. 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

  1. Keyword namepropertyOrder vs order.
  2. 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.
  3. 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.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions