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 -- Merges values, assumes all tables are arrays, inner values flattened, optionally constructing output
57 local function flattenOutput(out
, values
)
58 out
= not out
and {} or type(out
) == 'table' and out
or {out
}
59 if type(values
) == 'table' then
60 for _
, v
in ipairs(values
) do
64 out
[#out
+ 1] = values
69 -- Prepares the encoding map from the already provided modules and new config
70 local function prepareEncodeMap(options
)
72 for _
, name
in ipairs(modulesToLoad
) do
73 local encodermap
= loadedModules
[name
].getEncoder(options
[name
])
74 for valueType
, encoderSet
in pairs(encodermap
) do
75 map
[valueType
] = flattenOutput(map
[valueType
], encoderSet
)
82 Encode a value with a given encoding map and state
84 local function encodeWithMap(value
, map
, state
, isObjectKey
)
86 local encoderList
= assert(map
[t
], "Failed to encode value, unhandled type: " .. t
)
87 for _
, encoder
in ipairs(encoderList
) do
88 local ret
= encoder(value
, state
, isObjectKey
)
93 error("Failed to encode value, encoders for " .. t
.. " deny encoding")
97 local function getBaseEncoder(options
)
98 local encoderMap
= prepareEncodeMap(options
)
99 if options
.preProcess
then
100 local preProcess
= options
.preProcess
101 return function(value
, state
, isObjectKey
)
102 local ret
= preProcess(value
, isObjectKey
or false)
106 return encodeWithMap(value
, encoderMap
, state
)
109 return function(value
, state
, isObjectKey
)
110 return encodeWithMap(value
, encoderMap
, state
)
114 Retreive an initial encoder instance based on provided options
115 the initial encoder is responsible for initializing state
116 State has at least these values configured: encode, check_unique, already_encoded
118 function json_encode
.getEncoder(options
)
119 options
= options
and util_merge({}, json_encode
.default
, options
) or json_encode
.default
120 local encode
= getBaseEncoder(options
)
122 local function initialEncode(value
)
123 if options
.initialObject
then
124 local errorMessage
= "Invalid arguments: expects a JSON Object or Array at the root"
125 assert(type(value
) == 'table' and not isCall(value
, options
), errorMessage
)
128 local alreadyEncoded
= {}
129 local function check_unique(value
)
130 assert(not alreadyEncoded
[value
], "Recursive encoding of value")
131 alreadyEncoded
[value
] = true
134 local outputEncoder
= options
.output
and options
.output() or output
.getDefault()
137 check_unique
= check_unique
,
138 already_encoded
= alreadyEncoded
, -- To unmark encoding when moving up stack
139 outputEncoder
= outputEncoder
141 local ret
= encode(value
, state
)
143 return outputEncoder
.simple
and outputEncoder
.simple(ret
) or ret
149 -- CONSTRUCT STATE WITH FOLLOWING (at least)
152 check_unique -- used by inner encoders to make sure value is unique
153 already_encoded -- used to unmark a value as unique
155 function json_encode
.encode(data
, options
)
156 return json_encode
.getEncoder(options
)(data
)
160 mt
.__call
= function(self
, ...)
161 return json_encode
.encode(...)
164 setmetatable(json_encode
, mt
)