diff --git a/apisix/core/json.lua b/apisix/core/json.lua index 397b80191e13..de550b42cbc2 100644 --- a/apisix/core/json.lua +++ b/apisix/core/json.lua @@ -33,6 +33,46 @@ local cached_tab = {} cjson.encode_escape_forward_slash(false) cjson.decode_array_with_array_mt(true) + + +local dkjson = require("dkjson") +local dkjson_null = dkjson.null + + +local function convert_cjson_null(data, seen) + if data == cjson_null then + return dkjson_null + end + + if type(data) ~= "table" then + return data + end + + seen = seen or {} + if seen[data] then + return data + end + seen[data] = true + + local t = {} + for k, v in pairs(data) do + t[convert_cjson_null(k, seen)] = convert_cjson_null(v, seen) + end + + return t +end + + +local function stably_encode(data) + if data == cjson_null then + data = dkjson_null + elseif type(data) == "table" then + data = convert_cjson_null(data) + end + return dkjson.encode(data) +end + + local _M = { version = 0.1, array_mt = cjson.array_mt, @@ -40,7 +80,7 @@ local _M = { -- This method produces the same encoded string when the input is not changed. -- Different calls with cjson.encode will produce different string because -- it doesn't maintain the object key order. - stably_encode = require("dkjson").encode + stably_encode = stably_encode } diff --git a/t/core/json.t b/t/core/json.t index 1409f54f1e7b..39ae57624eab 100644 --- a/t/core/json.t +++ b/t/core/json.t @@ -193,3 +193,59 @@ b.d: 1 e: text a or default: default top-level null: nil + + + +=== TEST 9: stably_encode with cjson.null +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local data = {a = 1, b = core.json.null} + ngx.say(core.json.stably_encode(data)) + } + } +--- response_body +{"a":1,"b":null} + + + +=== TEST 10: stably_encode with nested cjson.null +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local data = {outer = {inner = core.json.null, val = "test"}} + ngx.say(core.json.stably_encode(data)) + } + } +--- response_body +{"outer":{"inner":null,"val":"test"}} + + + +=== TEST 11: stably_encode stability with cjson.null +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local data1 = {z = 1, a = core.json.null} + local data2 = {a = core.json.null, z = 1} + ngx.say(core.json.stably_encode(data1) == core.json.stably_encode(data2)) + } + } +--- response_body +true + + + +=== TEST 12: stably_encode top-level cjson.null +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + ngx.say(core.json.stably_encode(core.json.null)) + } + } +--- response_body +null