forked from rjpcomputing/luaforwindows
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathencode.lua
More file actions
159 lines (142 loc) · 4.3 KB
/
encode.lua
File metadata and controls
159 lines (142 loc) · 4.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
--[[
Licensed according to the included 'LICENSE' document
Author: Thomas Harning Jr <[email protected]>
]]
local type = type
local assert, error = assert, error
local getmetatable, setmetatable = getmetatable, setmetatable
local util = require("json.util")
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
module("json.encode")
--[[
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 = {}
-- Default configuration options to apply
local defaultOptions = {}
-- Configuration bases for client apps
default = nil
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)
defaultOptions[name] = mod.default
strict[name] = mod.strict
loadedModules[name] = mod
end
-- Merges values, assumes all tables are arrays, inner values flattened, optionally constructing output
local function flattenOutput(out, values)
out = not out and {} or type(out) == 'table' and out or {out}
if type(values) == 'table' then
for _, v in ipairs(values) do
out[#out + 1] = v
end
else
out[#out + 1] = values
end
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 getEncoder(options)
options = options and util_merge({}, defaultOptions, options) or defaultOptions
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 encode(data, options)
return getEncoder(options)(data)
end
local mt = getmetatable(_M) or {}
mt.__call = function(self, ...)
return encode(...)
end
setmetatable(_M, mt)