Updated JSON.lua
This commit is contained in:
parent
03e52b1d41
commit
4fbfd8556f
238
bot/JSON.lua
238
bot/JSON.lua
@ -14,13 +14,13 @@
|
||||
-- the web-page links above, and the 'AUTHOR_NOTE' string below are
|
||||
-- maintained. Enjoy.
|
||||
--
|
||||
local VERSION = 20140920.13 -- 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 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 20141223.14 ]-"
|
||||
|
||||
--
|
||||
-- The 'AUTHOR_NOTE' variable exists so that information about the source
|
||||
-- of the package is maintained even in compiled versions. It's included in
|
||||
-- OBJDEF mostly to quiet warnings about unused variables.
|
||||
-- of the package is maintained even in compiled versions. It's also
|
||||
-- included in OBJDEF below mostly to quiet warnings about unused variables.
|
||||
--
|
||||
local OBJDEF = {
|
||||
VERSION = VERSION,
|
||||
@ -33,7 +33,7 @@ local OBJDEF = {
|
||||
-- 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)
|
||||
--
|
||||
@ -41,9 +41,11 @@ local OBJDEF = {
|
||||
-- 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)
|
||||
--
|
||||
@ -58,22 +60,27 @@ local OBJDEF = {
|
||||
-- { "Larry", "Curly", "Moe" }
|
||||
--
|
||||
--
|
||||
-- The encode and decode routines accept an optional second argument, "etc", which is not used
|
||||
-- during encoding or decoding, but upon error is passed along to error handlers. It can be of any
|
||||
-- type (including nil).
|
||||
-- The encode and decode routines accept an optional second argument,
|
||||
-- "etc", which is not used during encoding or decoding, but upon error
|
||||
-- is passed along to error handlers. It can be of any type (including nil).
|
||||
--
|
||||
--
|
||||
--
|
||||
-- ERROR HANDLING
|
||||
--
|
||||
-- With most errors during decoding, this code calls
|
||||
--
|
||||
-- JSON:onDecodeError(message, text, location, etc)
|
||||
--
|
||||
-- with a message about the error, and if known, the JSON text being parsed and the byte count
|
||||
-- where the problem was discovered. You can replace the default JSON:onDecodeError() with your
|
||||
-- own function.
|
||||
-- with a message about the error, and if known, the JSON text being
|
||||
-- parsed and the byte count where the problem was discovered. You can
|
||||
-- replace the default JSON:onDecodeError() with your own function.
|
||||
--
|
||||
-- The default onDecodeError() merely augments the message with data about the text and the
|
||||
-- location if known (and if a second 'etc' argument had been provided to decode(), its value is
|
||||
-- tacked onto the message as well), and then calls JSON.assert(), which itself defaults to Lua's
|
||||
-- built-in assert(), and can also be overridden.
|
||||
-- The default onDecodeError() merely augments the message with data
|
||||
-- about the text and the location if known (and if a second 'etc'
|
||||
-- argument had been provided to decode(), its value is tacked onto the
|
||||
-- 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
|
||||
--
|
||||
@ -95,9 +102,10 @@ local OBJDEF = {
|
||||
--
|
||||
-- JSON:onDecodeOfHTMLError(message, text, nil, etc)
|
||||
--
|
||||
-- The use of the fourth 'etc' argument allows stronger coordination between decoding and error
|
||||
-- reporting, especially when you provide your own error-handling routines. Continuing with the
|
||||
-- the Adobe Lightroom plugin example:
|
||||
-- The use of the fourth 'etc' argument allows stronger coordination
|
||||
-- between decoding and error reporting, especially when you provide your
|
||||
-- own error-handling routines. Continuing with the the Adobe Lightroom
|
||||
-- plugin example:
|
||||
--
|
||||
-- function JSON:onDecodeError(message, text, location, etc)
|
||||
-- local note = "Internal Error: invalid JSON data"
|
||||
@ -121,42 +129,136 @@ local OBJDEF = {
|
||||
--
|
||||
--
|
||||
--
|
||||
|
||||
--
|
||||
-- DECODING AND STRICT TYPES
|
||||
--
|
||||
-- Because both JSON objects and JSON arrays are converted to Lua tables, it's not normally
|
||||
-- possible to tell which a JSON type a particular Lua table was derived from, or guarantee
|
||||
-- decode-encode round-trip equivalency.
|
||||
-- Because both JSON objects and JSON arrays are converted to Lua tables,
|
||||
-- it's not normally possible to tell which original JSON type a
|
||||
-- particular Lua table was derived from, or guarantee decode-encode
|
||||
-- round-trip equivalency.
|
||||
--
|
||||
-- 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
|
||||
--
|
||||
-- then the Lua table resulting from the decoding of a JSON object or JSON array is marked via Lua
|
||||
-- metatable, so that when re-encoded with JSON:encode() it ends up as the appropriate JSON type.
|
||||
-- then the Lua table resulting from the decoding of a JSON object or
|
||||
-- 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
|
||||
-- metatable set, for example, Lightroom API calls.)
|
||||
-- (This is not the default because other routines may not work well with
|
||||
-- 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 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:
|
||||
--
|
||||
-- JSON:onEncodeError(message, etc)
|
||||
-- JSON:onEncodeError(message, etc)
|
||||
--
|
||||
-- which you can override in your local JSON object.
|
||||
--
|
||||
-- If the Lua table 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.
|
||||
-- The 'etc' in the error call is the second argument to encode()
|
||||
-- and encode_pretty(), or nil if it wasn't provided.
|
||||
--
|
||||
--
|
||||
-- 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,
|
||||
-- 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
|
||||
-- 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 isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject
|
||||
|
||||
@ -692,8 +800,13 @@ end
|
||||
--
|
||||
-- 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
|
||||
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
|
||||
return 'null'
|
||||
@ -739,6 +852,13 @@ function encode_value(self, value, parents, etc, indent) -- non-nil indent means
|
||||
--
|
||||
local T = value
|
||||
|
||||
if type(options) ~= 'table' then
|
||||
options = {}
|
||||
end
|
||||
if type(indent) ~= 'string' then
|
||||
indent = ""
|
||||
end
|
||||
|
||||
if parents[T] then
|
||||
self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc)
|
||||
else
|
||||
@ -754,13 +874,13 @@ function encode_value(self, value, parents, etc, indent) -- non-nil indent means
|
||||
--
|
||||
local ITEMS = { }
|
||||
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
|
||||
|
||||
if indent then
|
||||
if options.pretty then
|
||||
result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]"
|
||||
else
|
||||
result_value = "[" .. table.concat(ITEMS, ",") .. "]"
|
||||
result_value = "[" .. table.concat(ITEMS, ",") .. "]"
|
||||
end
|
||||
|
||||
elseif object_keys then
|
||||
@ -769,22 +889,24 @@ function encode_value(self, value, parents, etc, indent) -- non-nil indent means
|
||||
--
|
||||
local TT = map or T
|
||||
|
||||
if indent then
|
||||
if options.pretty then
|
||||
|
||||
local KEYS = { }
|
||||
local max_key_length = 0
|
||||
for _, key in ipairs(object_keys) do
|
||||
local encoded = encode_value(self, tostring(key), parents, etc, "")
|
||||
max_key_length = math.max(max_key_length, #encoded)
|
||||
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)
|
||||
end
|
||||
table.insert(KEYS, encoded)
|
||||
end
|
||||
local key_indent = indent .. " "
|
||||
local subtable_indent = indent .. string.rep(" ", max_key_length + 2 + 4)
|
||||
local key_indent = indent .. tostring(options.indent or "")
|
||||
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 COMBINED_PARTS = { }
|
||||
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))
|
||||
end
|
||||
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 = { }
|
||||
for _, key in ipairs(object_keys) do
|
||||
local encoded_val = encode_value(self, TT[key], parents, etc, indent)
|
||||
local encoded_key = encode_value(self, tostring(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, options, indent)
|
||||
table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val))
|
||||
end
|
||||
result_value = "{" .. table.concat(PARTS, ",") .. "}"
|
||||
@ -813,18 +935,18 @@ function encode_value(self, value, parents, etc, indent) -- non-nil indent means
|
||||
end
|
||||
|
||||
|
||||
function OBJDEF:encode(value, etc)
|
||||
function OBJDEF:encode(value, etc, options)
|
||||
if type(self) ~= 'table' or self.__index ~= OBJDEF then
|
||||
OBJDEF:onEncodeError("JSON:encode must be called in method format", etc)
|
||||
end
|
||||
return encode_value(self, value, {}, etc, nil)
|
||||
return encode_value(self, value, {}, etc, options or nil)
|
||||
end
|
||||
|
||||
function OBJDEF:encode_pretty(value, etc)
|
||||
function OBJDEF:encode_pretty(value, etc, options)
|
||||
if type(self) ~= 'table' or self.__index ~= OBJDEF then
|
||||
OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc)
|
||||
end
|
||||
return encode_value(self, value, {}, etc, "")
|
||||
return encode_value(self, value, {}, etc, options or default_pretty_options)
|
||||
end
|
||||
|
||||
function OBJDEF.__tostring()
|
||||
@ -850,6 +972,16 @@ return OBJDEF:new()
|
||||
--
|
||||
-- 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,
|
||||
-- so that the source of the package, and its version number, are visible in compiled copies.
|
||||
--
|
||||
|
Reference in New Issue
Block a user