26b540428a31c79fa744b93cf6f6c66f9201e02d
[project/luci.git] / libs / json / luasrc / json.lua
1 --[[
2 LuCI - Lua Configuration Interface
3
4 Copyright 2008 Steven Barth <steven@midlink.org>
5 Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 $Id$
14
15 Decoder:
16 Info:
17 null will be decoded to luci.json.null if first parameter of Decoder() is true
18
19 Example:
20 decoder = luci.json.Decoder()
21 luci.ltn12.pump.all(luci.ltn12.source.string("decodableJSON"), decoder:sink())
22 luci.util.dumptable(decoder:get())
23
24 Known issues:
25 does not support unicode conversion \uXXYY with XX != 00 will be ignored
26
27
28 Encoder:
29 Info:
30 Accepts numbers, strings, nil, booleans as they are
31 Accepts luci.json.null as replacement for nil
32 Accepts full associative and full numerically indexed tables
33 Mixed tables will loose their associative values during conversion
34 Iterator functions will be encoded as an array of their return values
35 Non-iterator functions will probably corrupt the encoder
36
37 Example:
38 encoder = luci.json.Encoder(encodableData)
39 luci.ltn12.pump.all(encoder:source(), luci.ltn12.sink.file(io.open("someFile", w)))
40 ]]--
41
42 local util = require "luci.util"
43 local table = require "table"
44 local string = require "string"
45 local coroutine = require "coroutine"
46
47 local assert = assert
48 local tonumber = tonumber
49 local tostring = tostring
50 local error = error
51 local type = type
52 local pairs = pairs
53 local ipairs = ipairs
54 local next = next
55
56 local getmetatable = getmetatable
57
58 --- LuCI JSON-Library
59 -- @cstyle instance
60 module "luci.json"
61
62 --- Null replacement function
63 -- @return null
64 function null()
65 return null
66 end
67
68 --- Create a new JSON-Encoder.
69 -- @class function
70 -- @name Encoder
71 -- @param data Lua-Object to be encoded.
72 -- @param buffersize Blocksize of returned data source.
73 -- @param fastescape Use non-standard escaping (don't escape control chars)
74 -- @return JSON-Encoder
75 Encoder = util.class()
76
77 function Encoder.__init__(self, data, buffersize, fastescape)
78 self.data = data
79 self.buffersize = buffersize or 512
80 self.buffer = ""
81 self.fastescape = fastescape
82
83 getmetatable(self).__call = Encoder.source
84 end
85
86 --- Create an LTN12 source providing the encoded JSON-Data.
87 -- @return LTN12 source
88 function Encoder.source(self)
89 local source = coroutine.create(self.dispatch)
90 return function()
91 local res, data = coroutine.resume(source, self, self.data, true)
92 if res then
93 return data
94 else
95 return nil, data
96 end
97 end
98 end
99
100 function Encoder.dispatch(self, data, start)
101 local parser = self.parsers[type(data)]
102
103 parser(self, data)
104
105 if start then
106 if #self.buffer > 0 then
107 coroutine.yield(self.buffer)
108 end
109
110 coroutine.yield()
111 end
112 end
113
114 function Encoder.put(self, chunk)
115 if self.buffersize < 2 then
116 corountine.yield(chunk)
117 else
118 if #self.buffer + #chunk > self.buffersize then
119 local written = 0
120 local fbuffer = self.buffersize - #self.buffer
121
122 coroutine.yield(self.buffer .. chunk:sub(written + 1, fbuffer))
123 written = fbuffer
124
125 while #chunk - written > self.buffersize do
126 fbuffer = written + self.buffersize
127 coroutine.yield(chunk:sub(written + 1, fbuffer))
128 written = fbuffer
129 end
130
131 self.buffer = chunk:sub(written + 1)
132 else
133 self.buffer = self.buffer .. chunk
134 end
135 end
136 end
137
138 function Encoder.parse_nil(self)
139 self:put("null")
140 end
141
142 function Encoder.parse_bool(self, obj)
143 self:put(obj and "true" or "false")
144 end
145
146 function Encoder.parse_number(self, obj)
147 self:put(tostring(obj))
148 end
149
150 function Encoder.parse_string(self, obj)
151 if self.fastescape then
152 self:put('"' .. obj:gsub('\\', '\\\\'):gsub('"', '\\"') .. '"')
153 else
154 self:put('"' ..
155 obj:gsub('[%c\\"]',
156 function(char)
157 return '\\u00%02x' % char:byte()
158 end
159 )
160 .. '"')
161 end
162 end
163
164 function Encoder.parse_iter(self, obj)
165 if obj == null then
166 return self:put("null")
167 end
168
169 if type(obj) == "table" and (#obj == 0 and next(obj)) then
170 self:put("{")
171 local first = true
172
173 for key, entry in pairs(obj) do
174 first = first or self:put(",")
175 first = first and false
176 self:parse_string(tostring(key))
177 self:put(":")
178 self:dispatch(entry)
179 end
180
181 self:put("}")
182 else
183 self:put("[")
184 local first = true
185
186 if type(obj) == "table" then
187 for i=1, #obj do
188 first = first or self:put(",")
189 first = first and nil
190 self:dispatch(obj[i])
191 end
192 else
193 for entry in obj do
194 first = first or self:put(",")
195 first = first and nil
196 self:dispatch(entry)
197 end
198 end
199
200 self:put("]")
201 end
202 end
203
204 Encoder.parsers = {
205 ['nil'] = Encoder.parse_nil,
206 ['table'] = Encoder.parse_iter,
207 ['number'] = Encoder.parse_number,
208 ['string'] = Encoder.parse_string,
209 ['boolean'] = Encoder.parse_bool,
210 ['function'] = Encoder.parse_iter
211 }
212
213
214 --- Create a new JSON-Decoder.
215 -- @class function
216 -- @name Decoder
217 -- @param customnull Use luci.json.null instead of nil for decoding null
218 -- @return JSON-Decoder
219 Decoder = util.class()
220
221 function Decoder.__init__(self, customnull)
222 self.cnull = customnull
223 getmetatable(self).__call = Decoder.sink
224 end
225
226 --- Create an LTN12 sink from the decoder object which accepts the JSON-Data.
227 -- @return LTN12 sink
228 function Decoder.sink(self)
229 local sink = coroutine.create(self.dispatch)
230 return function(...)
231 return coroutine.resume(sink, self, ...)
232 end
233 end
234
235
236 --- Get the decoded data packets after the rawdata has been sent to the sink.
237 -- @return Decoded data
238 function Decoder.get(self)
239 return self.data
240 end
241
242 function Decoder.dispatch(self, chunk, src_err, strict)
243 local robject, object
244 local oset = false
245
246 while chunk do
247 while chunk and #chunk < 1 do
248 chunk = self:fetch()
249 end
250
251 assert(not strict or chunk, "Unexpected EOS")
252 if not chunk then break end
253
254 local char = chunk:sub(1, 1)
255 local parser = self.parsers[char]
256 or (char:match("%s") and self.parse_space)
257 or (char:match("[0-9-]") and self.parse_number)
258 or error("Unexpected char '%s'" % char)
259
260 chunk, robject = parser(self, chunk)
261
262 if parser ~= self.parse_space then
263 assert(not oset, "Scope violation: Too many objects")
264 object = robject
265 oset = true
266
267 if strict then
268 return chunk, object
269 end
270 end
271 end
272
273 assert(not src_err, src_err)
274 assert(oset, "Unexpected EOS")
275
276 self.data = object
277 end
278
279
280 function Decoder.fetch(self)
281 local tself, chunk, src_err = coroutine.yield()
282 assert(chunk or not src_err, src_err)
283 return chunk
284 end
285
286
287 function Decoder.fetch_atleast(self, chunk, bytes)
288 while #chunk < bytes do
289 local nchunk = self:fetch()
290 assert(nchunk, "Unexpected EOS")
291 chunk = chunk .. nchunk
292 end
293
294 return chunk
295 end
296
297
298 function Decoder.fetch_until(self, chunk, pattern)
299 local start = chunk:find(pattern)
300
301 while not start do
302 local nchunk = self:fetch()
303 assert(nchunk, "Unexpected EOS")
304 chunk = chunk .. nchunk
305 start = chunk:find(pattern)
306 end
307
308 return chunk, start
309 end
310
311
312 function Decoder.parse_space(self, chunk)
313 local start = chunk:find("[^%s]")
314
315 while not start do
316 chunk = self:fetch()
317 if not chunk then
318 return nil
319 end
320 start = chunk:find("[^%s]")
321 end
322
323 return chunk:sub(start)
324 end
325
326
327 function Decoder.parse_literal(self, chunk, literal, value)
328 chunk = self:fetch_atleast(chunk, #literal)
329 assert(chunk:sub(1, #literal) == literal, "Invalid character sequence")
330 return chunk:sub(#literal + 1), value
331 end
332
333
334 function Decoder.parse_null(self, chunk)
335 return self:parse_literal(chunk, "null", self.cnull and null)
336 end
337
338
339 function Decoder.parse_true(self, chunk)
340 return self:parse_literal(chunk, "true", true)
341 end
342
343
344 function Decoder.parse_false(self, chunk)
345 return self:parse_literal(chunk, "false", false)
346 end
347
348
349 function Decoder.parse_number(self, chunk)
350 local chunk, start = self:fetch_until(chunk, "[^0-9eE.+-]")
351 local number = tonumber(chunk:sub(1, start - 1))
352 assert(number, "Invalid number specification")
353 return chunk:sub(start), number
354 end
355
356
357 function Decoder.parse_string(self, chunk)
358 local str = ""
359 local object = nil
360 assert(chunk:sub(1, 1) == '"', 'Expected "')
361 chunk = chunk:sub(2)
362
363 while true do
364 local spos = chunk:find('[\\"]')
365 if spos then
366 str = str .. chunk:sub(1, spos - 1)
367
368 local char = chunk:sub(spos, spos)
369 if char == '"' then -- String end
370 chunk = chunk:sub(spos + 1)
371 break
372 elseif char == "\\" then -- Escape sequence
373 chunk, object = self:parse_escape(chunk:sub(spos))
374 str = str .. object
375 end
376 else
377 str = str .. chunk
378 chunk = self:fetch()
379 assert(chunk, "Unexpected EOS while parsing a string")
380 end
381 end
382
383 return chunk, str
384 end
385
386
387 function Decoder.parse_escape(self, chunk)
388 local str = ""
389 chunk = self:fetch_atleast(chunk:sub(2), 1)
390 local char = chunk:sub(1, 1)
391 chunk = chunk:sub(2)
392
393 if char == '"' then
394 return chunk, '"'
395 elseif char == "\\" then
396 return chunk, "\\"
397 elseif char == "u" then
398 chunk = self:fetch_atleast(chunk, 4)
399 local s1, s2 = chunk:sub(1, 2), chunk:sub(3, 4)
400 s1, s2 = tonumber(s1, 16), tonumber(s2, 16)
401 assert(s1 and s2, "Invalid Unicode character")
402
403 -- ToDo: Unicode support
404 return chunk:sub(5), s1 == 0 and string.char(s2) or ""
405 elseif char == "/" then
406 return chunk, "/"
407 elseif char == "b" then
408 return chunk, "\b"
409 elseif char == "f" then
410 return chunk, "\f"
411 elseif char == "n" then
412 return chunk, "\n"
413 elseif char == "r" then
414 return chunk, "\r"
415 elseif char == "t" then
416 return chunk, "\t"
417 else
418 error("Unexpected escaping sequence '\\%s'" % char)
419 end
420 end
421
422
423 function Decoder.parse_array(self, chunk)
424 chunk = chunk:sub(2)
425 local array = {}
426 local nextp = 1
427
428 local chunk, object = self:parse_delimiter(chunk, "%]")
429
430 if object then
431 return chunk, array
432 end
433
434 repeat
435 chunk, object = self:dispatch(chunk, nil, true)
436 table.insert(array, nextp, object)
437 nextp = nextp + 1
438
439 chunk, object = self:parse_delimiter(chunk, ",%]")
440 assert(object, "Delimiter expected")
441 until object == "]"
442
443 return chunk, array
444 end
445
446
447 function Decoder.parse_object(self, chunk)
448 chunk = chunk:sub(2)
449 local array = {}
450 local name
451
452 local chunk, object = self:parse_delimiter(chunk, "}")
453
454 if object then
455 return chunk, array
456 end
457
458 repeat
459 chunk = self:parse_space(chunk)
460 assert(chunk, "Unexpected EOS")
461
462 chunk, name = self:parse_string(chunk)
463
464 chunk, object = self:parse_delimiter(chunk, ":")
465 assert(object, "Separator expected")
466
467 chunk, object = self:dispatch(chunk, nil, true)
468 array[name] = object
469
470 chunk, object = self:parse_delimiter(chunk, ",}")
471 assert(object, "Delimiter expected")
472 until object == "}"
473
474 return chunk, array
475 end
476
477
478 function Decoder.parse_delimiter(self, chunk, delimiter)
479 while true do
480 chunk = self:fetch_atleast(chunk, 1)
481 local char = chunk:sub(1, 1)
482 if char:match("%s") then
483 chunk = self:parse_space(chunk)
484 assert(chunk, "Unexpected EOS")
485 elseif char:match("[%s]" % delimiter) then
486 return chunk:sub(2), char
487 else
488 return chunk, nil
489 end
490 end
491 end
492
493
494 Decoder.parsers = {
495 ['"'] = Decoder.parse_string,
496 ['t'] = Decoder.parse_true,
497 ['f'] = Decoder.parse_false,
498 ['n'] = Decoder.parse_null,
499 ['['] = Decoder.parse_array,
500 ['{'] = Decoder.parse_object
501 }
502
503
504 --- Create a new Active JSON-Decoder.
505 -- @class function
506 -- @name ActiveDecoder
507 -- @param customnull Use luci.json.null instead of nil for decoding null
508 -- @return Active JSON-Decoder
509 ActiveDecoder = util.class(Decoder)
510
511 function ActiveDecoder.__init__(self, source, customnull)
512 Decoder.__init__(self, customnull)
513 self.source = source
514 self.chunk = nil
515 getmetatable(self).__call = self.get
516 end
517
518
519 --- Fetches one JSON-object from given source
520 -- @return Decoded object
521 function ActiveDecoder.get(self)
522 local chunk, src_err, object
523 if not self.chunk then
524 chunk, src_err = self.source()
525 else
526 chunk = self.chunk
527 end
528
529 self.chunk, object = self:dispatch(chunk, src_err, true)
530 return object
531 end
532
533
534 function ActiveDecoder.fetch(self)
535 local chunk, src_err = self.source()
536 assert(chunk or not src_err, src_err)
537 return chunk
538 end