Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions spec/jsonata-suite/baseline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
return {
["array-constructor/array-sequences/2"] = true,
["array-constructor/array-sequences/3"] = true,
["array-constructor/array-sequences/4"] = true,
["array-constructor/case000"] = true,
["array-constructor/case001"] = true,
["array-constructor/case002"] = true,
Expand All @@ -15,11 +16,13 @@ return {
["array-constructor/case009"] = true,
["array-constructor/case011"] = true,
["array-constructor/case012"] = true,
["array-constructor/case015"] = true,
["array-constructor/case016"] = true,
["array-constructor/case017"] = true,
["array-constructor/case019"] = true,
["blocks/case000"] = true,
["blocks/case001"] = true,
["blocks/case002"] = true,
["blocks/case003"] = true,
["blocks/case004"] = true,
["blocks/case005"] = true,
Expand All @@ -36,6 +39,7 @@ return {
["boolean-expresssions/case009"] = true,
["boolean-expresssions/case010"] = true,
["boolean-expresssions/case011"] = true,
["boolean-expresssions/case016"] = true,
["boolean-expresssions/case017"] = true,
["boolean-expresssions/case018"] = true,
["boolean-expresssions/case019"] = true,
Expand All @@ -50,6 +54,7 @@ return {
["boolean-expresssions/case028"] = true,
["boolean-expresssions/case029"] = true,
["boolean-expresssions/case030"] = true,
["closures/case000"] = true,
["closures/case001"] = true,
["coalescing-operator/case000"] = true,
["coalescing-operator/case001"] = true,
Expand Down Expand Up @@ -124,6 +129,7 @@ return {
["context/case000"] = true,
["context/case001"] = true,
["context/case002"] = true,
["context/case003"] = true,
["default-operator/case000"] = true,
["default-operator/case001"] = true,
["default-operator/case002"] = true,
Expand All @@ -146,6 +152,9 @@ return {
["descendent-operator/case000"] = true,
["descendent-operator/case001"] = true,
["descendent-operator/case002"] = true,
["descendent-operator/case003"] = true,
["descendent-operator/case004"] = true,
["descendent-operator/case005"] = true,
["descendent-operator/case006"] = true,
["descendent-operator/case007"] = true,
["descendent-operator/case008"] = true,
Expand Down Expand Up @@ -189,6 +198,7 @@ return {
["fields/case007"] = true,
["flattening/array-inputs/0"] = true,
["flattening/array-inputs/1"] = true,
["flattening/array-inputs/2"] = true,
["flattening/array-inputs/3"] = true,
["flattening/array-inputs/4"] = true,
["flattening/array-inputs/5"] = true,
Expand Down Expand Up @@ -221,7 +231,15 @@ return {
["flattening/case034a"] = true,
["flattening/case035"] = true,
["flattening/case036"] = true,
["flattening/case037"] = true,
["flattening/case038"] = true,
["flattening/case039"] = true,
["flattening/case040"] = true,
["flattening/case041"] = true,
["flattening/case042"] = true,
["flattening/case043"] = true,
["flattening/case044"] = true,
["flattening/case045"] = true,
["flattening/sequence-of-arrays/0"] = true,
["flattening/sequence-of-arrays/1"] = true,
["function-abs/case000"] = true,
Expand Down Expand Up @@ -251,6 +269,7 @@ return {
["function-applications/case015"] = true,
["function-applications/case016"] = true,
["function-applications/case017"] = true,
["function-applications/case018"] = true,
["function-applications/case019"] = true,
["function-applications/case020"] = true,
["function-assert/case000"] = true,
Expand Down Expand Up @@ -631,6 +650,7 @@ return {
["function-sort/case002"] = true,
["function-sort/case003"] = true,
["function-sort/case005"] = true,
["function-sort/case006"] = true,
["function-sort/case007"] = true,
["function-sort/case008"] = true,
["function-sort/case009"] = true,
Expand Down Expand Up @@ -759,7 +779,10 @@ return {
["hof-filter/case003"] = true,
["hof-map/case000"] = true,
["hof-map/case001"] = true,
["hof-map/case0010"] = true,
["hof-map/case002"] = true,
["hof-map/case003"] = true,
["hof-map/case004"] = true,
["hof-map/case005"] = true,
["hof-map/case006"] = true,
["hof-map/case007"] = true,
Expand Down Expand Up @@ -879,6 +902,8 @@ return {
["object-constructor/case005"] = true,
["object-constructor/case006"] = true,
["object-constructor/case007"] = true,
["object-constructor/case008"] = true,
["object-constructor/case009"] = true,
["object-constructor/case010"] = true,
["object-constructor/case011"] = true,
["object-constructor/case012"] = true,
Expand All @@ -888,6 +913,8 @@ return {
["object-constructor/case016"] = true,
["object-constructor/case017"] = true,
["object-constructor/case018"] = true,
["object-constructor/case019"] = true,
["object-constructor/case020"] = true,
["object-constructor/case021"] = true,
["object-constructor/case022"] = true,
["object-constructor/case023"] = true,
Expand Down Expand Up @@ -952,7 +979,12 @@ return {
["predicates/case001"] = true,
["predicates/case002"] = true,
["predicates/case003"] = true,
["quoted-selectors/case000"] = true,
["quoted-selectors/case001"] = true,
["quoted-selectors/case002"] = true,
["quoted-selectors/case003"] = true,
["quoted-selectors/case004"] = true,
["quoted-selectors/case005"] = true,
["quoted-selectors/case006"] = true,
["quoted-selectors/case007"] = true,
["range-operator/case000"] = true,
Expand Down Expand Up @@ -1037,6 +1069,8 @@ return {
["tail-recursion/case000"] = true,
["tail-recursion/case003"] = true,
["tail-recursion/case004"] = true,
["token-conversion/case000"] = true,
["token-conversion/case001"] = true,
["token-conversion/case002"] = true,
["transform/case000"] = true,
["transform/case001"] = true,
Expand Down Expand Up @@ -1066,6 +1100,8 @@ return {
["transform/case025"] = true,
["transform/case026"] = true,
["transform/case027"] = true,
["transform/case030"] = true,
["transform/case031"] = true,
["transform/case032"] = true,
["transform/case033"] = true,
["transform/case034"] = true,
Expand Down Expand Up @@ -1171,6 +1207,7 @@ return {
["wildcards/case004"] = true,
["wildcards/case005"] = true,
["wildcards/case006"] = true,
["wildcards/case007"] = true,
["wildcards/case008"] = true,
["wildcards/case009"] = true,
["wildcards/case010/0"] = true,
Expand Down
66 changes: 66 additions & 0 deletions spec/keeparray_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
local jsonata = require("jsonata")
local function run(src, input)
return jsonata.compile(src):evaluate(input)
end

describe("M6b A-2: quoted-string path step selects a field", function()
it('foo."bar" selects the field bar', function()
assert.are.equal(7, run('foo."bar"', { foo = { bar = 7 } }))
end)
it("a top-level string is still the literal", function()
assert.are.equal("bar", run('"bar"', { foo = { bar = 7 } }))
end)
it('foo."bar" over a non-object foo is undefined (faithful)', function()
assert.is_nil(run('foo."bar"', { foo = 5 }))
end)
it("a standalone string with a predicate stays a literal", function()
assert.are.equal("Red", run('"Red"[$$ = "Bus"]', "Bus"))
assert.is_nil(run('"Red"[$$ = "Car"]', "Bus"))
end)
it("a first-position quoted string in a path is also a field selector", function()
assert.are.equal(1, run('"x"."y"', { x = { y = 1 } }))
assert.are.equal(1, run('"x".y', { x = { y = 1 } }))
assert.are.equal(99, run('"a"."b"."c"', { a = { b = { c = 99 } } }))
end)

it("a standalone string / parenthesized literal is unaffected for non-path uses", function()
assert.are.equal("bar", run('"bar"', {}))
assert.are.equal("Red", run('"Red"[$$ = "Bus"]', "Bus"))
end)
end)

describe("M6b A-4: trailing ; in a block", function()
it("tolerates a trailing ; before )", function()
assert.are.equal(2, run("(1; 2;)"))
end)
it("( a; ) yields a", function()
assert.are.equal(1, run("(1;)"))
end)
it("empty () is unaffected", function()
assert.is_nil(run("()"))
end)
end)

describe("M6b []: keepArray (any step keeps the final array)", function()
it("a.b[] forces a single result into an array", function()
assert.are.same({ 1 }, run("a.b[]", { a = { b = 1 } }))
end)
it("a.b[] is a no-op on a multi-element result", function()
assert.are.same({ 1, 2 }, run("a.b[]", { a = { { b = 1 }, { b = 2 } } }))
end)
it("a mid-path [] keeps the final array (a[].b)", function()
assert.are.same({ 1 }, run("a[].b", { a = { b = 1 } }))
end)
it("a mid-path [] with a predicate keeps the final array (case037 shape)", function()
assert.are.same({ 1 }, run('x[k="a"][].v', { x = { { k = "a", v = 1 } } }))
end)
it("foo[] over a missing field is undefined, not []", function()
assert.is_nil(run("foo[]", {}))
end)
it("foo[][] is idempotent", function()
assert.are.same({ 5 }, run("foo[][]", { foo = 5 }))
end)
it("a normal path still unwraps a single result (regression)", function()
assert.are.equal(1, run("a.b", { a = { b = 1 } }))
end)
end)
18 changes: 16 additions & 2 deletions src/jsonata/evaluator.lua
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,19 @@ local function eval_path(node, input, env)
return context
end

-- True iff any step of this path (or of any nested path step) carries keepArray.
local function path_keeps_array(node)
for _, s in ipairs(node.steps) do
if s.keepArray then
return true
end
if s.steps and path_keeps_array(s) then
return true
end
end
return false
end

-- Singleton unwrapping applied at the boundary of path/array results.
local function finalize_sequence(seq, keep_singleton)
if #seq == 0 then
Expand Down Expand Up @@ -750,6 +763,7 @@ local function _evaluate(node, input, env)
end
return V.obj_get(input, node.value)
elseif t == "path" then
local keep = path_keeps_array(node)
local tuple_mode = false
for _, s in ipairs(node.steps) do
if s.tuple then
Expand All @@ -762,9 +776,9 @@ local function _evaluate(node, input, env)
if node.tuple then
return seq -- tuple stream for an enclosing tuple step; no unwrap
end
return finalize_sequence(seq, false)
return finalize_sequence(seq, keep)
end
return finalize_sequence(eval_path(node, input, env), false)
return finalize_sequence(eval_path(node, input, env), keep)
elseif t == "array" then
local arr = V.array({})
for _, e in ipairs(node.expressions) do
Expand Down
22 changes: 21 additions & 1 deletion src/jsonata/parser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ do
expressions[#expressions + 1] = p.expression(0)
while p.node.id == ";" do
p.advance()
if p.node.id == ")" then
break
end
expressions[#expressions + 1] = p.expression(0)
end
end
Expand Down Expand Up @@ -257,6 +260,10 @@ do
return { type = "array", expressions = expressions, position = t.position }
end
s.led = function(p, t, left)
if p.node.id == "]" then
p.advance()
return { type = "predicate", expr = left, keepArray = true, position = t.position }
end
local filter = p.expression(0)
if p.node.id ~= "]" then
errors.raise("S0203", { position = p.node.position, token = "]" })
Expand Down Expand Up @@ -674,7 +681,6 @@ process_ast = function(ast, ctx)
end
if ast.type == "predicate" then
local target = process_ast(ast.expr, ctx)
local filter = process_ast(ast.filter, ctx)
local step, path
if target.type == "path" then
path = target
Expand All @@ -683,6 +689,12 @@ process_ast = function(ast, ctx)
step = target
path = { type = "path", steps = { target }, position = ast.position }
end
if ast.keepArray then
step.keepArray = true
push_ancestry(path, step)
return path
end
local filter = process_ast(ast.filter, ctx)
if filter.seekingParent ~= nil then
for _, slot in ipairs(filter.seekingParent) do
if slot.level == 1 then
Expand Down Expand Up @@ -737,6 +749,14 @@ process_ast = function(ast, ctx)
if ast.type == "binary" and ast.value == "." then
local steps = {}
flatten_path(ast, steps, ctx)
-- A string step after the path head selects a field (jsonata: `"x".y` ≡ `x.y`).
-- i=1 also covers a first-position quoted string. (Edge: `("x").y` collapses to a
-- bare string in the parser and is wrongly treated as a field here — obscure, accepted.)
for i = 1, #steps do
if steps[i].type == "string" then
steps[i] = { type = "name", value = steps[i].value, position = steps[i].position }
end
end
local path = { type = "path", steps = steps, position = ast.position }
resolve_ancestry(path, ctx)
return path
Expand Down
Loading