json.lua icon indicating copy to clipboard operation
json.lua copied to clipboard

Empty object encodes as array

Open tmpgnh opened this issue 4 years ago • 2 comments

% lua
Lua 5.3.3  Copyright (C) 1994-2016 Lua.org, PUC-Rio
> json=require'json'
> json.encode(json.decode('{}'))
[]

Why doesn't return '{}'? (Maybe related to https://github.com/rxi/json.lua/issues/19)

Thanks,

tmpgnh avatar Apr 07 '20 09:04 tmpgnh

This is expected behaviour, there's a few reasons this choice was made though:

In Lua there's no distinction between an "array" and a "map". When json.lua encounters a table it tries to determine first if it's a valid array -- it does so by checking to make sure it doesn't contain any non-numerical keys, and that it contains the keys in the inclusive range 1 to n without any gaps -- by this definition an empty table is considered an array.

This behaviour was chosen as in typical cases you won't be encoding an empty object, and, if you are, in many cases that object can be stored as an array and treated like an object once loaded (lua, javascript). Typically if you have an empty table you're writing to JSON in lua it's a table that you're using as an array.

The library makes only the following guarantees: that valid JSON will decode correctly, that encoded JSON will be valid, and that data encoded with json.encode() will result in the same data when json.decode() is used.

If you want to force your empty table to be encoded as an object, a simple work around might be to add a dummy value to it, for example:

json.encode { _ = false } -- => {"_":false}

rxi avatar Apr 07 '20 09:04 rxi

Thank you for the explanation. I actually felt that the second guarantee

data encoded with json.encode() will result in the same data when json.decode() is used

is being violated---cf. the empty JSON object '{}'. For my use case which parses and then returns JSON I'm thinking to patch json.lua along these lines:

@@ -56,6 +56,9 @@
 end
 
 
+local json_object_tag = {}
+
+
 local function encode_table(val, stack)
   local res = {}
   stack = stack or {}
@@ -65,7 +68,7 @@
 
   stack[val] = true
 
-  if rawget(val, 1) ~= nil or next(val) == nil then
+  if getmetatable(val) ~= json_object_tag and (rawget(val, 1) ~= nil or next(val) == nil) then
     -- Treat as array -- check keys are valid and it is not sparse
     local n = 0
     for k in pairs(val) do
@@ -337,6 +340,7 @@
     if chr == "}" then break end
     if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
   end
+  setmetatable(res, json_object_tag)
   return res, i
 end

tmpgnh avatar Apr 07 '20 10:04 tmpgnh