Updated JSON.lua

This commit is contained in:
yago 2014-12-24 19:19:16 +01:00
parent 03e52b1d41
commit 4fbfd8556f

View File

@ -14,13 +14,13 @@
-- the web-page links above, and the 'AUTHOR_NOTE' string below are -- the web-page links above, and the 'AUTHOR_NOTE' string below are
-- maintained. Enjoy. -- maintained. Enjoy.
-- --
local VERSION = 20140920.13 -- version history at end of file local VERSION = 20141223.14 -- version history at end of file
local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20140920.13 ]-" local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20141223.14 ]-"
-- --
-- The 'AUTHOR_NOTE' variable exists so that information about the source -- The 'AUTHOR_NOTE' variable exists so that information about the source
-- of the package is maintained even in compiled versions. It's included in -- of the package is maintained even in compiled versions. It's also
-- OBJDEF mostly to quiet warnings about unused variables. -- included in OBJDEF below mostly to quiet warnings about unused variables.
-- --
local OBJDEF = { local OBJDEF = {
VERSION = VERSION, VERSION = VERSION,
@ -33,7 +33,7 @@ local OBJDEF = {
-- http://www.json.org/ -- http://www.json.org/
-- --
-- --
-- JSON = (loadfile "JSON.lua")() -- one-time load of the routines -- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines
-- --
-- local lua_value = JSON:decode(raw_json_text) -- local lua_value = JSON:decode(raw_json_text)
-- --
@ -41,9 +41,11 @@ local OBJDEF = {
-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability -- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability
-- --
-- --
-- DECODING
-- --
-- JSON = (loadfile "JSON.lua")() -- one-time load of the routines -- DECODING (from a JSON string to a Lua table)
--
--
-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines
-- --
-- local lua_value = JSON:decode(raw_json_text) -- local lua_value = JSON:decode(raw_json_text)
-- --
@ -58,22 +60,27 @@ local OBJDEF = {
-- { "Larry", "Curly", "Moe" } -- { "Larry", "Curly", "Moe" }
-- --
-- --
-- The encode and decode routines accept an optional second argument, "etc", which is not used -- The encode and decode routines accept an optional second argument,
-- during encoding or decoding, but upon error is passed along to error handlers. It can be of any -- "etc", which is not used during encoding or decoding, but upon error
-- type (including nil). -- is passed along to error handlers. It can be of any type (including nil).
--
--
--
-- ERROR HANDLING
-- --
-- With most errors during decoding, this code calls -- With most errors during decoding, this code calls
-- --
-- JSON:onDecodeError(message, text, location, etc) -- JSON:onDecodeError(message, text, location, etc)
-- --
-- with a message about the error, and if known, the JSON text being parsed and the byte count -- with a message about the error, and if known, the JSON text being
-- where the problem was discovered. You can replace the default JSON:onDecodeError() with your -- parsed and the byte count where the problem was discovered. You can
-- own function. -- replace the default JSON:onDecodeError() with your own function.
-- --
-- The default onDecodeError() merely augments the message with data about the text and the -- The default onDecodeError() merely augments the message with data
-- location if known (and if a second 'etc' argument had been provided to decode(), its value is -- about the text and the location if known (and if a second 'etc'
-- tacked onto the message as well), and then calls JSON.assert(), which itself defaults to Lua's -- argument had been provided to decode(), its value is tacked onto the
-- built-in assert(), and can also be overridden. -- message as well), and then calls JSON.assert(), which itself defaults
-- to Lua's built-in assert(), and can also be overridden.
-- --
-- For example, in an Adobe Lightroom plugin, you might use something like -- For example, in an Adobe Lightroom plugin, you might use something like
-- --
@ -95,9 +102,10 @@ local OBJDEF = {
-- --
-- JSON:onDecodeOfHTMLError(message, text, nil, etc) -- JSON:onDecodeOfHTMLError(message, text, nil, etc)
-- --
-- The use of the fourth 'etc' argument allows stronger coordination between decoding and error -- The use of the fourth 'etc' argument allows stronger coordination
-- reporting, especially when you provide your own error-handling routines. Continuing with the -- between decoding and error reporting, especially when you provide your
-- the Adobe Lightroom plugin example: -- own error-handling routines. Continuing with the the Adobe Lightroom
-- plugin example:
-- --
-- function JSON:onDecodeError(message, text, location, etc) -- function JSON:onDecodeError(message, text, location, etc)
-- local note = "Internal Error: invalid JSON data" -- local note = "Internal Error: invalid JSON data"
@ -121,42 +129,136 @@ local OBJDEF = {
-- --
-- --
-- --
--
-- DECODING AND STRICT TYPES -- DECODING AND STRICT TYPES
-- --
-- Because both JSON objects and JSON arrays are converted to Lua tables, it's not normally -- Because both JSON objects and JSON arrays are converted to Lua tables,
-- possible to tell which a JSON type a particular Lua table was derived from, or guarantee -- it's not normally possible to tell which original JSON type a
-- decode-encode round-trip equivalency. -- particular Lua table was derived from, or guarantee decode-encode
-- round-trip equivalency.
-- --
-- However, if you enable strictTypes, e.g. -- However, if you enable strictTypes, e.g.
-- --
-- JSON = (loadfile "JSON.lua")() --load the routines -- JSON = assert(loadfile "JSON.lua")() --load the routines
-- JSON.strictTypes = true -- JSON.strictTypes = true
-- --
-- then the Lua table resulting from the decoding of a JSON object or JSON array is marked via Lua -- then the Lua table resulting from the decoding of a JSON object or
-- metatable, so that when re-encoded with JSON:encode() it ends up as the appropriate JSON type. -- JSON array is marked via Lua metatable, so that when re-encoded with
-- JSON:encode() it ends up as the appropriate JSON type.
-- --
-- (This is not the default because other routines may not work well with tables that have a -- (This is not the default because other routines may not work well with
-- metatable set, for example, Lightroom API calls.) -- tables that have a metatable set, for example, Lightroom API calls.)
-- --
-- --
-- ENCODING -- ENCODING (from a lua table to a JSON string)
-- --
-- JSON = (loadfile "JSON.lua")() -- one-time load of the routines -- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines
-- --
-- local raw_json_text = JSON:encode(lua_table_or_value) -- local raw_json_text = JSON:encode(lua_table_or_value)
-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability -- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability
-- local custom_pretty = JSON:encode(lua_table_or_value, etc, { pretty = true, indent = "| ", align_keys = false })
--
-- On error during encoding, this code calls: -- On error during encoding, this code calls:
-- --
-- JSON:onEncodeError(message, etc) -- JSON:onEncodeError(message, etc)
-- --
-- which you can override in your local JSON object. -- which you can override in your local JSON object.
-- --
-- If the Lua table contains both string and numeric keys, it fits neither JSON's -- The 'etc' in the error call is the second argument to encode()
-- idea of an object, nor its idea of an array. To get around this, when any string -- and encode_pretty(), or nil if it wasn't provided.
-- key exists (or when non-positive numeric keys exist), numeric keys are converted --
-- to strings. --
-- PRETTY-PRINTING
--
-- An optional third argument, a table of options, allows a bit of
-- configuration about how the encoding takes place:
--
-- pretty = JSON:encode(val, etc, {
-- pretty = true, -- if false, no other options matter
-- indent = " ", -- this provides for a three-space indent per nesting level
-- align_keys = false, -- see below
-- })
--
-- encode() and encode_pretty() are identical except that encode_pretty()
-- provides a default options table if none given in the call:
--
-- { pretty = true, align_keys = false, indent = " " }
--
-- For example, if
--
-- JSON:encode(data)
--
-- produces:
--
-- {"city":"Kyoto","climate":{"avg_temp":16,"humidity":"high","snowfall":"minimal"},"country":"Japan","wards":11}
--
-- then
--
-- JSON:encode_pretty(data)
--
-- produces:
--
-- {
-- "city": "Kyoto",
-- "climate": {
-- "avg_temp": 16,
-- "humidity": "high",
-- "snowfall": "minimal"
-- },
-- "country": "Japan",
-- "wards": 11
-- }
--
-- The following three lines return identical results:
-- JSON:encode_pretty(data)
-- JSON:encode_pretty(data, nil, { pretty = true, align_keys = false, indent = " " })
-- JSON:encode (data, nil, { pretty = true, align_keys = false, indent = " " })
--
-- An example of setting your own indent string:
--
-- JSON:encode_pretty(data, nil, { pretty = true, indent = "| " })
--
-- produces:
--
-- {
-- | "city": "Kyoto",
-- | "climate": {
-- | | "avg_temp": 16,
-- | | "humidity": "high",
-- | | "snowfall": "minimal"
-- | },
-- | "country": "Japan",
-- | "wards": 11
-- }
--
-- An example of setting align_keys to true:
--
-- JSON:encode_pretty(data, nil, { pretty = true, indent = " ", align_keys = true })
--
-- produces:
--
-- {
-- "city": "Kyoto",
-- "climate": {
-- "avg_temp": 16,
-- "humidity": "high",
-- "snowfall": "minimal"
-- },
-- "country": "Japan",
-- "wards": 11
-- }
--
-- which I must admit is kinda ugly, sorry. This was the default for
-- encode_pretty() prior to version 20141223.14.
--
--
-- AMBIGUOUS SITUATIONS DURING THE ENCODING
--
-- During the encode, if a Lua table being encoded contains both string
-- and numeric keys, it fits neither JSON's idea of an object, nor its
-- idea of an array. To get around this, when any string key exists (or
-- when non-positive numeric keys exist), numeric keys are converted to
-- strings.
-- --
-- For example, -- For example,
-- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) -- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" }))
@ -165,6 +267,9 @@ local OBJDEF = {
-- --
-- To prohibit this conversion and instead make it an error condition, set -- To prohibit this conversion and instead make it an error condition, set
-- JSON.noKeyConversion = true -- JSON.noKeyConversion = true
--
-- --
@ -181,6 +286,9 @@ local OBJDEF = {
-- --
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
local default_pretty_indent = " "
local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent }
local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray
local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject
@ -692,8 +800,13 @@ end
-- --
-- Encode -- Encode
-- --
-- 'options' is nil, or a table with possible keys:
-- pretty -- if true, return a pretty-printed version
-- indent -- a string (usually of spaces) used to indent each nested level
-- align_keys -- if true, align all the keys when formatting a table
--
local encode_value -- must predeclare because it calls itself local encode_value -- must predeclare because it calls itself
function encode_value(self, value, parents, etc, indent) -- non-nil indent means pretty-printing function encode_value(self, value, parents, etc, options, indent)
if value == nil then if value == nil then
return 'null' return 'null'
@ -739,6 +852,13 @@ function encode_value(self, value, parents, etc, indent) -- non-nil indent means
-- --
local T = value local T = value
if type(options) ~= 'table' then
options = {}
end
if type(indent) ~= 'string' then
indent = ""
end
if parents[T] then if parents[T] then
self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc) self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc)
else else
@ -754,10 +874,10 @@ function encode_value(self, value, parents, etc, indent) -- non-nil indent means
-- --
local ITEMS = { } local ITEMS = { }
for i = 1, maximum_number_key do for i = 1, maximum_number_key do
table.insert(ITEMS, encode_value(self, T[i], parents, etc, indent)) table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, indent))
end end
if indent then if options.pretty then
result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]" result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]"
else else
result_value = "[" .. table.concat(ITEMS, ",") .. "]" result_value = "[" .. table.concat(ITEMS, ",") .. "]"
@ -769,22 +889,24 @@ function encode_value(self, value, parents, etc, indent) -- non-nil indent means
-- --
local TT = map or T local TT = map or T
if indent then if options.pretty then
local KEYS = { } local KEYS = { }
local max_key_length = 0 local max_key_length = 0
for _, key in ipairs(object_keys) do for _, key in ipairs(object_keys) do
local encoded = encode_value(self, tostring(key), parents, etc, "") local encoded = encode_value(self, tostring(key), parents, etc, options, indent)
if options.align_keys then
max_key_length = math.max(max_key_length, #encoded) max_key_length = math.max(max_key_length, #encoded)
end
table.insert(KEYS, encoded) table.insert(KEYS, encoded)
end end
local key_indent = indent .. " " local key_indent = indent .. tostring(options.indent or "")
local subtable_indent = indent .. string.rep(" ", max_key_length + 2 + 4) local subtable_indent = key_indent .. string.rep(" ", max_key_length) .. (options.align_keys and " " or "")
local FORMAT = "%s%" .. string.format("%d", max_key_length) .. "s: %s" local FORMAT = "%s%" .. string.format("%d", max_key_length) .. "s: %s"
local COMBINED_PARTS = { } local COMBINED_PARTS = { }
for i, key in ipairs(object_keys) do for i, key in ipairs(object_keys) do
local encoded_val = encode_value(self, TT[key], parents, etc, subtable_indent) local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent)
table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val)) table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val))
end end
result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}" result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}"
@ -793,8 +915,8 @@ function encode_value(self, value, parents, etc, indent) -- non-nil indent means
local PARTS = { } local PARTS = { }
for _, key in ipairs(object_keys) do for _, key in ipairs(object_keys) do
local encoded_val = encode_value(self, TT[key], parents, etc, indent) local encoded_val = encode_value(self, TT[key], parents, etc, options, indent)
local encoded_key = encode_value(self, tostring(key), parents, etc, indent) local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent)
table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val)) table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val))
end end
result_value = "{" .. table.concat(PARTS, ",") .. "}" result_value = "{" .. table.concat(PARTS, ",") .. "}"
@ -813,18 +935,18 @@ function encode_value(self, value, parents, etc, indent) -- non-nil indent means
end end
function OBJDEF:encode(value, etc) function OBJDEF:encode(value, etc, options)
if type(self) ~= 'table' or self.__index ~= OBJDEF then if type(self) ~= 'table' or self.__index ~= OBJDEF then
OBJDEF:onEncodeError("JSON:encode must be called in method format", etc) OBJDEF:onEncodeError("JSON:encode must be called in method format", etc)
end end
return encode_value(self, value, {}, etc, nil) return encode_value(self, value, {}, etc, options or nil)
end end
function OBJDEF:encode_pretty(value, etc) function OBJDEF:encode_pretty(value, etc, options)
if type(self) ~= 'table' or self.__index ~= OBJDEF then if type(self) ~= 'table' or self.__index ~= OBJDEF then
OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc) OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc)
end end
return encode_value(self, value, {}, etc, "") return encode_value(self, value, {}, etc, options or default_pretty_options)
end end
function OBJDEF.__tostring() function OBJDEF.__tostring()
@ -850,6 +972,16 @@ return OBJDEF:new()
-- --
-- Version history: -- Version history:
-- --
-- 20141223.14 The encode_pretty() routine produced fine results for small datasets, but isn't really
-- appropriate for anything large, so with help from Alex Aulbach I've made the encode routines
-- more flexible, and changed the default encode_pretty() to be more generally useful.
--
-- Added a third 'options' argument to the encode() and encode_pretty() routines, to control
-- how the encoding takes place.
--
-- Updated docs to add assert() call to the loadfile() line, just as good practice so that
-- if there is a problem loading JSON.lua, the appropriate error message will percolate up.
--
-- 20140920.13 Put back (in a way that doesn't cause warnings about unused variables) the author string, -- 20140920.13 Put back (in a way that doesn't cause warnings about unused variables) the author string,
-- so that the source of the package, and its version number, are visible in compiled copies. -- so that the source of the package, and its version number, are visible in compiled copies.
-- --