· 8 years ago · Nov 26, 2017, 04:26 PM
1local default_pretty_indent = " "
2local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent }
3
4local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray
5local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject
6
7
8function OBJDEF:newArray(tbl)
9 return setmetatable(tbl or {}, isArray)
10end
11
12function OBJDEF:newObject(tbl)
13 return setmetatable(tbl or {}, isObject)
14end
15
16local function unicode_codepoint_as_utf8(codepoint)
17 --
18 -- codepoint is a number
19 --
20 if codepoint <= 127 then
21 return string.char(codepoint)
22
23 elseif codepoint <= 2047 then
24 --
25 -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8
26 --
27 local highpart = math.floor(codepoint / 0x40)
28 local lowpart = codepoint - (0x40 * highpart)
29 return string.char(0xC0 + highpart,
30 0x80 + lowpart)
31
32 elseif codepoint <= 65535 then
33 --
34 -- 1110yyyy 10yyyyxx 10xxxxxx
35 --
36 local highpart = math.floor(codepoint / 0x1000)
37 local remainder = codepoint - 0x1000 * highpart
38 local midpart = math.floor(remainder / 0x40)
39 local lowpart = remainder - 0x40 * midpart
40
41 highpart = 0xE0 + highpart
42 midpart = 0x80 + midpart
43 lowpart = 0x80 + lowpart
44
45 --
46 -- Check for an invalid character (thanks Andy R. at Adobe).
47 -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070
48 --
49 if ( highpart == 0xE0 and midpart < 0xA0 ) or
50 ( highpart == 0xED and midpart > 0x9F ) or
51 ( highpart == 0xF0 and midpart < 0x90 ) or
52 ( highpart == 0xF4 and midpart > 0x8F )
53 then
54 return "?"
55 else
56 return string.char(highpart,
57 midpart,
58 lowpart)
59 end
60
61 else
62 --
63 -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx
64 --
65 local highpart = math.floor(codepoint / 0x40000)
66 local remainder = codepoint - 0x40000 * highpart
67 local midA = math.floor(remainder / 0x1000)
68 remainder = remainder - 0x1000 * midA
69 local midB = math.floor(remainder / 0x40)
70 local lowpart = remainder - 0x40 * midB
71
72 return string.char(0xF0 + highpart,
73 0x80 + midA,
74 0x80 + midB,
75 0x80 + lowpart)
76 end
77end
78
79function OBJDEF:onDecodeError(message, text, location, etc)
80 if text then
81 if location then
82 message = string.format("%s at char %d of: %s", message, location, text)
83 else
84 message = string.format("%s: %s", message, text)
85 end
86 end
87
88 if etc ~= nil then
89 message = message .. " (" .. OBJDEF:encode(etc) .. ")"
90 end
91
92 if self.assert then
93 self.assert(false, message)
94 else
95 assert(false, message)
96 end
97end
98
99OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError
100OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError
101
102function OBJDEF:onEncodeError(message, etc)
103 if etc ~= nil then
104 message = message .. " (" .. OBJDEF:encode(etc) .. ")"
105 end
106
107 if self.assert then
108 self.assert(false, message)
109 else
110 assert(false, message)
111 end
112end
113
114local function grok_number(self, text, start, etc)
115 --
116 -- Grab the integer part
117 --
118 local integer_part = text:match('^-?[1-9]%d*', start)
119 or text:match("^-?0", start)
120
121 if not integer_part then
122 self:onDecodeError("expected number", text, start, etc)
123 end
124
125 local i = start + integer_part:len()
126
127 --
128 -- Grab an optional decimal part
129 --
130 local decimal_part = text:match('^%.%d+', i) or ""
131
132 i = i + decimal_part:len()
133
134 --
135 -- Grab an optional exponential part
136 --
137 local exponent_part = text:match('^[eE][-+]?%d+', i) or ""
138
139 i = i + exponent_part:len()
140
141 local full_number_text = integer_part .. decimal_part .. exponent_part
142 local as_number = tonumber(full_number_text)
143
144 if not as_number then
145 self:onDecodeError("bad number", text, start, etc)
146 end
147
148 return as_number, i
149end
150
151
152local function grok_string(self, text, start, etc)
153
154 if text:sub(start,start) ~= '"' then
155 self:onDecodeError("expected string's opening quote", text, start, etc)
156 end
157
158 local i = start + 1 -- +1 to bypass the initial quote
159 local text_len = text:len()
160 local VALUE = ""
161 while i <= text_len do
162 local c = text:sub(i,i)
163 if c == '"' then
164 return VALUE, i + 1
165 end
166 if c ~= '\\' then
167 VALUE = VALUE .. c
168 i = i + 1
169 elseif text:match('^\\b', i) then
170 VALUE = VALUE .. "\b"
171 i = i + 2
172 elseif text:match('^\\f', i) then
173 VALUE = VALUE .. "\f"
174 i = i + 2
175 elseif text:match('^\\n', i) then
176 VALUE = VALUE .. "\n"
177 i = i + 2
178 elseif text:match('^\\r', i) then
179 VALUE = VALUE .. "\r"
180 i = i + 2
181 elseif text:match('^\\t', i) then
182 VALUE = VALUE .. "\t"
183 i = i + 2
184 else
185 local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i)
186 if hex then
187 i = i + 6 -- bypass what we just read
188
189 -- We have a Unicode codepoint. It could be standalone, or if in the proper range and
190 -- followed by another in a specific range, it'll be a two-code surrogate pair.
191 local codepoint = tonumber(hex, 16)
192 if codepoint >= 0xD800 and codepoint <= 0xDBFF then
193 -- it's a hi surrogate... see whether we have a following low
194 local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i)
195 if lo_surrogate then
196 i = i + 6 -- bypass the low surrogate we just read
197 codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16)
198 else
199 -- not a proper low, so we'll just leave the first codepoint as is and spit it out.
200 end
201 end
202 VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint)
203
204 else
205
206 -- just pass through what's escaped
207 VALUE = VALUE .. text:match('^\\(.)', i)
208 i = i + 2
209 end
210 end
211 end
212
213 self:onDecodeError("unclosed string", text, start, etc)
214end
215
216local function skip_whitespace(text, start)
217
218 local _, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2
219 if match_end then
220 return match_end + 1
221 else
222 return start
223 end
224end
225
226local grok_one -- assigned later
227
228local function grok_object(self, text, start, etc)
229 if text:sub(start,start) ~= '{' then
230 self:onDecodeError("expected '{'", text, start, etc)
231 end
232
233 local i = skip_whitespace(text, start + 1) -- +1 to skip the '{'
234
235 local VALUE = self.strictTypes and self:newObject { } or { }
236
237 if text:sub(i,i) == '}' then
238 return VALUE, i + 1
239 end
240 local text_len = text:len()
241 while i <= text_len do
242 local key, new_i = grok_string(self, text, i, etc)
243
244 i = skip_whitespace(text, new_i)
245
246 if text:sub(i, i) ~= ':' then
247 self:onDecodeError("expected colon", text, i, etc)
248 end
249
250 i = skip_whitespace(text, i + 1)
251
252 local new_val, new_i = grok_one(self, text, i)
253
254 VALUE[key] = new_val
255
256 --
257 -- Expect now either '}' to end things, or a ',' to allow us to continue.
258 --
259 i = skip_whitespace(text, new_i)
260
261 local c = text:sub(i,i)
262
263 if c == '}' then
264 return VALUE, i + 1
265 end
266
267 if text:sub(i, i) ~= ',' then
268 self:onDecodeError("expected comma or '}'", text, i, etc)
269 end
270
271 i = skip_whitespace(text, i + 1)
272 end
273
274 self:onDecodeError("unclosed '{'", text, start, etc)
275end
276
277local function grok_array(self, text, start, etc)
278 if text:sub(start,start) ~= '[' then
279 self:onDecodeError("expected '['", text, start, etc)
280 end
281
282 local i = skip_whitespace(text, start + 1) -- +1 to skip the '['
283 local VALUE = self.strictTypes and self:newArray { } or { }
284 if text:sub(i,i) == ']' then
285 return VALUE, i + 1
286 end
287
288 local VALUE_INDEX = 1
289
290 local text_len = text:len()
291 while i <= text_len do
292 local val, new_i = grok_one(self, text, i)
293
294 -- can't table.insert(VALUE, val) here because it's a no-op if val is nil
295 VALUE[VALUE_INDEX] = val
296 VALUE_INDEX = VALUE_INDEX + 1
297
298 i = skip_whitespace(text, new_i)
299
300 --
301 -- Expect now either ']' to end things, or a ',' to allow us to continue.
302 --
303 local c = text:sub(i,i)
304 if c == ']' then
305 return VALUE, i + 1
306 end
307 if text:sub(i, i) ~= ',' then
308 self:onDecodeError("expected comma or '['", text, i, etc)
309 end
310 i = skip_whitespace(text, i + 1)
311 end
312 self:onDecodeError("unclosed '['", text, start, etc)
313end
314
315
316grok_one = function(self, text, start, etc)
317 -- Skip any whitespace
318 start = skip_whitespace(text, start)
319
320 if start > text:len() then
321 self:onDecodeError("unexpected end of string", text, nil, etc)
322 end
323
324 if text:find('^"', start) then
325 return grok_string(self, text, start, etc)
326
327 elseif text:find('^[-0123456789 ]', start) then
328 return grok_number(self, text, start, etc)
329
330 elseif text:find('^%{', start) then
331 return grok_object(self, text, start, etc)
332
333 elseif text:find('^%[', start) then
334 return grok_array(self, text, start, etc)
335
336 elseif text:find('^true', start) then
337 return true, start + 4
338
339 elseif text:find('^false', start) then
340 return false, start + 5
341
342 elseif text:find('^null', start) then
343 return nil, start + 4
344
345 else
346 self:onDecodeError("can't parse JSON", text, start, etc)
347 end
348end
349
350function OBJDEF:decode(text, etc)
351 if type(self) ~= 'table' or self.__index ~= OBJDEF then
352 OBJDEF:onDecodeError("JSON:decode must be called in method format", nil, nil, etc)
353 end
354
355 if text == nil then
356 self:onDecodeOfNilError(string.format("nil passed to JSON:decode()"), nil, nil, etc)
357 elseif type(text) ~= 'string' then
358 self:onDecodeError(string.format("expected string argument to JSON:decode(), got %s", type(text)), nil, nil, etc)
359 end
360
361 if text:match('^%s*$') then
362 return nil
363 end
364
365 if text:match('^%s*<') then
366 -- Can't be JSON... we'll assume it's HTML
367 self:onDecodeOfHTMLError(string.format("html passed to JSON:decode()"), text, nil, etc)
368 end
369
370 --
371 -- Ensure that it's not UTF-32 or UTF-16.
372 -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3),
373 -- but this package can't handle them.
374 --
375 if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then
376 self:onDecodeError("JSON package groks only UTF-8, sorry", text, nil, etc)
377 end
378
379 local success, value = pcall(grok_one, self, text, 1, etc)
380
381 if success then
382 return value
383 else
384 -- if JSON:onDecodeError() didn't abort out of the pcall, we'll have received the error message here as "value", so pass it along as an assert.
385 if self.assert then
386 self.assert(false, value)
387 else
388 assert(false, value)
389 end
390 -- and if we're still here, return a nil and throw the error message on as a second arg
391 return nil, value
392 end
393end
394
395local function backslash_replacement_function(c)
396 if c == "\n" then
397 return "\\n"
398 elseif c == "\r" then
399 return "\\r"
400 elseif c == "\t" then
401 return "\\t"
402 elseif c == "\b" then
403 return "\\b"
404 elseif c == "\f" then
405 return "\\f"
406 elseif c == '"' then
407 return '\\"'
408 elseif c == '\\' then
409 return '\\\\'
410 else
411 return string.format("\\u%04x", c:byte())
412 end
413end
414
415local chars_to_be_escaped_in_JSON_string
416 = '['
417 .. '"' -- class sub-pattern to match a double quote
418 .. '%\\' -- class sub-pattern to match a backslash
419 .. '%z' -- class sub-pattern to match a null
420 .. '\001' .. '-' .. '\031' -- class sub-pattern to match control characters
421 .. ']'
422
423local function json_string_literal(value)
424 local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function)
425 return '"' .. newval .. '"'
426end
427
428local function object_or_array(self, T, etc)
429 --
430 -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON
431 -- object. If there are only numbers, it's a JSON array.
432 --
433 -- If we'll be converting to a JSON object, we'll want to sort the keys so that the
434 -- end result is deterministic.
435 --
436 local string_keys = { }
437 local number_keys = { }
438 local number_keys_must_be_strings = false
439 local maximum_number_key
440
441 for key in pairs(T) do
442 if type(key) == 'string' then
443 table.insert(string_keys, key)
444 elseif type(key) == 'number' then
445 table.insert(number_keys, key)
446 if key <= 0 or key >= math.huge then
447 number_keys_must_be_strings = true
448 elseif not maximum_number_key or key > maximum_number_key then
449 maximum_number_key = key
450 end
451 else
452 self:onEncodeError("can't encode table with a key of type " .. type(key), etc)
453 end
454 end
455
456 if #string_keys == 0 and not number_keys_must_be_strings then
457 --
458 -- An empty table, or a numeric-only array
459 --
460 if #number_keys > 0 then
461 return nil, maximum_number_key -- an array
462 elseif tostring(T) == "JSON array" then
463 return nil
464 elseif tostring(T) == "JSON object" then
465 return { }
466 else
467 -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects
468 return nil
469 end
470 end
471
472 table.sort(string_keys)
473
474 local map
475 if #number_keys > 0 then
476 --
477 -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array
478 -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object.
479 --
480
481 if self.noKeyConversion then
482 self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc)
483 end
484
485 --
486 -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings
487 --
488 map = { }
489 for key, val in pairs(T) do
490 map[key] = val
491 end
492
493 table.sort(number_keys)
494
495 --
496 -- Throw numeric keys in there as strings
497 --
498 for _, number_key in ipairs(number_keys) do
499 local string_key = tostring(number_key)
500 if map[string_key] == nil then
501 table.insert(string_keys , string_key)
502 map[string_key] = T[number_key]
503 else
504 self:onEncodeError("conflict converting table with mixed-type keys into a JSON object: key " .. number_key .. " exists both as a string and a number.", etc)
505 end
506 end
507 end
508
509 return string_keys, nil, map
510end
511
512--
513-- Encode
514--
515-- 'options' is nil, or a table with possible keys:
516-- pretty -- if true, return a pretty-printed version
517-- indent -- a string (usually of spaces) used to indent each nested level
518-- align_keys -- if true, align all the keys when formatting a table
519--
520local encode_value -- must predeclare because it calls itself
521function encode_value(self, value, parents, etc, options, indent)
522
523 if value == nil then
524 return 'null'
525
526 elseif type(value) == 'string' then
527 return json_string_literal(value)
528
529 elseif type(value) == 'number' then
530 if value ~= value then
531 --
532 -- NaN (Not a Number).
533 -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option.
534 --
535 return "null"
536 elseif value >= math.huge then
537 --
538 -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should
539 -- really be a package option. Note: at least with some implementations, positive infinity
540 -- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is.
541 -- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">="
542 -- case first.
543 --
544 return "1e+9999"
545 elseif value <= -math.huge then
546 --
547 -- Negative infinity.
548 -- JSON has no INF, so we have to fudge the best we can. This should really be a package option.
549 --
550 return "-1e+9999"
551 else
552 return tostring(value)
553 end
554
555 elseif type(value) == 'boolean' then
556 return tostring(value)
557
558 elseif type(value) ~= 'table' then
559 self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc)
560
561 else
562 --
563 -- A table to be converted to either a JSON object or array.
564 --
565 local T = value
566
567 if type(options) ~= 'table' then
568 options = {}
569 end
570 if type(indent) ~= 'string' then
571 indent = ""
572 end
573
574 if parents[T] then
575 self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc)
576 else
577 parents[T] = true
578 end
579
580 local result_value
581
582 local object_keys, maximum_number_key, map = object_or_array(self, T, etc)
583 if maximum_number_key then
584 --
585 -- An array...
586 --
587 local ITEMS = { }
588 for i = 1, maximum_number_key do
589 table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, indent))
590 end
591
592 if options.pretty then
593 result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]"
594 else
595 result_value = "[" .. table.concat(ITEMS, ",") .. "]"
596 end
597
598 elseif object_keys then
599 --
600 -- An object
601 --
602 local TT = map or T
603
604 if options.pretty then
605
606 local KEYS = { }
607 local max_key_length = 0
608 for _, key in ipairs(object_keys) do
609 local encoded = encode_value(self, tostring(key), parents, etc, options, indent)
610 if options.align_keys then
611 max_key_length = math.max(max_key_length, #encoded)
612 end
613 table.insert(KEYS, encoded)
614 end
615 local key_indent = indent .. tostring(options.indent or "")
616 local subtable_indent = key_indent .. string.rep(" ", max_key_length) .. (options.align_keys and " " or "")
617 local FORMAT = "%s%" .. string.format("%d", max_key_length) .. "s: %s"
618
619 local COMBINED_PARTS = { }
620 for i, key in ipairs(object_keys) do
621 local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent)
622 table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val))
623 end
624 result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}"
625
626 else
627
628 local PARTS = { }
629 for _, key in ipairs(object_keys) do
630 local encoded_val = encode_value(self, TT[key], parents, etc, options, indent)
631 local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent)
632 table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val))
633 end
634 result_value = "{" .. table.concat(PARTS, ",") .. "}"
635
636 end
637 else
638 --
639 -- An empty array/object... we'll treat it as an array, though it should really be an option
640 --
641 result_value = "[]"
642 end
643
644 parents[T] = false
645 return result_value
646 end
647end
648
649
650function OBJDEF:encode(value, etc, options)
651 if type(self) ~= 'table' or self.__index ~= OBJDEF then
652 OBJDEF:onEncodeError("JSON:encode must be called in method format", etc)
653 end
654 return encode_value(self, value, {}, etc, options or nil)
655end
656
657function OBJDEF:encode_pretty(value, etc, options)
658 if type(self) ~= 'table' or self.__index ~= OBJDEF then
659 OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc)
660 end
661 return encode_value(self, value, {}, etc, options or default_pretty_options)
662end
663
664function OBJDEF.__tostring()
665 return "JSON encode/decode package"
666end
667
668OBJDEF.__index = OBJDEF
669
670function OBJDEF:new(args)
671 local new = { }
672
673 if args then
674 for key, val in pairs(args) do
675 new[key] = val
676 end
677 end
678
679 return setmetatable(new, OBJDEF)
680end
681
682return OBJDEF:new()