Repro
Against /api/objects/openconnector/mapping/<id>:
- Object stored as
{mapping: {beta: 'x', alpha: 'y', gamma: 'z'}}
- Send PUT with
{mapping: {zeta: 'a', alpha: 'b', beta: 'c', gamma: 'd'}}
- 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
- 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.
- 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.
- 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).
Repro
Against
/api/objects/openconnector/mapping/<id>:{mapping: {beta: 'x', alpha: 'y', gamma: 'z'}}{mapping: {zeta: 'a', alpha: 'b', beta: 'c', gamma: 'd'}}{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 likeunsetare 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::saveObjectand the storage layer below.Fix options
_ordersidecar metadata field on objects whose schema declares ordered keys (could bex-openregister-ordered-keys: trueon the property). MappingService etc. honor it on read. Backward-compatible.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).