Module:Jf-JSON: Difference between revisions

Content deleted Content added
fix isNumber initialization bug
update from http://regex.info/code/JSON.lua - adds feature for dealing with trailing garbage
 
(4 intermediate revisions by the same user not shown)
Line 10:
-- http://creativecommons.org/licenses/by/3.0/deed.en_US
--
-- It can be used for any purpose so long as:
-- 1) the copyright notice above, is maintained
-- 2) the web-page links above, andare the 'AUTHOR_NOTE' string below aremaintained
-- 3) the 'AUTHOR_NOTE' string below is maintained
-- maintained. Enjoy.
--
local VERSION = 20160728'20161109.1721' -- version history at end of file
local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 2016072820161109.1721 ]-"
 
--
Line 128 ⟶ 129:
--
--
-- If the JSON text passed to decode() has trailing garbage (e.g. as with the JSON "[123]xyzzy"),
-- the method
--
-- JSON:onTrailingGarbage(json_text, ___location, parsed_value, etc)
--
-- is invoked, where:
--
-- json_text is the original JSON text being parsed,
-- ___location is the count of bytes into json_text where the garbage starts (6 in the example),
-- parsed_value is the Lua result of what was successfully parsed ({123} in the example),
-- etc is as above.
--
-- If JSON:onTrailingGarbage() does not abort, it should return the value decode() should return,
-- or nil + an error message.
--
-- local new_value, error_message = JSON:onTrailingGarbage()
--
-- The default handler just invokes JSON:onDecodeError("trailing garbage"...), but you can have
-- this package ignore trailing garbage via
--
-- function JSON:onTrailingGarbage(json_text, ___location, parsed_value, etc)
-- return parsed_value
-- end
--
--
Line 333 ⟶ 357:
-- produces
-- ["one","two",null,null]
--
--
--
--
-- HANDLING LARGE AND/OR PRECISE NUMBERS
--
--
-- Without special handling, numbers in JSON can lose precision in Lua.
-- For example:
Line 365 ⟶ 392:
-- (This is done by encoding the numeric data with a Lua table/metatable that returns
-- the possibly-imprecise numeric form when accessed numerically, but the original precise
-- representation when accessed as a string.) You can also explicitly access
-- via JSON:forceString() and JSON:forceNumber())
--
-- Consider the example above, with this option turned on:
Line 486 ⟶ 514:
-- onDecodeOfNilError
-- onDecodeOfHTMLError
-- onTrailingGarbage
-- onEncodeError
--
Line 514 ⟶ 543:
end
 
local isNumber = {
isNumber = {
__index = isNumber,
 
__tostring = function(T) return T.S end,
__unm = function(op) return getnum(op) end,
Line 532 ⟶ 558:
__le = function(op1, op2) return getnum(op1) <= getnum(op2) end,
}
isNumber.__index = isNumber
 
function OBJDEF:asNumber(item)
Line 550 ⟶ 577:
end
end
 
--
-- Given an item that might be a normal string or number, or might be an 'isNumber' object defined above,
-- return the string version. This shouldn't be needed often because the 'isNumber' object should autoconvert
-- to a string in most cases, but it's here to allow it to be forced when needed.
--
function OBJDEF:forceString(item)
if type(item) == 'table' and type(item.S) == 'string' then
return item.S
else
return tostring(item)
end
end
 
--
-- Given an item that might be a normal string or number, or might be an 'isNumber' object defined above,
-- return the numeric version.
--
function OBJDEF:forceNumber(item)
if type(item) == 'table' and type(item.N) == 'number' then
return item.N
else
return tonumber(item)
end
end
 
local function unicode_codepoint_as_utf8(codepoint)
Line 617 ⟶ 670:
if text then
if ___location then
message = string.format("%s at charbyte %d of: %s", message, ___location, text)
else
message = string.format("%s: %s", message, text)
Line 632 ⟶ 685:
assert(false, message)
end
end
 
function OBJDEF:onTrailingGarbage(json_text, ___location, parsed_value, etc)
return self:onDecodeError("trailing garbage", json_text, ___location, etc)
end
 
Line 658 ⟶ 715:
if not integer_part then
self:onDecodeError("expected number", text, start, options.etc)
return nil, start -- in case the error method doesn't abort, return something sensible
end
 
Line 707 ⟶ 765:
if not as_number then
self:onDecodeError("bad number", text, start, options.etc)
return nil, start -- in case the error method doesn't abort, return something sensible
end
 
Line 717 ⟶ 776:
if text:sub(start,start) ~= '"' then
self:onDecodeError("expected string's opening quote", text, start, options.etc)
return nil, start -- in case the error method doesn't abort, return something sensible
end
 
Line 775 ⟶ 835:
 
self:onDecodeError("unclosed string", text, start, options.etc)
return nil, start -- in case the error method doesn't abort, return something sensible
end
 
Line 793 ⟶ 854:
if text:sub(start,start) ~= '{' then
self:onDecodeError("expected '{'", text, start, options.etc)
return nil, start -- in case the error method doesn't abort, return something sensible
end
 
Line 810 ⟶ 872:
if text:sub(i, i) ~= ':' then
self:onDecodeError("expected colon", text, i, options.etc)
return nil, i -- in case the error method doesn't abort, return something sensible
end
 
Line 831 ⟶ 894:
if text:sub(i, i) ~= ',' then
self:onDecodeError("expected comma or '}'", text, i, options.etc)
return nil, i -- in case the error method doesn't abort, return something sensible
end
 
Line 837 ⟶ 901:
 
self:onDecodeError("unclosed '{'", text, start, options.etc)
return nil, start -- in case the error method doesn't abort, return something sensible
end
 
Line 842 ⟶ 907:
if text:sub(start,start) ~= '[' then
self:onDecodeError("expected '['", text, start, options.etc)
return nil, start -- in case the error method doesn't abort, return something sensible
end
 
Line 870 ⟶ 936:
end
if text:sub(i, i) ~= ',' then
self:onDecodeError("expected comma or '[]'", text, i, options.etc)
return nil, i -- in case the error method doesn't abort, return something sensible
end
i = skip_whitespace(text, i + 1)
end
self:onDecodeError("unclosed '['", text, start, options.etc)
return nil, i -- in case the error method doesn't abort, return something sensible
end
 
Line 884 ⟶ 952:
if start > text:len() then
self:onDecodeError("unexpected end of string", text, nil, options.etc)
return nil, start -- in case the error method doesn't abort, return something sensible
end
 
Line 909 ⟶ 978:
else
self:onDecodeError("can't parse JSON", text, start, options.etc)
return nil, 1 -- in case the error method doesn't abort, return something sensible
end
end
Line 930 ⟶ 1,000:
 
if type(self) ~= 'table' or self.__index ~= OBJDEF then
OBJDEF:onDecodeError(local error_message = "JSON:decode must be called in method format", nil, nil, options.etc)
OBJDEF:onDecodeError(error_message, nil, nil, options.etc)
return nil, error_message -- in case the error method doesn't abort, return something sensible
end
 
if text == nil then
self:onDecodeOfNilError(string.format(local error_message = "nil passed to JSON:decode()"), nil, nil, options.etc)
self:onDecodeOfNilError(error_message, nil, nil, options.etc)
return nil, error_message -- in case the error method doesn't abort, return something sensible
 
elseif type(text) ~= 'string' then
self:onDecodeError(string.format(local error_message = "expected string argument to JSON:decode(), got %s", type(text)), nil, nil, options.etc)
self:onDecodeError(string.format("%s, got %s", error_message, type(text)), nil, nil, options.etc)
return nil, error_message -- in case the error method doesn't abort, return something sensible
end
 
if text:match('^%s*$') then
-- an empty string is nothing, but not an error
return nil
end
Line 945 ⟶ 1,023:
if text:match('^%s*<') then
-- Can't be JSON... we'll assume it's HTML
self:onDecodeOfHTMLError(string.format(local error_message = "htmlHTML passed to JSON:decode()"), text, nil, options.etc)
self:onDecodeOfHTMLError(error_message, text, nil, options.etc)
return nil, error_message -- in case the error method doesn't abort, return something sensible
end
 
Line 954 ⟶ 1,034:
--
if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then
self:onDecodeError(local error_message = "JSON package groks only UTF-8, sorry", text, nil, options.etc)
self:onDecodeError(error_message, text, nil, options.etc)
return nil, error_message -- in case the error method doesn't abort, return something sensible
end
 
Line 970 ⟶ 1,052:
end
 
--
local success, value = pcall(grok_one, self, text, 1, options)
-- Finally, go parse it
--
local success, value, next_i = pcall(grok_one, self, text, 1, options)
 
if success then
 
return value
local error_message = nil
if next_i ~= #text + 1 then
-- something's left over after we parsed the first thing.... whitespace is allowed.
next_i = skip_whitespace(text, next_i)
 
-- if we have something left over now, it's trailing garbage
if next_i ~= #text + 1 then
value, error_message = self:onTrailingGarbage(text, next_i, value, options.etc)
end
end
return value, error_message
 
else
 
-- 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.
-- 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.
local error_message = value
if self.assert then
self.assert(false, valueerror_message)
else
assert(false, valueerror_message)
end
-- ...and if we're still here (because the assert didn't throw an error),
-- return a nil and throw the error message on as a second arg
return nil, valueerror_message
 
end
end
Line 1,269 ⟶ 1,371:
end
 
local function top_level_encode(self, value, etc, options)
local val = encode_value(self, value, {}, etc, options)
if val == nil then
--PRIVATE("may need to revert to the previous public verison if I can't figure out what the guy wanted")
return val
else
return val
end
end
 
function OBJDEF:encode(value, etc, options)
Line 1,282 ⟶ 1,393:
end
 
return encode_valuetop_level_encode(self, value, {}, etc, options)
end
 
Line 1,297 ⟶ 1,408:
end
 
return encode_valuetop_level_encode(self, value, {}, etc, options)
end
 
Line 1,322 ⟶ 1,433:
--
-- Version history:
--
-- 20161109.21 Oops, had a small boo-boo in the previous update.
--
-- 20161103.20 Used to silently ignore trailing garbage when decoding. Now fails via JSON:onTrailingGarbage()
-- http://seriot.ch/parsing_json.php
--
-- Built-in error message about "expected comma or ']'" had mistakenly referred to '['
--
-- Updated the built-in error reporting to refer to bytes rather than characters.
--
-- The decode() method no longer assumes that error handlers abort.
--
-- Made the VERSION string a string instead of a number
--
 
-- 20160916.19 Fixed the isNumber.__index assignment (thanks to Jack Taylor)
--
-- 2016072820160730.1718 Added concatenationJSON:forceString() to the metatable forand JSON:asNumberforceNumber().
--
-- 20160728.17 Added concatenation to the metatable for JSON:asNumber()
--
-- 20160709.16 Could crash if not passed an options table (thanks jarno heikkinen <jarnoh@capturemonkey.com>).