2 Licensed according to the included 'LICENSE' document
3 Author: Thomas Harning Jr <harningt@gmail.com>
6 local assert, error = assert, error
7 local getmetatable
, setmetatable
= getmetatable
, setmetatable
9 local ipairs
, pairs
= ipairs
, pairs
10 local require
= require
12 local output
= require("json.encode.output")
14 local util
= require("json.util")
15 local util_merge
, isCall
= util
.merge
, util
.isCall
20 List of encoding modules to load.
21 Loaded in sequence such that earlier encoders get priority when
22 duplicate type-handlers exist.
24 local modulesToLoad
= {
32 -- Modules that have been loaded
33 local loadedModules
= {}
35 local json_encode
= {}
37 -- Configuration bases for client apps
38 local modes_defined
= { "default", "strict" }
40 json_encode
.default
= {}
41 json_encode
.strict
= {
42 initialObject
= true -- Require an object at the root
45 -- For each module, load it and its defaults
46 for _
,name
in ipairs(modulesToLoad
) do
47 local mod = require("json.encode." .. name
)
48 if mod.mergeOptions
then
49 for _
, mode
in pairs(modes_defined
) do
50 mod.mergeOptions(json_encode
[mode
], mode
)
53 loadedModules
[name
] = mod
56 -- NOTE: Nested not found, so assume unsupported until use case arises
57 local function flattenOutput(out
, value
)
58 assert(type(value
) ~= 'table')
64 -- Prepares the encoding map from the already provided modules and new config
65 local function prepareEncodeMap(options
)
67 for _
, name
in ipairs(modulesToLoad
) do
68 local encodermap
= loadedModules
[name
].getEncoder(options
[name
])
69 for valueType
, encoderSet
in pairs(encodermap
) do
70 map
[valueType
] = flattenOutput(map
[valueType
], encoderSet
)
77 Encode a value with a given encoding map and state
79 local function encodeWithMap(value
, map
, state
, isObjectKey
)
81 local encoderList
= assert(map
[t
], "Failed to encode value, unhandled type: " .. t
)
82 for _
, encoder
in ipairs(encoderList
) do
83 local ret
= encoder(value
, state
, isObjectKey
)
88 error("Failed to encode value, encoders for " .. t
.. " deny encoding")
92 local function getBaseEncoder(options
)
93 local encoderMap
= prepareEncodeMap(options
)
94 if options
.preProcess
then
95 local preProcess
= options
.preProcess
96 return function(value
, state
, isObjectKey
)
97 local ret
= preProcess(value
, isObjectKey
or false)
101 return encodeWithMap(value
, encoderMap
, state
)
104 return function(value
, state
, isObjectKey
)
105 return encodeWithMap(value
, encoderMap
, state
)
109 Retreive an initial encoder instance based on provided options
110 the initial encoder is responsible for initializing state
111 State has at least these values configured: encode, check_unique, already_encoded
113 function json_encode
.getEncoder(options
)
114 options
= options
and util_merge({}, json_encode
.default
, options
) or json_encode
.default
115 local encode
= getBaseEncoder(options
)
117 local function initialEncode(value
)
118 if options
.initialObject
then
119 local errorMessage
= "Invalid arguments: expects a JSON Object or Array at the root"
120 assert(type(value
) == 'table' and not isCall(value
, options
), errorMessage
)
123 local alreadyEncoded
= {}
124 local function check_unique(value
)
125 assert(not alreadyEncoded
[value
], "Recursive encoding of value")
126 alreadyEncoded
[value
] = true
129 local outputEncoder
= options
.output
and options
.output() or output
.getDefault()
132 check_unique
= check_unique
,
133 already_encoded
= alreadyEncoded
, -- To unmark encoding when moving up stack
134 outputEncoder
= outputEncoder
136 local ret
= encode(value
, state
)
138 return outputEncoder
.simple
and outputEncoder
.simple(ret
) or ret
144 -- CONSTRUCT STATE WITH FOLLOWING (at least)
147 check_unique -- used by inner encoders to make sure value is unique
148 already_encoded -- used to unmark a value as unique
150 function json_encode
.encode(data
, options
)
151 return json_encode
.getEncoder(options
)(data
)
155 mt
.__call
= function(self
, ...)
156 return json_encode
.encode(...)
159 setmetatable(json_encode
, mt
)