162 lines
4.3 KiB
Lua
162 lines
4.3 KiB
Lua
|
--[[
|
||
|
Licensed according to the included 'LICENSE' document
|
||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||
|
]]
|
||
|
local type = type
|
||
|
local assert, error = assert, error
|
||
|
local getmetatable, setmetatable = getmetatable, setmetatable
|
||
|
|
||
|
local ipairs, pairs = ipairs, pairs
|
||
|
local require = require
|
||
|
|
||
|
local output = require("json.encode.output")
|
||
|
|
||
|
local util = require("json.util")
|
||
|
local util_merge, isCall = util.merge, util.isCall
|
||
|
|
||
|
local _ENV = nil
|
||
|
|
||
|
--[[
|
||
|
List of encoding modules to load.
|
||
|
Loaded in sequence such that earlier encoders get priority when
|
||
|
duplicate type-handlers exist.
|
||
|
]]
|
||
|
local modulesToLoad = {
|
||
|
"strings",
|
||
|
"number",
|
||
|
"calls",
|
||
|
"others",
|
||
|
"array",
|
||
|
"object"
|
||
|
}
|
||
|
-- Modules that have been loaded
|
||
|
local loadedModules = {}
|
||
|
|
||
|
local json_encode = {}
|
||
|
|
||
|
-- Configuration bases for client apps
|
||
|
local modes_defined = { "default", "strict" }
|
||
|
|
||
|
json_encode.default = {}
|
||
|
json_encode.strict = {
|
||
|
initialObject = true -- Require an object at the root
|
||
|
}
|
||
|
|
||
|
-- For each module, load it and its defaults
|
||
|
for _,name in ipairs(modulesToLoad) do
|
||
|
local mod = require("json.encode." .. name)
|
||
|
if mod.mergeOptions then
|
||
|
for _, mode in pairs(modes_defined) do
|
||
|
mod.mergeOptions(json_encode[mode], mode)
|
||
|
end
|
||
|
end
|
||
|
loadedModules[name] = mod
|
||
|
end
|
||
|
|
||
|
-- NOTE: Nested not found, so assume unsupported until use case arises
|
||
|
local function flattenOutput(out, value)
|
||
|
assert(type(value) ~= 'table')
|
||
|
out = out or {}
|
||
|
out[#out + 1] = value
|
||
|
return out
|
||
|
end
|
||
|
|
||
|
-- Prepares the encoding map from the already provided modules and new config
|
||
|
local function prepareEncodeMap(options)
|
||
|
local map = {}
|
||
|
for _, name in ipairs(modulesToLoad) do
|
||
|
local encodermap = loadedModules[name].getEncoder(options[name])
|
||
|
for valueType, encoderSet in pairs(encodermap) do
|
||
|
map[valueType] = flattenOutput(map[valueType], encoderSet)
|
||
|
end
|
||
|
end
|
||
|
return map
|
||
|
end
|
||
|
|
||
|
--[[
|
||
|
Encode a value with a given encoding map and state
|
||
|
]]
|
||
|
local function encodeWithMap(value, map, state, isObjectKey)
|
||
|
local t = type(value)
|
||
|
local encoderList = assert(map[t], "Failed to encode value, unhandled type: " .. t)
|
||
|
for _, encoder in ipairs(encoderList) do
|
||
|
local ret = encoder(value, state, isObjectKey)
|
||
|
if false ~= ret then
|
||
|
return ret
|
||
|
end
|
||
|
end
|
||
|
error("Failed to encode value, encoders for " .. t .. " deny encoding")
|
||
|
end
|
||
|
|
||
|
|
||
|
local function getBaseEncoder(options)
|
||
|
local encoderMap = prepareEncodeMap(options)
|
||
|
if options.preProcess then
|
||
|
local preProcess = options.preProcess
|
||
|
return function(value, state, isObjectKey)
|
||
|
local ret = preProcess(value, isObjectKey or false)
|
||
|
if nil ~= ret then
|
||
|
value = ret
|
||
|
end
|
||
|
return encodeWithMap(value, encoderMap, state)
|
||
|
end
|
||
|
end
|
||
|
return function(value, state, isObjectKey)
|
||
|
return encodeWithMap(value, encoderMap, state)
|
||
|
end
|
||
|
end
|
||
|
--[[
|
||
|
Retreive an initial encoder instance based on provided options
|
||
|
the initial encoder is responsible for initializing state
|
||
|
State has at least these values configured: encode, check_unique, already_encoded
|
||
|
]]
|
||
|
function json_encode.getEncoder(options)
|
||
|
options = options and util_merge({}, json_encode.default, options) or json_encode.default
|
||
|
local encode = getBaseEncoder(options)
|
||
|
|
||
|
local function initialEncode(value)
|
||
|
if options.initialObject then
|
||
|
local errorMessage = "Invalid arguments: expects a JSON Object or Array at the root"
|
||
|
assert(type(value) == 'table' and not isCall(value, options), errorMessage)
|
||
|
end
|
||
|
|
||
|
local alreadyEncoded = {}
|
||
|
local function check_unique(value)
|
||
|
assert(not alreadyEncoded[value], "Recursive encoding of value")
|
||
|
alreadyEncoded[value] = true
|
||
|
end
|
||
|
|
||
|
local outputEncoder = options.output and options.output() or output.getDefault()
|
||
|
local state = {
|
||
|
encode = encode,
|
||
|
check_unique = check_unique,
|
||
|
already_encoded = alreadyEncoded, -- To unmark encoding when moving up stack
|
||
|
outputEncoder = outputEncoder
|
||
|
}
|
||
|
local ret = encode(value, state)
|
||
|
if nil ~= ret then
|
||
|
return outputEncoder.simple and outputEncoder.simple(ret) or ret
|
||
|
end
|
||
|
end
|
||
|
return initialEncode
|
||
|
end
|
||
|
|
||
|
-- CONSTRUCT STATE WITH FOLLOWING (at least)
|
||
|
--[[
|
||
|
encoder
|
||
|
check_unique -- used by inner encoders to make sure value is unique
|
||
|
already_encoded -- used to unmark a value as unique
|
||
|
]]
|
||
|
function json_encode.encode(data, options)
|
||
|
return json_encode.getEncoder(options)(data)
|
||
|
end
|
||
|
|
||
|
local mt = {}
|
||
|
mt.__call = function(self, ...)
|
||
|
return json_encode.encode(...)
|
||
|
end
|
||
|
|
||
|
setmetatable(json_encode, mt)
|
||
|
|
||
|
return json_encode
|