Skip to content

Fix singleton unwrapping for $each, $spread, and $match#3

Merged
NirBarak-RecoLabs merged 1 commit intomainfrom
fix-singleton-unwrapping-each-spread-match
Mar 24, 2026
Merged

Fix singleton unwrapping for $each, $spread, and $match#3
NirBarak-RecoLabs merged 1 commit intomainfrom
fix-singleton-unwrapping-each-spread-match

Conversation

@NirBarak-RecoLabs
Copy link
Collaborator

Summary

  • $each, $spread, and $match returned []any instead of *Sequence, which prevented CollapseSequence from performing singleton unwrapping. Per the JSONata spec, when a function returns a single-element sequence the result must be unwrapped to its scalar value.
  • The KeepArray operator ([] suffix) now correctly sets KeepSingleton on the Sequence before CollapseSequence runs, ensuring single-element arrays are preserved when the operator is applied.

What changed

File Change
functions/object_funcs.go $each and $spread return *Sequence instead of []any
functions/string_match_replace.go $match returns *Sequence instead of []any
internal/evaluator/eval_function.go Set KeepSingleton when KeepArray is true, before calling CollapseSequence
evaluator_test.go New TestEachSingletonUnwrap covering scalar, array, nested, and error cases
testdata/groups/function-each/ 5 new JSON suite tests (case003-case007)
testdata/groups/function-spread/ 2 new JSON suite tests (case004-case005)
testdata/groups/function-match/ 3 new JSON suite tests (case000-case002)

Examples

/* Before fix: returned ["a=1"], now correctly returns "a=1" */
$each({"a": 1}, function($v, $k) { $k & "=" & $string($v) })

/* Before fix: returned [{"a": 1}], now correctly returns {"a": 1} */
$spread({"a": 1})

/* Before fix: returned [{"match": "h", ...}], now correctly returns {"match": "h", ...} */
$match("hello", /^h/)

Test plan

  • All existing JSON suite tests pass
  • All existing Go unit tests pass
  • New tests verify singleton unwrapping for $each, $spread, $match
  • New tests verify KeepArray operator preserves arrays correctly
  • Verified parity with jsonata-js reference implementation

@NirBarak-RecoLabs NirBarak-RecoLabs self-assigned this Mar 24, 2026
@NirBarak-RecoLabs NirBarak-RecoLabs force-pushed the fix-singleton-unwrapping-each-spread-match branch from 7fe47d1 to dc20de3 Compare March 24, 2026 09:37
These three functions returned []any instead of *Sequence,
preventing CollapseSequence from unwrapping single-element
results. This caused parity divergence from jsonata-js:

- $each({"a":1}, fn) returned ["result"] instead of "result"
- $spread({"a":1}) returned [{"a":1}] instead of {"a":1}
- $match("hello", /^h/) returned [{...}] instead of {...}

Return *Sequence from all three so evalFunction properly
singleton-unwraps single-element results. Also introduces
CollapseAndKeep helper in value.go (with shallow struct copy
for alias safety) to centralize KeepArray/singleton logic,
and collapses intermediate results in function composition
to prevent raw *Sequence leaking between composed functions.

Signed-off-by: NirBarak-RecoLabs <nirb@recolabs.ai>
@NirBarak-RecoLabs NirBarak-RecoLabs force-pushed the fix-singleton-unwrapping-each-spread-match branch from c8f8286 to d3ed71a Compare March 24, 2026 11:00
@NirBarak-RecoLabs NirBarak-RecoLabs merged commit 6375ab4 into main Mar 24, 2026
2 checks passed
@NirBarak-RecoLabs NirBarak-RecoLabs deleted the fix-singleton-unwrapping-each-spread-match branch March 24, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants