--[[ Licensed according to the included 'LICENSE' document Author: Thomas Harning Jr ]] 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