tests: adds positive test case with nothrow to enhance test coverage
[luajson.git] / lua / json / encode.lua
blobe07a6b84efe413a9eeae8e55b8b5257a79d920c9
1 --[[
2 Licensed according to the included 'LICENSE' document
3 Author: Thomas Harning Jr <harningt@gmail.com>
4 ]]
5 local type = type
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
17 local _ENV = nil
19 --[[
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 = {
25 "strings",
26 "number",
27 "calls",
28 "others",
29 "array",
30 "object"
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)
51 end
52 end
53 loadedModules[name] = mod
54 end
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
61 out[#out + 1] = v
62 end
63 else
64 out[#out + 1] = values
65 end
66 return out
67 end
69 -- Prepares the encoding map from the already provided modules and new config
70 local function prepareEncodeMap(options)
71 local map = {}
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)
76 end
77 end
78 return map
79 end
81 --[[
82 Encode a value with a given encoding map and state
84 local function encodeWithMap(value, map, state, isObjectKey)
85 local t = type(value)
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)
89 if false ~= ret then
90 return ret
91 end
92 end
93 error("Failed to encode value, encoders for " .. t .. " deny encoding")
94 end
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)
103 if nil ~= ret then
104 value = ret
106 return encodeWithMap(value, encoderMap, state)
109 return function(value, state, isObjectKey)
110 return encodeWithMap(value, encoderMap, state)
113 --[[
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()
135 local state = {
136 encode = encode,
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)
142 if nil ~= ret then
143 return outputEncoder.simple and outputEncoder.simple(ret) or ret
146 return initialEncode
149 -- CONSTRUCT STATE WITH FOLLOWING (at least)
150 --[[
151 encoder
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)
159 local mt = {}
160 mt.__call = function(self, ...)
161 return json_encode.encode(...)
164 setmetatable(json_encode, mt)
166 return json_encode