Skip to content

PUT path drops JSON object key ordering — breaks drag-reorder UX in object-keyed schemas #1720

Description

@rubenvdlinde

Repro

Against /api/objects/openconnector/mapping/<id>:

  1. Object stored as {mapping: {beta: 'x', alpha: 'y', gamma: 'z'}}
  2. Send PUT with {mapping: {zeta: 'a', alpha: 'b', beta: 'c', gamma: 'd'}}
  3. GET returns {mapping: {beta: 'c', zeta: 'a', alpha: 'b', gamma: 'd'}}

The client-supplied key order is dropped. Existing keys retain their storage order, new keys get inserted between them.

Effect

Surfaced during openconnector PR #903 (drag-reorder rules in MappingDetailPage). The drag UI works in-session — local Vue state reorders, the PUT fires with the new order — but on the next GET the storage returns the keys in its own order, snapping the visible list back. Users drag, save, reload, see the old order. Confusing.

Mapping/cast/rule schemas use object-keyed shapes ({targetProp: template}) where order matters semantically (cascading transformations). Array-shaped fields like unset are unaffected.

Root cause hypothesis

OR's PUT path likely stores the JSON via PHP json_decode($x, true) → associative array → re-encodes on GET. Associative arrays in PHP preserve insertion order, so the dropping likely happens at the column-level merge step (existing keys get UPDATE, new keys get INSERT, then GET sorts by some default ordering).

Worth tracing in lib/Service/ObjectService::saveObject and the storage layer below.

Fix options

  1. Preserve client-supplied object key order by serializing the full object verbatim on PUT and storing as a single JSON column — no column-level diff. Most accurate fix, biggest blast radius.
  2. Add an _order sidecar metadata field on objects whose schema declares ordered keys (could be x-openregister-ordered-keys: true on the property). MappingService etc. honor it on read. Backward-compatible.
  3. Schema migration: switch mapping/cast to array-of-{property, template} pairs. Cleanest data model but breaking change for every existing mapping object.

Acceptance

curl -X PUT ... -d '{"mapping":{"zeta":1,"alpha":2,"beta":3}}' && curl ... returns keys in the same order: zeta, alpha, beta.

Caller

Tracked in openconnector#876 (drag-reorder follow-up to #874's MappingDetailPage).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions