util-jsn.lua /size: 15 Kb    last modification: 2020-07-01 14:35
1
if
not
modules
then
modules
=
{
}
end
modules
[
'
util-jsn
'
]
=
{
2
version
=
1
.
001
,
3
comment
=
"
companion to m-json.mkiv
"
,
4
author
=
"
Hans Hagen, PRAGMA-ADE, Hasselt NL
"
,
5
copyright
=
"
PRAGMA ADE / ConTeXt Development Team
"
,
6
license
=
"
see context related readme files
"
7
}
8 9
-- Of course we could make a nice complete parser with proper error messages but
10
-- as json is generated programmatically errors are systematic and we can assume
11
-- a correct stream. If not, we have some fatal error anyway. So, we can just rely
12
-- on strings being strings (apart from the unicode escape which is not in 5.1) and
13
-- as we first catch known types we just assume that anything else is a number.
14
--
15
-- Reminder for me: check usage in framework and extend when needed. Also document
16
-- it in the cld lib documentation.
17
--
18
-- Upgraded for handling the somewhat more fax server templates.
19 20
local
P
,
V
,
R
,
S
,
C
,
Cc
,
Cs
,
Ct
,
Cf
,
Cg
=
lpeg
.
P
,
lpeg
.
V
,
lpeg
.
R
,
lpeg
.
S
,
lpeg
.
C
,
lpeg
.
Cc
,
lpeg
.
Cs
,
lpeg
.
Ct
,
lpeg
.
Cf
,
lpeg
.
Cg
21
local
lpegmatch
=
lpeg
.
match
22
local
format
,
gsub
=
string
.
format
,
string
.
gsub
23
local
formatters
=
string
.
formatters
24
local
utfchar
=
utf
.
char
25
local
concat
,
sortedkeys
=
table
.
concat
,
table
.
sortedkeys
26 27
local
tonumber
,
tostring
,
rawset
,
type
,
next
=
tonumber
,
tostring
,
rawset
,
type
,
next
28 29
local
json
=
utilities
.
json
or
{
}
30
utilities
.
json
=
json
31 32
do
33 34
-- \\ \/ \b \f \n \r \t \uHHHH
35 36
local
lbrace
=
P
(
"
{
"
)
37
local
rbrace
=
P
(
"
}
"
)
38
local
lparent
=
P
(
"
[
"
)
39
local
rparent
=
P
(
"
]
"
)
40
local
comma
=
P
(
"
,
"
)
41
local
colon
=
P
(
"
:
"
)
42
local
dquote
=
P
(
'
"
'
)
43 44
local
whitespace
=
lpeg
.
patterns
.
whitespace
45
local
optionalws
=
whitespace
^
0
46 47
local
escapes
=
{
48
[
"
b
"
]
=
"
\010
"
,
49
[
"
f
"
]
=
"
\014
"
,
50
[
"
n
"
]
=
"
\n
"
,
51
[
"
r
"
]
=
"
\r
"
,
52
[
"
t
"
]
=
"
\t
"
,
53
}
54 55
-- todo: also handle larger utf16
56 57
local
escape_un
=
P
(
"
\\u
"
)
/
"
"
*
(
C
(
R
(
"
09
"
,
"
AF
"
,
"
af
"
)
^
-4
)
/
function
(
s
)
58
return
utfchar
(
tonumber
(
s
,
16
)
)
59
end
)
60 61
local
escape_bs
=
P
(
[[
\
]]
)
/
"
"
*
(
P
(
1
)
/
escapes
)
-- if not found then P(1) is returned i.e. the to be escaped char
62 63
local
jstring
=
dquote
*
Cs
(
(
escape_un
+
escape_bs
+
(
1
-
dquote
)
)
^
0
)
*
dquote
64
local
jtrue
=
P
(
"
true
"
)
*
Cc
(
true
)
65
local
jfalse
=
P
(
"
false
"
)
*
Cc
(
false
)
66
local
jnull
=
P
(
"
null
"
)
*
Cc
(
nil
)
67
local
jnumber
=
(
1
-
whitespace
-
rparent
-
rbrace
-
comma
)
^
1
/
tonumber
68 69
local
key
=
jstring
70 71
local
jsonconverter
=
{
"
value
"
,
72
hash
=
lbrace
*
Cf
(
Ct
(
"
"
)
*
(
V
(
"
pair
"
)
*
(
comma
*
V
(
"
pair
"
)
)
^
0
+
optionalws
)
,
rawset
)
*
rbrace
,
73
pair
=
Cg
(
optionalws
*
key
*
optionalws
*
colon
*
V
(
"
value
"
)
)
,
74
array
=
Ct
(
lparent
*
(
V
(
"
value
"
)
*
(
comma
*
V
(
"
value
"
)
)
^
0
+
optionalws
)
*
rparent
)
,
75
-- value = optionalws * (jstring + V("hash") + V("array") + jtrue + jfalse + jnull + jnumber + #rparent) * optionalws,
76
value
=
optionalws
*
(
jstring
+
V
(
"
hash
"
)
+
V
(
"
array
"
)
+
jtrue
+
jfalse
+
jnull
+
jnumber
)
*
optionalws
,
77
}
78 79
-- local jsonconverter = { "value",
80
-- hash = lbrace * Cf(Ct("") * (V("pair") * (comma * V("pair"))^0 + optionalws),rawset) * rbrace,
81
-- pair = Cg(optionalws * V("string") * optionalws * colon * V("value")),
82
-- array = Ct(lparent * (V("value") * (comma * V("value"))^0 + optionalws) * rparent),
83
-- string = jstring,
84
-- value = optionalws * (V("string") + V("hash") + V("array") + jtrue + jfalse + jnull + jnumber) * optionalws,
85
-- }
86 87
-- lpeg.print(jsonconverter) -- size 181
88 89
function
json
.
tolua
(
str
)
90
return
lpegmatch
(
jsonconverter
,
str
)
91
end
92 93
function
json
.
load
(
filename
)
94
local
data
=
io
.
loaddata
(
filename
)
95
if
data
then
96
return
lpegmatch
(
jsonconverter
,
data
)
97
end
98
end
99 100
end
101 102
do
103 104
-- It's pretty bad that JSON doesn't allow the trailing comma ... it's a
105
-- typical example of a spec that then forces all generators to check for
106
-- this. It's a way to make sure programmers keep jobs.
107 108
local
escaper
109 110
local
f_start_hash
=
formatters
[
'
%w{
'
]
111
local
f_start_array
=
formatters
[
'
%w[
'
]
112
local
f_start_hash_new
=
formatters
[
"
\n
"
.
.
'
%w{
'
]
113
local
f_start_array_new
=
formatters
[
"
\n
"
.
.
'
%w[
'
]
114
local
f_start_hash_key
=
formatters
[
"
\n
"
.
.
'
%w"%s" : {
'
]
115
local
f_start_array_key
=
formatters
[
"
\n
"
.
.
'
%w"%s" : [
'
]
116 117
local
f_stop_hash
=
formatters
[
"
\n
"
.
.
'
%w}
'
]
118
local
f_stop_array
=
formatters
[
"
\n
"
.
.
'
%w]
'
]
119 120
local
f_key_val_seq
=
formatters
[
"
\n
"
.
.
'
%w"%s" : %s
'
]
121
local
f_key_val_str
=
formatters
[
"
\n
"
.
.
'
%w"%s" : "%s"
'
]
122
local
f_key_val_num
=
f_key_val_seq
123
local
f_key_val_yes
=
formatters
[
"
\n
"
.
.
'
%w"%s" : true
'
]
124
local
f_key_val_nop
=
formatters
[
"
\n
"
.
.
'
%w"%s" : false
'
]
125
local
f_key_val_null
=
formatters
[
"
\n
"
.
.
'
%w"%s" : null
'
]
126 127
local
f_val_num
=
formatters
[
"
\n
"
.
.
'
%w%s
'
]
128
local
f_val_str
=
formatters
[
"
\n
"
.
.
'
%w"%s"
'
]
129
local
f_val_yes
=
formatters
[
"
\n
"
.
.
'
%wtrue
'
]
130
local
f_val_nop
=
formatters
[
"
\n
"
.
.
'
%wfalse
'
]
131
local
f_val_null
=
formatters
[
"
\n
"
.
.
'
%wnull
'
]
132
local
f_val_empty
=
formatters
[
"
\n
"
.
.
'
%w{ }
'
]
133
local
f_val_seq
=
f_val_num
134 135
-- no empty tables because unknown if table or hash
136 137
local
t
=
{
}
138
local
n
=
0
139 140
local
function
is_simple_table
(
tt
)
-- also used in util-tab so maybe public
141
local
l
=
#
tt
142
if
l
>
0
then
143
for
i
=
1
,
l
do
144
if
type
(
tt
[
i
]
)
=
=
"
table
"
then
145
return
false
146
end
147
end
148
local
nn
=
n
149
n
=
n
+
1
t
[
n
]
=
"
[
"
150
for
i
=
1
,
l
do
151
if
i
>
1
then
152
n
=
n
+
1
t
[
n
]
=
"
,
"
153
end
154
local
v
=
tt
[
i
]
155
local
tv
=
type
(
v
)
156
if
tv
=
=
"
number
"
then
157
n
=
n
+
1
t
[
n
]
=
v
158
elseif
tv
=
=
"
string
"
then
159
n
=
n
+
1
t
[
n
]
=
'
"
'
160
n
=
n
+
1
t
[
n
]
=
lpegmatch
(
escaper
,
v
)
or
v
161
n
=
n
+
1
t
[
n
]
=
'
"
'
162
elseif
tv
=
=
"
boolean
"
then
163
n
=
n
+
1
t
[
n
]
=
v
and
"
true
"
or
"
false
"
164
elseif
v
then
165
n
=
n
+
1
t
[
n
]
=
tostring
(
v
)
166
else
167
n
=
n
+
1
t
[
n
]
=
"
null
"
168
end
169
end
170
n
=
n
+
1
t
[
n
]
=
"
]
"
171
local
s
=
concat
(
t
,
"
"
,
nn
+
1
,
n
)
172
n
=
nn
173
return
s
174
end
175
return
false
176
end
177 178
local
function
tojsonpp
(
root
,
name
,
depth
,
level
,
size
)
179
if
root
then
180
local
indexed
=
size
>
0
181
n
=
n
+
1
182
if
level
=
=
0
then
183
if
indexed
then
184
t
[
n
]
=
f_start_array
(
depth
)
185
else
186
t
[
n
]
=
f_start_hash
(
depth
)
187
end
188
elseif
name
then
189
if
tn
=
=
"
string
"
then
190
name
=
lpegmatch
(
escaper
,
name
)
or
name
191
elseif
tn
~
=
"
number
"
then
192
name
=
tostring
(
name
)
193
end
194
if
indexed
then
195
t
[
n
]
=
f_start_array_key
(
depth
,
name
)
196
else
197
t
[
n
]
=
f_start_hash_key
(
depth
,
name
)
198
end
199
else
200
if
indexed
then
201
t
[
n
]
=
f_start_array_new
(
depth
)
202
else
203
t
[
n
]
=
f_start_hash_new
(
depth
)
204
end
205
end
206
depth
=
depth
+
1
207
if
indexed
then
-- indexed
208
for
i
=
1
,
size
do
209
if
i
>
1
then
210
n
=
n
+
1
t
[
n
]
=
"
,
"
211
end
212
local
v
=
root
[
i
]
213
local
tv
=
type
(
v
)
214
if
tv
=
=
"
number
"
then
215
n
=
n
+
1
t
[
n
]
=
f_val_num
(
depth
,
v
)
216
elseif
tv
=
=
"
string
"
then
217
v
=
lpegmatch
(
escaper
,
v
)
or
v
218
n
=
n
+
1
t
[
n
]
=
f_val_str
(
depth
,
v
)
219
elseif
tv
=
=
"
table
"
then
220
if
next
(
v
)
then
221
local
st
=
is_simple_table
(
v
)
222
if
st
then
223
n
=
n
+
1
t
[
n
]
=
f_val_seq
(
depth
,
st
)
224
else
225
tojsonpp
(
v
,
nil
,
depth
,
level
+
1
,
#
v
)
226
end
227
else
228
n
=
n
+
1
229
t
[
n
]
=
f_val_empty
(
depth
)
230
end
231
elseif
tv
=
=
"
boolean
"
then
232
n
=
n
+
1
233
if
v
then
234
t
[
n
]
=
f_val_yes
(
depth
,
v
)
235
else
236
t
[
n
]
=
f_val_nop
(
depth
,
v
)
237
end
238
else
239
n
=
n
+
1
240
t
[
n
]
=
f_val_null
(
depth
)
241
end
242
end
243
elseif
next
(
root
)
then
244
local
sk
=
sortedkeys
(
root
)
245
for
i
=
1
,
#
sk
do
246
if
i
>
1
then
247
n
=
n
+
1
t
[
n
]
=
"
,
"
248
end
249
local
k
=
sk
[
i
]
250
local
v
=
root
[
k
]
251
local
tv
=
type
(
v
)
252
local
tk
=
type
(
k
)
253
if
tv
=
=
"
number
"
then
254
if
tk
=
=
"
number
"
then
255
n
=
n
+
1
t
[
n
]
=
f_key_val_num
(
depth
,
k
,
v
)
256
elseif
tk
=
=
"
string
"
then
257
k
=
lpegmatch
(
escaper
,
k
)
or
k
258
n
=
n
+
1
t
[
n
]
=
f_key_val_num
(
depth
,
k
,
v
)
259
end
260
elseif
tv
=
=
"
string
"
then
261
if
tk
=
=
"
number
"
then
262
v
=
lpegmatch
(
escaper
,
v
)
or
v
263
n
=
n
+
1
t
[
n
]
=
f_key_val_str
(
depth
,
k
,
v
)
264
elseif
tk
=
=
"
string
"
then
265
k
=
lpegmatch
(
escaper
,
k
)
or
k
266
v
=
lpegmatch
(
escaper
,
v
)
or
v
267
n
=
n
+
1
t
[
n
]
=
f_key_val_str
(
depth
,
k
,
v
)
268
end
269
elseif
tv
=
=
"
table
"
then
270
local
l
=
#
v
271
if
l
>
0
then
272
local
st
=
is_simple_table
(
v
)
273
if
not
st
then
274
tojsonpp
(
v
,
k
,
depth
,
level
+
1
,
l
)
275
elseif
tk
=
=
"
number
"
then
276
n
=
n
+
1
t
[
n
]
=
f_key_val_seq
(
depth
,
k
,
st
)
277
elseif
tk
=
=
"
string
"
then
278
k
=
lpegmatch
(
escaper
,
k
)
or
k
279
n
=
n
+
1
t
[
n
]
=
f_key_val_seq
(
depth
,
k
,
st
)
280
end
281
elseif
next
(
v
)
then
282
tojsonpp
(
v
,
k
,
depth
,
level
+
1
,
0
)
283
end
284
elseif
tv
=
=
"
boolean
"
then
285
if
tk
=
=
"
number
"
then
286
n
=
n
+
1
287
if
v
then
288
t
[
n
]
=
f_key_val_yes
(
depth
,
k
)
289
else
290
t
[
n
]
=
f_key_val_nop
(
depth
,
k
)
291
end
292
elseif
tk
=
=
"
string
"
then
293
k
=
lpegmatch
(
escaper
,
k
)
or
k
294
n
=
n
+
1
295
if
v
then
296
t
[
n
]
=
f_key_val_yes
(
depth
,
k
)
297
else
298
t
[
n
]
=
f_key_val_nop
(
depth
,
k
)
299
end
300
end
301
else
302
if
tk
=
=
"
number
"
then
303
n
=
n
+
1
304
t
[
n
]
=
f_key_val_null
(
depth
,
k
)
305
elseif
tk
=
=
"
string
"
then
306
k
=
lpegmatch
(
escaper
,
k
)
or
k
307
n
=
n
+
1
308
t
[
n
]
=
f_key_val_null
(
depth
,
k
)
309
end
310
end
311
end
312
end
313
n
=
n
+
1
314
if
indexed
then
315
t
[
n
]
=
f_stop_array
(
depth
-1
)
316
else
317
t
[
n
]
=
f_stop_hash
(
depth
-1
)
318
end
319
end
320
end
321 322
local
function
tojson
(
value
,
n
)
323
local
kind
=
type
(
value
)
324
if
kind
=
=
"
table
"
then
325
local
done
=
false
326
local
size
=
#
value
327
if
size
=
=
0
then
328
for
k
,
v
in
next
,
value
do
329
if
done
then
330
-- n = n + 1 ; t[n] = ","
331
n
=
n
+
1
;
t
[
n
]
=
'
,"
'
332
else
333
-- n = n + 1 ; t[n] = "{"
334
n
=
n
+
1
;
t
[
n
]
=
'
{"
'
335
done
=
true
336
end
337
n
=
n
+
1
;
t
[
n
]
=
lpegmatch
(
escaper
,
k
)
or
k
338
n
=
n
+
1
;
t
[
n
]
=
'
":
'
339
t
,
n
=
tojson
(
v
,
n
)
340
end
341
if
done
then
342
n
=
n
+
1
;
t
[
n
]
=
"
}
"
343
else
344
n
=
n
+
1
;
t
[
n
]
=
"
{}
"
345
end
346
elseif
size
=
=
1
then
347
-- we can optimize for non tables
348
n
=
n
+
1
;
t
[
n
]
=
"
[
"
349
t
,
n
=
tojson
(
value
[
1
]
,
n
)
350
n
=
n
+
1
;
t
[
n
]
=
"
]
"
351
else
352
for
i
=
1
,
size
do
353
if
done
then
354
n
=
n
+
1
;
t
[
n
]
=
"
,
"
355
else
356
n
=
n
+
1
;
t
[
n
]
=
"
[
"
357
done
=
true
358
end
359
t
,
n
=
tojson
(
value
[
i
]
,
n
)
360
end
361
n
=
n
+
1
;
t
[
n
]
=
"
]
"
362
end
363
elseif
kind
=
=
"
string
"
then
364
n
=
n
+
1
;
t
[
n
]
=
'
"
'
365
n
=
n
+
1
;
t
[
n
]
=
lpegmatch
(
escaper
,
value
)
or
value
366
n
=
n
+
1
;
t
[
n
]
=
'
"
'
367
elseif
kind
=
=
"
number
"
then
368
n
=
n
+
1
;
t
[
n
]
=
value
369
elseif
kind
=
=
"
boolean
"
then
370
n
=
n
+
1
;
t
[
n
]
=
tostring
(
value
)
371
else
372
n
=
n
+
1
;
t
[
n
]
=
"
null
"
373
end
374
return
t
,
n
375
end
376 377
-- escaping keys can become an option
378 379
local
function
jsontostring
(
value
,
pretty
)
380
-- todo optimize for non table
381
local
kind
=
type
(
value
)
382
if
kind
=
=
"
table
"
then
383
if
not
escaper
then
384
local
escapes
=
{
385
[
"
\\
"
]
=
"
\\u005C
"
,
386
[
"
\"
"
]
=
"
\\u0022
"
,
387
}
388
for
i
=
0
,
0x1F
do
389
escapes
[
utfchar
(
i
)
]
=
format
(
"
\\u%04X
"
,
i
)
390
end
391
escaper
=
Cs
(
(
392
(
R
(
'
\0\x20
'
)
+
S
(
'
\"\\
'
)
)
/
escapes
393
+
P
(
1
)
394
)
^
1
)
395 396
end
397
-- local to the closure (saves wrapping and local functions)
398
t
=
{
}
399
n
=
0
400
if
pretty
then
401
tojsonpp
(
value
,
name
,
0
,
0
,
#
value
)
402
value
=
concat
(
t
,
"
"
,
1
,
n
)
403
else
404
t
,
n
=
tojson
(
value
,
0
)
405
value
=
concat
(
t
,
"
"
,
1
,
n
)
406
end
407
t
=
nil
408
n
=
0
409
return
value
410
elseif
kind
=
=
"
string
"
or
kind
=
=
"
number
"
then
411
return
lpegmatch
(
escaper
,
value
)
or
value
412
else
413
return
tostring
(
value
)
414
end
415
end
416 417
json
.
tostring
=
jsontostring
418 419
function
json
.
tojson
(
value
)
420
return
jsontostring
(
value
,
true
)
421
end
422 423
end
424 425
-- local tmp = [[ { "t\nt t" : "foo bar", "a" : true, "b" : [ 123 , 456E-10, { "a" : true, "b" : [ 123 , 456 ] } ] } ]]
426
-- tmp = json.tolua(tmp)
427
-- inspect(tmp)
428
-- tmp = json.tostring(tmp,true)
429
-- inspect(tmp)
430
-- tmp = json.tolua(tmp)
431
-- inspect(tmp)
432
-- tmp = json.tostring(tmp)
433
-- inspect(tmp)
434
-- inspect(json.tostring(true))
435 436
-- local s = [[\foo"bar"]]
437
-- local j = json.tostring { s = s }
438
-- local l = json.tolua(j)
439
-- inspect(j)
440
-- inspect(l)
441
-- print(s==l.s)
442 443
return
json
444