2 local always_use_lpeg
= false
3 local register_global_module_table
= false
4 local global_module_name
= 'json'
8 David Kolf
's JSON module for Lua 5.1 - 5.4
13 For the documentation see the corresponding readme.txt or visit
14 <http://dkolf.de/dkjson-lua/>.
16 You can contact the author by sending an e-mail to 'david
' at the
20 Copyright (C) 2010-2024 David Heiko Kolf
22 Permission is hereby granted, free of charge, to any person obtaining
23 a copy of this software and associated documentation files (the
24 "Software"), to deal in the Software without restriction, including
25 without limitation the rights to use, copy, modify, merge, publish,
26 distribute, sublicense, and/or sell copies of the Software, and to
27 permit persons to whom the Software is furnished to do so, subject to
28 the following conditions:
30 The above copyright notice and this permission notice shall be
31 included in all copies or substantial portions of the Software.
33 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
34 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
35 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
36 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
37 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
38 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
39 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44 -- global dependencies:
45 local pairs, type, tostring, tonumber, getmetatable, setmetatable =
46 pairs, type, tostring, tonumber, getmetatable, setmetatable
47 local error, require, pcall, select = error, require, pcall, select
48 local floor, huge = math.floor, math.huge
49 local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
50 string.rep, string.gsub, string.sub, string.byte, string.char,
51 string.find, string.len, string.format
52 local strmatch = string.match
53 local concat = table.concat
55 local json = { version = "dkjson 2.8" }
59 if register_global_module_table then
60 if always_use_lpeg then
61 _G[global_module_name] = jsonlpeg
63 _G[global_module_name] = json
67 local _ENV = nil -- blocking globals in Lua 5.2 and later
70 -- Enable access to blocked metatables.
71 -- Don't worry
, this module doesn
't change anything in them.
72 local debmeta = require "debug".getmetatable
73 if debmeta then getmetatable = debmeta end
76 json.null = setmetatable ({}, {
77 __tojson = function () return "null" end
80 local function isarray (tbl)
81 local max, n, arraylen = 0, 0, 0
82 for k,v in pairs (tbl) do
83 if k == 'n
' and type(v) == 'number' then
89 if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
98 if max > 10 and max > arraylen and max > n * 2 then
99 return false -- don't create an array with too many holes
104 local escapecodes
= {
105 ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
106 ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
109 local function escapeutf8 (uchar
)
110 local value
= escapecodes
[uchar
]
114 local a
, b
, c
, d
= strbyte (uchar
, 1, 4)
115 a
, b
, c
, d
= a
or 0, b
or 0, c
or 0, d
or 0
118 elseif 0xc0 <= a
and a
<= 0xdf and b
>= 0x80 then
119 value
= (a
- 0xc0) * 0x40 + b
- 0x80
120 elseif 0xe0 <= a
and a
<= 0xef and b
>= 0x80 and c
>= 0x80 then
121 value
= ((a
- 0xe0) * 0x40 + b
- 0x80) * 0x40 + c
- 0x80
122 elseif 0xf0 <= a
and a
<= 0xf7 and b
>= 0x80 and c
>= 0x80 and d
>= 0x80 then
123 value
= (((a
- 0xf0) * 0x40 + b
- 0x80) * 0x40 + c
- 0x80) * 0x40 + d
- 0x80
127 if value
<= 0xffff then
128 return strformat ("\\u%.4x", value
)
129 elseif value
<= 0x10ffff then
130 -- encode as UTF-16 surrogate pair
131 value
= value
- 0x10000
132 local highsur
, lowsur
= 0xD800 + floor (value
/0x400), 0xDC00 + (value
% 0x400)
133 return strformat ("\\u%.4x\\u%.4x", highsur
, lowsur
)
139 local function fsub (str
, pattern
, repl
)
140 -- gsub always builds a new string in a buffer, even when no match
141 -- exists. First using find should be more efficient when most strings
142 -- don't contain the pattern.
143 if strfind (str
, pattern
) then
144 return gsub (str
, pattern
, repl
)
150 local function quotestring (value
)
151 -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
152 value
= fsub (value
, "[%z\1-\31\"\\\127]", escapeutf8
)
153 if strfind (value
, "[\194\216\220\225\226\239]") then
154 value
= fsub (value
, "\194[\128-\159\173]", escapeutf8
)
155 value
= fsub (value
, "\216[\128-\132]", escapeutf8
)
156 value
= fsub (value
, "\220\143", escapeutf8
)
157 value
= fsub (value
, "\225\158[\180\181]", escapeutf8
)
158 value
= fsub (value
, "\226\128[\140-\143\168-\175]", escapeutf8
)
159 value
= fsub (value
, "\226\129[\160-\175]", escapeutf8
)
160 value
= fsub (value
, "\239\187\191", escapeutf8
)
161 value
= fsub (value
, "\239\191[\176-\191]", escapeutf8
)
163 return "\"" .. value
.. "\""
165 json
.quotestring
= quotestring
167 local function replace(str
, o
, n
)
168 local i
, j
= strfind (str
, o
, 1, true)
170 return strsub(str
, 1, i
-1) .. n
.. strsub(str
, j
+1, -1)
176 -- locale independent num2str and str2num functions
177 local decpoint
, numfilter
179 local function updatedecpoint ()
180 decpoint
= strmatch(tostring(0.5), "([^05+])")
181 -- build a filter that can be used to remove group separators
182 numfilter
= "[^0-9%-%+eE" .. gsub(decpoint
, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
187 local function num2str (num
)
188 return replace(fsub(tostring(num
), numfilter
, ""), decpoint
, ".")
191 local function str2num (str
)
192 local num
= tonumber(replace(str
, ".", decpoint
))
195 num
= tonumber(replace(str
, ".", decpoint
))
200 local function addnewline2 (level
, buffer
, buflen
)
201 buffer
[buflen
+1] = "\n"
202 buffer
[buflen
+2] = strrep (" ", level
)
207 function json
.addnewline (state
)
209 state
.bufferlen
= addnewline2 (state
.level
or 0,
210 state
.buffer
, state
.bufferlen
or #(state
.buffer
))
214 local encode2
-- forward declaration
216 local function addpair (key
, value
, prev
, indent
, level
, buffer
, buflen
, tables
, globalorder
, state
)
217 local kt
= type (key
)
218 if kt
~= 'string' and kt
~= 'number' then
219 return nil, "type '" .. kt
.. "' is not supported as a key by JSON."
226 buflen
= addnewline2 (level
, buffer
, buflen
)
228 -- When Lua is compiled with LUA_NOCVTN2S this will fail when
229 -- numbers are mixed into the keys of the table. JSON keys are always
230 -- strings, so this would be an implicit conversion too and the failure
232 buffer
[buflen
+1] = quotestring (key
)
233 buffer
[buflen
+2] = ":"
234 return encode2 (value
, indent
, level
, buffer
, buflen
+ 2, tables
, globalorder
, state
)
237 local function appendcustom(res
, buffer
, state
)
238 local buflen
= state
.bufferlen
239 if type (res
) == 'string' then
246 local function exception(reason
, value
, state
, buffer
, buflen
, defaultmessage
)
247 defaultmessage
= defaultmessage
or reason
248 local handler
= state
.exception
250 return nil, defaultmessage
252 state
.bufferlen
= buflen
253 local ret
, msg
= handler (reason
, value
, state
, defaultmessage
)
254 if not ret
then return nil, msg
or defaultmessage
end
255 return appendcustom(ret
, buffer
, state
)
259 function json
.encodeexception(reason
, value
, state
, defaultmessage
)
260 return quotestring("<" .. defaultmessage
.. ">")
263 encode2
= function (value
, indent
, level
, buffer
, buflen
, tables
, globalorder
, state
)
264 local valtype
= type (value
)
265 local valmeta
= getmetatable (value
)
266 valmeta
= type (valmeta
) == 'table' and valmeta
-- only tables
267 local valtojson
= valmeta
and valmeta
.__tojson
269 if tables
[value
] then
270 return exception('reference cycle', value
, state
, buffer
, buflen
)
273 state
.bufferlen
= buflen
274 local ret
, msg
= valtojson (value
, state
)
275 if not ret
then return exception('custom encoder failed', value
, state
, buffer
, buflen
, msg
) end
277 buflen
= appendcustom(ret
, buffer
, state
)
278 elseif value
== nil then
280 buffer
[buflen
] = "null"
281 elseif valtype
== 'number' then
283 if value
~= value
or value
>= huge
or -value
>= huge
then
284 -- This is the behaviour of the original JSON implementation.
291 elseif valtype
== 'boolean' then
293 buffer
[buflen
] = value
and "true" or "false"
294 elseif valtype
== 'string' then
296 buffer
[buflen
] = quotestring (value
)
297 elseif valtype
== 'table' then
298 if tables
[value
] then
299 return exception('reference cycle', value
, state
, buffer
, buflen
)
303 local isa
, n
= isarray (value
)
304 if n
== 0 and valmeta
and valmeta
.__jsontype
== 'object' then
308 if isa
then -- JSON array
312 buflen
, msg
= encode2 (value
[i
], indent
, level
, buffer
, buflen
, tables
, globalorder
, state
)
313 if not buflen
then return nil, msg
end
325 local order
= valmeta
and valmeta
.__jsonorder
or globalorder
334 buflen
, msg
= addpair (k
, v
, prev
, indent
, level
, buffer
, buflen
, tables
, globalorder
, state
)
335 if not buflen
then return nil, msg
end
336 prev
= true -- add a seperator before the next element
339 for k
,v
in pairs (value
) do
341 buflen
, msg
= addpair (k
, v
, prev
, indent
, level
, buffer
, buflen
, tables
, globalorder
, state
)
342 if not buflen
then return nil, msg
end
343 prev
= true -- add a seperator before the next element
347 for k
,v
in pairs (value
) do
348 buflen
, msg
= addpair (k
, v
, prev
, indent
, level
, buffer
, buflen
, tables
, globalorder
, state
)
349 if not buflen
then return nil, msg
end
350 prev
= true -- add a seperator before the next element
354 buflen
= addnewline2 (level
- 1, buffer
, buflen
)
361 return exception ('unsupported type', value
, state
, buffer
, buflen
,
362 "type '" .. valtype
.. "' is not supported by JSON.")
367 function json
.encode (value
, state
)
369 local oldbuffer
= state
.buffer
370 local buffer
= oldbuffer
or {}
371 state
.buffer
= buffer
373 local ret
, msg
= encode2 (value
, state
.indent
, state
.level
or 0,
374 buffer
, state
.bufferlen
or 0, state
.tables
or {}, state
.keyorder
, state
)
377 elseif oldbuffer
== buffer
then
378 state
.bufferlen
= ret
381 state
.bufferlen
= nil
383 return concat (buffer
)
387 local function loc (str
, where
)
388 local line
, pos
, linepos
= 1, 1, 0
390 pos
= strfind (str
, "\n", pos
, true)
391 if pos
and pos
< where
then
399 return strformat ("line %d, column %d", line
, where
- linepos
)
402 local function unterminated (str
, what
, where
)
403 return nil, strlen (str
) + 1, "unterminated " .. what
.. " at " .. loc (str
, where
)
406 local function scanwhite (str
, pos
)
408 pos
= strfind (str
, "%S", pos
)
409 if not pos
then return nil end
410 local sub2
= strsub (str
, pos
, pos
+ 1)
411 if sub2
== "\239\187" and strsub (str
, pos
+ 2, pos
+ 2) == "\191" then
412 -- UTF-8 Byte Order Mark
414 elseif sub2
== "//" then
415 pos
= strfind (str
, "[\n\r]", pos
+ 2)
416 if not pos
then return nil end
417 elseif sub2
== "/*" then
418 pos
= strfind (str
, "*/", pos
+ 2)
419 if not pos
then return nil end
427 local escapechars
= {
428 ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
429 ["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
432 local function unichar (value
)
435 elseif value
<= 0x007f then
436 return strchar (value
)
437 elseif value
<= 0x07ff then
438 return strchar (0xc0 + floor(value
/0x40),
439 0x80 + (floor(value
) % 0x40))
440 elseif value
<= 0xffff then
441 return strchar (0xe0 + floor(value
/0x1000),
442 0x80 + (floor(value
/0x40) % 0x40),
443 0x80 + (floor(value
) % 0x40))
444 elseif value
<= 0x10ffff then
445 return strchar (0xf0 + floor(value
/0x40000),
446 0x80 + (floor(value
/0x1000) % 0x40),
447 0x80 + (floor(value
/0x40) % 0x40),
448 0x80 + (floor(value
) % 0x40))
454 local function scanstring (str
, pos
)
455 local lastpos
= pos
+ 1
456 local buffer
, n
= {}, 0
458 local nextpos
= strfind (str
, "[\"\\]", lastpos
)
460 return unterminated (str
, "string", pos
)
462 if nextpos
> lastpos
then
464 buffer
[n
] = strsub (str
, lastpos
, nextpos
- 1)
466 if strsub (str
, nextpos
, nextpos
) == "\"" then
467 lastpos
= nextpos
+ 1
470 local escchar
= strsub (str
, nextpos
+ 1, nextpos
+ 1)
472 if escchar
== "u" then
473 value
= tonumber (strsub (str
, nextpos
+ 2, nextpos
+ 5), 16)
476 if 0xD800 <= value
and value
<= 0xDBff then
477 -- we have the high surrogate of UTF-16. Check if there is a
478 -- low surrogate escaped nearby to combine them.
479 if strsub (str
, nextpos
+ 6, nextpos
+ 7) == "\\u" then
480 value2
= tonumber (strsub (str
, nextpos
+ 8, nextpos
+ 11), 16)
481 if value2
and 0xDC00 <= value2
and value2
<= 0xDFFF then
482 value
= (value
- 0xD800) * 0x400 + (value2
- 0xDC00) + 0x10000
484 value2
= nil -- in case it was out of range for a low surrogate
488 value
= value
and unichar (value
)
491 lastpos
= nextpos
+ 12
493 lastpos
= nextpos
+ 6
499 value
= escapechars
[escchar
] or escchar
500 lastpos
= nextpos
+ 2
507 return buffer
[1], lastpos
509 return concat (buffer
), lastpos
515 local scanvalue
-- forward declaration
517 local function scantable (what
, closechar
, str
, startpos
, nullval
, objectmeta
, arraymeta
)
519 local pos
= startpos
+ 1
520 if what
== 'object' then
521 setmetatable (tbl
, objectmeta
)
523 setmetatable (tbl
, arraymeta
)
526 pos
= scanwhite (str
, pos
)
527 if not pos
then return unterminated (str
, what
, startpos
) end
528 local char
= strsub (str
, pos
, pos
)
529 if char
== closechar
then
533 val1
, pos
, err
= scanvalue (str
, pos
, nullval
, objectmeta
, arraymeta
)
534 if err
then return nil, pos
, err
end
535 pos
= scanwhite (str
, pos
)
536 if not pos
then return unterminated (str
, what
, startpos
) end
537 char
= strsub (str
, pos
, pos
)
540 return nil, pos
, "cannot use nil as table index (at " .. loc (str
, pos
) .. ")"
542 pos
= scanwhite (str
, pos
+ 1)
543 if not pos
then return unterminated (str
, what
, startpos
) end
545 val2
, pos
, err
= scanvalue (str
, pos
, nullval
, objectmeta
, arraymeta
)
546 if err
then return nil, pos
, err
end
548 pos
= scanwhite (str
, pos
)
549 if not pos
then return unterminated (str
, what
, startpos
) end
550 char
= strsub (str
, pos
, pos
)
561 scanvalue
= function (str
, pos
, nullval
, objectmeta
, arraymeta
)
563 pos
= scanwhite (str
, pos
)
565 return nil, strlen (str
) + 1, "no valid JSON value (reached the end)"
567 local char
= strsub (str
, pos
, pos
)
569 return scantable ('object', "}", str
, pos
, nullval
, objectmeta
, arraymeta
)
570 elseif char
== "[" then
571 return scantable ('array', "]", str
, pos
, nullval
, objectmeta
, arraymeta
)
572 elseif char
== "\"" then
573 return scanstring (str
, pos
)
575 local pstart
, pend
= strfind (str
, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos
)
577 local number = str2num (strsub (str
, pstart
, pend
))
579 return number, pend
+ 1
582 pstart
, pend
= strfind (str
, "^%a%w*", pos
)
584 local name
= strsub (str
, pstart
, pend
)
585 if name
== "true" then
586 return true, pend
+ 1
587 elseif name
== "false" then
588 return false, pend
+ 1
589 elseif name
== "null" then
590 return nullval
, pend
+ 1
593 return nil, pos
, "no valid JSON value at " .. loc (str
, pos
)
597 local function optionalmetatables(...)
598 if select("#", ...) > 0 then
601 return {__jsontype
= 'object'}, {__jsontype
= 'array'}
605 function json
.decode (str
, pos
, nullval
, ...)
606 local objectmeta
, arraymeta
= optionalmetatables(...)
607 return scanvalue (str
, pos
, nullval
, objectmeta
, arraymeta
)
610 function json
.use_lpeg ()
611 local g
= require ("lpeg")
613 if type(g
.version
) == 'function' and g
.version() == "0.11" then
614 error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
617 local pegmatch
= g
.match
618 local P
, S
, R
= g
.P
, g
.S
, g
.R
620 local function ErrorCall (str
, pos
, msg
, state
)
621 if not state
.msg
then
622 state
.msg
= msg
.. " at " .. loc (str
, pos
)
628 local function Err (msg
)
629 return g
.Cmt (g
.Cc (msg
) * g
.Carg (2), ErrorCall
)
632 local function ErrorUnterminatedCall (str
, pos
, what
, state
)
633 return ErrorCall (str
, pos
- 1, "unterminated " .. what
, state
)
636 local SingleLineComment
= P
"//" * (1 - S
"\n\r")^
0
637 local MultiLineComment
= P
"/*" * (1 - P
"*/")^
0 * P
"*/"
638 local Space
= (S
" \n\r\t" + P
"\239\187\191" + SingleLineComment
+ MultiLineComment
)^
0
640 local function ErrUnterminated (what
)
641 return g
.Cmt (g
.Cc (what
) * g
.Carg (2), ErrorUnterminatedCall
)
644 local PlainChar
= 1 - S
"\"\\\n\r"
645 local EscapeSequence
= (P
"\\" * g
.C (S
"\"\\/bfnrt" + Err
"unsupported escape sequence")) / escapechars
646 local HexDigit
= R("09", "af", "AF")
647 local function UTF16Surrogate (match
, pos
, high
, low
)
648 high
, low
= tonumber (high
, 16), tonumber (low
, 16)
649 if 0xD800 <= high
and high
<= 0xDBff and 0xDC00 <= low
and low
<= 0xDFFF then
650 return true, unichar ((high
- 0xD800) * 0x400 + (low
- 0xDC00) + 0x10000)
655 local function UTF16BMP (hex
)
656 return unichar (tonumber (hex
, 16))
658 local U16Sequence
= (P
"\\u" * g
.C (HexDigit
* HexDigit
* HexDigit
* HexDigit
))
659 local UnicodeEscape
= g
.Cmt (U16Sequence
* U16Sequence
, UTF16Surrogate
) + U16Sequence
/UTF16BMP
660 local Char
= UnicodeEscape
+ EscapeSequence
+ PlainChar
661 local String
= P
"\"" * (g
.Cs (Char ^
0) * P
"\"" + ErrUnterminated
"string")
662 local Integer
= P
"-"^
(-1) * (P
"0" + (R
"19" * R
"09"^
0))
663 local Fractal
= P
"." * R
"09"^
0
664 local Exponent
= (S
"eE") * (S
"+-")^
(-1) * R
"09"^
1
665 local Number
= (Integer
* Fractal^
(-1) * Exponent^
(-1))/str2num
666 local Constant
= P
"true" * g
.Cc (true) + P
"false" * g
.Cc (false) + P
"null" * g
.Carg (1)
667 local SimpleValue
= Number
+ String
+ Constant
668 local ArrayContent
, ObjectContent
670 -- The functions parsearray and parseobject parse only a single value/pair
671 -- at a time and store them directly to avoid hitting the LPeg limits.
672 local function parsearray (str
, pos
, nullval
, state
)
678 obj
, cont
, npos
= pegmatch (ArrayContent
, str
, pos
, nullval
, state
)
679 if cont
== 'end' then
680 return ErrorUnterminatedCall (str
, start
, "array", state
)
683 if cont
== 'cont' or cont
== 'last' then
688 return pos
, setmetatable (t
, state
.arraymeta
)
691 local function parseobject (str
, pos
, nullval
, state
)
697 key
, obj
, cont
, npos
= pegmatch (ObjectContent
, str
, pos
, nullval
, state
)
698 if cont
== 'end' then
699 return ErrorUnterminatedCall (str
, start
, "object", state
)
702 if cont
== 'cont' or cont
== 'last' then
706 return pos
, setmetatable (t
, state
.objectmeta
)
709 local Array
= P
"[" * g
.Cmt (g
.Carg(1) * g
.Carg(2), parsearray
)
710 local Object
= P
"{" * g
.Cmt (g
.Carg(1) * g
.Carg(2), parseobject
)
711 local Value
= Space
* (Array
+ Object
+ SimpleValue
)
712 local ExpectedValue
= Value
+ Space
* Err
"value expected"
713 local ExpectedKey
= String
+ Err
"key expected"
714 local End
= P(-1) * g
.Cc
'end'
715 local ErrInvalid
= Err
"invalid JSON"
716 ArrayContent
= (Value
* Space
* (P
"," * g
.Cc
'cont' + P
"]" * g
.Cc
'last'+ End
+ ErrInvalid
) + g
.Cc(nil) * (P
"]" * g
.Cc
'empty' + End
+ ErrInvalid
)) * g
.Cp()
717 local Pair
= g
.Cg (Space
* ExpectedKey
* Space
* (P
":" + Err
"colon expected") * ExpectedValue
)
718 ObjectContent
= (g
.Cc(nil) * g
.Cc(nil) * P
"}" * g
.Cc
'empty' + End
+ (Pair
* Space
* (P
"," * g
.Cc
'cont' + P
"}" * g
.Cc
'last' + End
+ ErrInvalid
) + ErrInvalid
)) * g
.Cp()
719 local DecodeValue
= ExpectedValue
* g
.Cp ()
721 jsonlpeg
.version
= json
.version
722 jsonlpeg
.encode
= json
.encode
723 jsonlpeg
.null
= json
.null
724 jsonlpeg
.quotestring
= json
.quotestring
725 jsonlpeg
.addnewline
= json
.addnewline
726 jsonlpeg
.encodeexception
= json
.encodeexception
727 jsonlpeg
.using_lpeg
= true
729 function jsonlpeg
.decode (str
, pos
, nullval
, ...)
731 state
.objectmeta
, state
.arraymeta
= optionalmetatables(...)
732 local obj
, retpos
= pegmatch (DecodeValue
, str
, pos
, nullval
, state
)
734 return nil, state
.pos
, state
.msg
740 -- cache result of this function:
741 json
.use_lpeg
= function () return jsonlpeg
end
742 jsonlpeg
.use_lpeg
= json
.use_lpeg
747 if always_use_lpeg
then
748 return json
.use_lpeg()