Skip to content
Closed
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
17 changes: 7 additions & 10 deletions lua/qjson/table.lua
Original file line number Diff line number Diff line change
Expand Up @@ -344,21 +344,18 @@ LazyObject.__newindex = function(t, k, v)
end
end
else
-- Encode the new value (we need both encoded and lua_value)
local encoded = encode(v)

-- Update or add patch
-- Update or add patch (re-encode lua_value at emit time for correctness
-- against mutable post-assignment mutations like t.a = {}; t.a.x = 1).
local found = false
for _, p in ipairs(patches) do
if p.key == k then
p.encoded_value = encoded
p.lua_value = v
found = true
break
end
end
if not found then
patches[#patches + 1] = { key = k, encoded_value = encoded, lua_value = v }
patches[#patches + 1] = { key = k, lua_value = v }
end

-- Remove from deleted if previously deleted
Expand Down Expand Up @@ -602,8 +599,8 @@ local function encode_lazy_object_walking_with_patches(t, patches, deleted)
local v
local patch = patched_keys[k]
if patch then
-- Use patched value (already encoded)
parts[#parts + 1] = encode_string(k) .. ":" .. patch.encoded_value
-- Re-encode at emit time so mutations after assignment are reflected.
parts[#parts + 1] = encode_string(k) .. ":" .. encode(patch.lua_value)
else
-- Use original or cached value
local cached = rawget(t, k)
Expand All @@ -622,7 +619,7 @@ local function encode_lazy_object_walking_with_patches(t, patches, deleted)
for _, p in ipairs(patches) do
local rc = C.qjson_cursor_field_bytes(t._cur, p.key, #p.key, sz_a, sz_b)
if rc == QJSON_NOT_FOUND then
parts[#parts + 1] = encode_string(p.key) .. ":" .. p.encoded_value
parts[#parts + 1] = encode_string(p.key) .. ":" .. encode(p.lua_value)
elseif rc ~= QJSON_OK then
check(rc) -- propagate unexpected errors
end
Expand All @@ -649,7 +646,7 @@ local function encode_with_patches(t)
replacements[#replacements + 1] = {
start = tonumber(sz_a[0]),
end_ = tonumber(sz_b[0]),
value = p.encoded_value,
value = encode(p.lua_value), -- re-encode at emit time
}
elseif rc == QJSON_NOT_FOUND then
has_new_field = true
Expand Down
15 changes: 14 additions & 1 deletion tests/lua/lazy_patch_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,23 @@ describe("Lazy Patch - type changes", function()
assert.are.equal(10, parsed.a.x)
end)

it("mutation of patch value after assignment is reflected in encode", function()
-- Regression: encoded_value was cached at assignment time; mutations to
-- the patched table were invisible to encode because it used the stale
-- encoded_value instead of re-encoding lua_value at emit time.
local t = qjson.decode('{"a":1}')
t.a = {x = 1} -- records patch; lua_value is the table {x=1}
t.a.x = 2 -- mutates the table in lua_value; must show up in encode
local out = qjson.encode(t)
local cjson = require("cjson")
local parsed = cjson.decode(out)
assert.are.equal(2, parsed.a.x)
end)

it("changes scalar to array", function()
local t = qjson.decode('{"a":1}')
t.a = {10, 20, 30}
local out = qjson.encode(t)

local cjson = require("cjson")
local parsed = cjson.decode(out)
assert.are.same({10, 20, 30}, parsed.a)
Expand Down
Loading