-
Notifications
You must be signed in to change notification settings - Fork 150
/
Copy pathfnc_parseJSON.sqf
272 lines (223 loc) · 8.11 KB
/
fnc_parseJSON.sqf
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#include "script_component.hpp"
/* ----------------------------------------------------------------------------
Function: CBA_fnc_parseJSON
Description:
Deserializes a JSON string.
Parameters:
_json - String containing valid JSON. <STRING>
_objectType - Selects the type used for deserializing objects (optional) <BOOLEAN or NUMBER>
0, false: CBA namespace (default)
1, true: CBA hash
2: Native hash map
Returns:
_object - The deserialized JSON object or nil if JSON is invalid.
<LOCATION, ARRAY, STRING, NUMBER, BOOL, HASHMAP, NIL>
Examples:
(begin example)
private _json = "{ ""enabled"": true }";
private _settings = [_json] call CBA_fnc_parseJSON;
private _enabled = _settings getVariable "enabled";
loadFile "data\config.json" call CBA_fnc_parseJSON
[preprocessFile "data\config.json", true] call CBA_fnc_parseJSON
(end)
Author:
BaerMitUmlaut
---------------------------------------------------------------------------- */
SCRIPT(parseJSON);
params ["_json", ["_objectType", 0]];
// Wrappers for creating "objects" and setting values on them
private ["_objectSet", "_createObject"];
switch (_objectType) do {
case false;
case 0: {
_createObject = CBA_fnc_createNamespace;
_objectSet = {
params ["_obj", "_key", "_val"];
_obj setVariable [_key, _val];
};
};
case true;
case 1: {
_createObject = CBA_fnc_hashCreate;
_objectSet = CBA_fnc_hashSet;
};
case 2: {
_createObject = { createHashMap };
_objectSet = {
params ["_obj", "_key", "_val"];
_obj set [_key, _val];
};
};
};
// Handles escaped characters, except for unicode escapes (\uXXXX)
private _unescape = {
params ["_char"];
switch (_char) do {
case """": { """" };
case "\": { "\" };
case "/": { "/" };
case "b": { toString [8] };
case "f": { toString [12] };
case "n": { endl };
case "r": { toString [13] };
case "t": { toString [9] };
default { "" };
};
};
// Splits the input string into tokens
// Tokens can be numbers, strings, null, true, false and symbols
// Strings are prefixed with $ to distinguish them from symbols
private _tokenize = {
params ["_input"];
// Split string into chars, works with unicode unlike splitString
_input = toArray _input apply {toString [_x]};
private _tokens = [];
private _numeric = "+-.0123456789eE" splitString "";
private _symbols = "{}[]:," splitString "";
private _consts = "tfn" splitString "";
while {count _input > 0} do {
private _c = _input deleteAt 0;
switch (true) do {
// Symbols ({}[]:,) are passed directly into the tokens
case (_c in _symbols): {
_tokens pushBack _c;
};
// Number parsing
// This can fail with some invalid JSON numbers, like e10
// Those would require some additional logic or regex
// Valid numbers are all parsed correctly though
case (_c in _numeric): {
private _numStr = _c;
while { _c = _input deleteAt 0; !isNil "_c" && {_c in _numeric} } do {
_numStr = _numStr + _c;
};
_tokens pushBack parseNumber _numStr;
if (!isNil "_c") then {
_input = [_c] + _input;
};
};
// true, false and null
// Only check first char and assume JSON is valid
case (_c in _consts): {
switch (_c) do {
case "t": {
_input deleteRange [0, 3];
_tokens pushBack true;
};
case "f": {
_input deleteRange [0, 4];
_tokens pushBack false;
};
case "n": {
_input deleteRange [0, 3];
_tokens pushBack objNull;
};
};
};
// String parsing
case (_c == """"): {
private _str = "$";
while {true} do {
_c = _input deleteAt 0;
if (_c == """") exitWith {};
if (_c == "\") then {
_str = _str + ((_input deleteAt 0) call _unescape);
} else {
_str = _str + _c;
};
};
_tokens pushBack _str;
};
};
};
_tokens
};
// Appends the next token to the parsing stack
// Returns true unless no more tokens left
private _shift = {
params ["_parseStack", "_tokens"];
if (count _tokens > 0) then {
_parseStack pushBack (_tokens deleteAt 0);
true
} else {
false
};
};
// Tries to reduce the current parsing stack (collect arrays or objects)
// Returns true if parsing stack could be reduced
private _reduce = {
params ["_parseStack", "_tokens"];
// Nothing to reduce
if (count _parseStack == 0) exitWith { false };
// Check top of stack
switch (_parseStack#(count _parseStack - 1)) do {
// Reached end of array, time to collect elements
case "]": {
private _array = [];
// Empty arrays need special handling
if (_parseStack#(count _parseStack - 2) isNotEqualTo "[") then {
// Get next token, if [ beginning is reached, otherwise assume
// valid JSON and that the token is a comma
while {_parseStack deleteAt (count _parseStack - 1) != "["} do {
private _element = _parseStack deleteAt (count _parseStack - 1);
// Remove $ prefix from string
if (_element isEqualType "") then {
_element = _element select [1];
};
_array pushBack _element;
};
reverse _array;
} else {
_parseStack resize (count _parseStack - 2);
};
_parseStack pushBack _array;
true
};
// Reached end of array, time to collect elements
// Works very similar to arrays
case "}": {
private _object = [] call _createObject;
// Empty objects need special handling
if (_parseStack#(count _parseStack - 2) isNotEqualTo "{") then {
// Get next token, if { beginning is reached, otherwise assume
// valid JSON and that token is comma
while {_parseStack deleteAt (count _parseStack - 1) != "{"} do {
private _value = _parseStack deleteAt (count _parseStack - 1);
private _colon = _parseStack deleteAt (count _parseStack - 1);
private _name = _parseStack deleteAt (count _parseStack - 1);
// Remove $ prefix from strings
if (_value isEqualType "") then {
_value = _value select [1];
};
_name = _name select [1];
[_object, _name, _value] call _objectSet;
};
} else {
_parseStack resize (count _parseStack - 2);
};
_parseStack pushBack _object;
true
};
default {
false
};
};
};
// Simple shift-reduce parser
private _parse = {
params ["_tokens"];
private _parseStack = [];
private _params = [_parseStack, _tokens];
while { _params call _reduce || {_params call _shift} } do {};
if (count _parseStack != 1) then {
nil
} else {
private _object = _parseStack#0;
// If JSON is just a string, remove $ prefix from it
if (_object isEqualType "") then {
_object = _object select [1];
};
_object
};
};
[_json call _tokenize] call _parse