Skip to content

Commit aab4f64

Browse files
committed
Fix number encoding & quadratic complexity
1 parent dbf4b2d commit aab4f64

File tree

1 file changed

+39
-24
lines changed

1 file changed

+39
-24
lines changed

json.lua

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,16 @@ local function escape_char(c)
5151
end
5252

5353

54-
local function encode_nil(val)
55-
return "null"
54+
local function encode_nil(rope)
55+
rope[#rope + 1] = "null"
5656
end
5757

5858

59-
local function encode_table(val, stack)
60-
local res = {}
61-
stack = stack or {}
62-
59+
local function encode_table(rope, val, stack)
6360
-- Circular reference?
6461
if stack[val] then error("circular reference") end
6562

6663
stack[val] = true
67-
6864
if rawget(val, 1) ~= nil or next(val) == nil then
6965
-- Treat as array -- check keys are valid and it is not sparse
7066
local n = 0
@@ -78,61 +74,80 @@ local function encode_table(val, stack)
7874
error("invalid table: sparse array")
7975
end
8076
-- Encode
77+
rope[#rope + 1] = "["
8178
for i, v in ipairs(val) do
82-
table.insert(res, encode(v, stack))
79+
if i > 1 then
80+
rope[#rope + 1] = ","
81+
end
82+
encode(rope, v, stack)
8383
end
84-
stack[val] = nil
85-
return "[" .. table.concat(res, ",") .. "]"
86-
84+
rope[#rope + 1] = "]"
8785
else
8886
-- Treat as an object
87+
rope[#rope + 1] = "{"
88+
local first = true
8989
for k, v in pairs(val) do
9090
if type(k) ~= "string" then
9191
error("invalid table: mixed or invalid key types")
9292
end
93-
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
93+
if not first then
94+
rope[#rope + 1] = ","
95+
end
96+
encode(rope, k, stack)
97+
rope[#rope + 1] = ":"
98+
encode(rope, v, stack)
99+
first = false
94100
end
95-
stack[val] = nil
96-
return "{" .. table.concat(res, ",") .. "}"
101+
rope[#rope + 1] = "}"
97102
end
103+
stack[val] = nil
98104
end
99105

100106

101-
local function encode_string(val)
102-
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
107+
local function encode_string(rope, val)
108+
rope[#rope + 1] = '"'
109+
rope[#rope + 1] = val:gsub('[%z\1-\31\\"]', escape_char)
110+
rope[#rope + 1] = '"'
103111
end
104112

105113

106-
local function encode_number(val)
114+
local function encode_number(rope, val)
107115
-- Check for NaN, -inf and inf
108116
if val ~= val or val <= -math.huge or val >= math.huge then
109117
error("unexpected number value '" .. tostring(val) .. "'")
110118
end
111-
return string.format("%.14g", val)
119+
-- See www.cs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF
120+
-- 17 digits suffice to losslessly represent 64-bit IEEE754 floats
121+
rope[#rope + 1] = ("%.17g"):format(val)
112122
end
113123

124+
local function encode_boolean(rope, val)
125+
rope[#rope + 1] = val and "true" or "false"
126+
end
114127

115128
local type_func_map = {
116129
[ "nil" ] = encode_nil,
117130
[ "table" ] = encode_table,
118131
[ "string" ] = encode_string,
119132
[ "number" ] = encode_number,
120-
[ "boolean" ] = tostring,
133+
[ "boolean" ] = encode_boolean,
121134
}
122135

123136

124-
encode = function(val, stack)
137+
function encode(rope, val, stack)
125138
local t = type(val)
126-
local f = type_func_map[t]
127-
if f then
128-
return f(val, stack)
139+
local encoder = type_func_map[t]
140+
if encoder then
141+
return encoder(rope, val, stack)
129142
end
130143
error("unexpected type '" .. t .. "'")
131144
end
132145

133146

134147
function json.encode(val)
135-
return ( encode(val) )
148+
local rope = {}
149+
encode(rope, val, {})
150+
return table.concat(rope)
136151
end
137152

138153

0 commit comments

Comments
 (0)