2 LuCI - Lua Configuration Interface
4 Copyright 2008 Steven Barth <steven@midlink.org>
5 Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
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
11 http://www.apache.org/licenses/LICENSE-2.0
17 null will be decoded to luci.json.null if first parameter of Decoder() is true
20 decoder = luci.json.Decoder()
21 luci.ltn12.pump.all(luci.ltn12.source.string("decodableJSON"), decoder:sink())
22 luci.util.dumptable(decoder:get())
25 does not support unicode conversion \uXXYY with XX != 00 will be ignored
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
38 encoder = luci.json.Encoder(encodableData)
39 luci.ltn12.pump.all(encoder:source(), luci.ltn12.sink.file(io.open("someFile", w)))
42 local nixio = require "nixio"
43 local util = require "luci.util"
44 local table = require "table"
45 local string = require "string"
46 local coroutine = require "coroutine"
49 local tonumber = tonumber
50 local tostring = tostring
58 local band = nixio.bit.band
59 local bor = nixio.bit.bor
60 local rshift = nixio.bit.rshift
61 local char = string.char
63 local getmetatable = getmetatable
70 --- Directly decode a JSON string
71 -- @param json JSON-String
73 function decode(json, ...)
74 local a = ActiveDecoder(function() return nil end, ...)
76 local s, obj = pcall(a.get, a)
77 return s and obj or nil
81 --- Direcly encode a Lua object into a JSON string.
82 -- @param obj Lua Object
83 -- @return JSON string
84 function encode(obj, ...)
86 local e = Encoder(obj, 1, ...):source()
92 return not err and table.concat(out) or nil
96 --- Null replacement function
102 --- Create a new JSON-Encoder.
105 -- @param data Lua-Object to be encoded.
106 -- @param buffersize Blocksize of returned data source.
107 -- @param fastescape Use non-standard escaping (don't escape control chars)
108 -- @return JSON-Encoder
109 Encoder = util.class()
111 function Encoder.__init__(self, data, buffersize, fastescape)
113 self.buffersize = buffersize or 512
115 self.fastescape = fastescape
117 getmetatable(self).__call = Encoder.source
120 --- Create an LTN12 source providing the encoded JSON-Data.
121 -- @return LTN12 source
122 function Encoder.source(self)
123 local source = coroutine.create(self.dispatch)
125 local res, data = coroutine.resume(source, self, self.data, true)
134 function Encoder.dispatch(self, data, start)
135 local parser = self.parsers[type(data)]
140 if #self.buffer > 0 then
141 coroutine.yield(self.buffer)
148 function Encoder.put(self, chunk)
149 if self.buffersize < 2 then
150 coroutine.yield(chunk)
152 if #self.buffer + #chunk > self.buffersize then
154 local fbuffer = self.buffersize - #self.buffer
156 coroutine.yield(self.buffer .. chunk:sub(written + 1, fbuffer))
159 while #chunk - written > self.buffersize do
160 fbuffer = written + self.buffersize
161 coroutine.yield(chunk:sub(written + 1, fbuffer))
165 self.buffer = chunk:sub(written + 1)
167 self.buffer = self.buffer .. chunk
172 function Encoder.parse_nil(self)
176 function Encoder.parse_bool(self, obj)
177 self:put(obj and "true" or "false")
180 function Encoder.parse_number(self, obj)
181 self:put(tostring(obj))
184 function Encoder.parse_string(self, obj)
185 if self.fastescape then
186 self:put('"' .. obj:gsub('\\', '\\\\'):gsub('"', '\\"') .. '"')
191 return '\\u00%02x' % char:byte()
198 function Encoder.parse_iter(self, obj)
200 return self:put("null")
203 if type(obj) == "table" and (#obj == 0 and next(obj)) then
207 for key, entry in pairs(obj) do
208 first = first or self:put(",")
209 first = first and false
210 self:parse_string(tostring(key))
220 if type(obj) == "table" then
222 first = first or self:put(",")
223 first = first and nil
224 self:dispatch(obj[i])
228 first = first or self:put(",")
229 first = first and nil
239 ['nil'] = Encoder.parse_nil,
240 ['table'] = Encoder.parse_iter,
241 ['number'] = Encoder.parse_number,
242 ['string'] = Encoder.parse_string,
243 ['boolean'] = Encoder.parse_bool,
244 ['function'] = Encoder.parse_iter
248 --- Create a new JSON-Decoder.
251 -- @param customnull Use luci.json.null instead of nil for decoding null
252 -- @return JSON-Decoder
253 Decoder = util.class()
255 function Decoder.__init__(self, customnull)
256 self.cnull = customnull
257 getmetatable(self).__call = Decoder.sink
260 --- Create an LTN12 sink from the decoder object which accepts the JSON-Data.
261 -- @return LTN12 sink
262 function Decoder.sink(self)
263 local sink = coroutine.create(self.dispatch)
265 return coroutine.resume(sink, self, ...)
270 --- Get the decoded data packets after the rawdata has been sent to the sink.
271 -- @return Decoded data
272 function Decoder.get(self)
276 function Decoder.dispatch(self, chunk, src_err, strict)
277 local robject, object
281 while chunk and #chunk < 1 do
285 assert(not strict or chunk, "Unexpected EOS")
286 if not chunk then break end
288 local char = chunk:sub(1, 1)
289 local parser = self.parsers[char]
290 or (char:match("%s") and self.parse_space)
291 or (char:match("[0-9-]") and self.parse_number)
292 or error("Unexpected char '%s'" % char)
294 chunk, robject = parser(self, chunk)
296 if parser ~= self.parse_space then
297 assert(not oset, "Scope violation: Too many objects")
307 assert(not src_err, src_err)
308 assert(oset, "Unexpected EOS")
314 function Decoder.fetch(self)
315 local tself, chunk, src_err = coroutine.yield()
316 assert(chunk or not src_err, src_err)
321 function Decoder.fetch_atleast(self, chunk, bytes)
322 while #chunk < bytes do
323 local nchunk = self:fetch()
324 assert(nchunk, "Unexpected EOS")
325 chunk = chunk .. nchunk
332 function Decoder.fetch_until(self, chunk, pattern)
333 local start = chunk:find(pattern)
336 local nchunk = self:fetch()
337 assert(nchunk, "Unexpected EOS")
338 chunk = chunk .. nchunk
339 start = chunk:find(pattern)
346 function Decoder.parse_space(self, chunk)
347 local start = chunk:find("[^%s]")
354 start = chunk:find("[^%s]")
357 return chunk:sub(start)
361 function Decoder.parse_literal(self, chunk, literal, value)
362 chunk = self:fetch_atleast(chunk, #literal)
363 assert(chunk:sub(1, #literal) == literal, "Invalid character sequence")
364 return chunk:sub(#literal + 1), value
368 function Decoder.parse_null(self, chunk)
369 return self:parse_literal(chunk, "null", self.cnull and null)
373 function Decoder.parse_true(self, chunk)
374 return self:parse_literal(chunk, "true", true)
378 function Decoder.parse_false(self, chunk)
379 return self:parse_literal(chunk, "false", false)
383 function Decoder.parse_number(self, chunk)
384 local chunk, start = self:fetch_until(chunk, "[^0-9eE.+-]")
385 local number = tonumber(chunk:sub(1, start - 1))
386 assert(number, "Invalid number specification")
387 return chunk:sub(start), number
391 function Decoder.parse_string(self, chunk)
394 assert(chunk:sub(1, 1) == '"', 'Expected "')
398 local spos = chunk:find('[\\"]')
400 str = str .. chunk:sub(1, spos - 1)
402 local char = chunk:sub(spos, spos)
403 if char == '"' then -- String end
404 chunk = chunk:sub(spos + 1)
406 elseif char == "\\" then -- Escape sequence
407 chunk, object = self:parse_escape(chunk:sub(spos))
413 assert(chunk, "Unexpected EOS while parsing a string")
421 function Decoder.utf8_encode(self, s1, s2)
422 local n = s1 * 256 + s2
424 if n >= 0 and n <= 0x7F then
426 elseif n >= 0 and n <= 0x7FF then
428 bor(band(rshift(n, 6), 0x1F), 0xC0),
429 bor(band(n, 0x3F), 0x80)
431 elseif n >= 0 and n <= 0xFFFF then
433 bor(band(rshift(n, 12), 0x0F), 0xE0),
434 bor(band(rshift(n, 6), 0x3F), 0x80),
435 bor(band(n, 0x3F), 0x80)
437 elseif n >= 0 and n <= 0x10FFFF then
439 bor(band(rshift(n, 18), 0x07), 0xF0),
440 bor(band(rshift(n, 12), 0x3F), 0x80),
441 bor(band(rshift(n, 6), 0x3F), 0x80),
442 bor(band(n, 0x3F), 0x80)
450 function Decoder.parse_escape(self, chunk)
452 chunk = self:fetch_atleast(chunk:sub(2), 1)
453 local char = chunk:sub(1, 1)
458 elseif char == "\\" then
460 elseif char == "u" then
461 chunk = self:fetch_atleast(chunk, 4)
462 local s1, s2 = chunk:sub(1, 2), chunk:sub(3, 4)
463 s1, s2 = tonumber(s1, 16), tonumber(s2, 16)
464 assert(s1 and s2, "Invalid Unicode character")
466 return chunk:sub(5), self:utf8_encode(s1, s2)
467 elseif char == "/" then
469 elseif char == "b" then
471 elseif char == "f" then
473 elseif char == "n" then
475 elseif char == "r" then
477 elseif char == "t" then
480 error("Unexpected escaping sequence '\\%s'" % char)
485 function Decoder.parse_array(self, chunk)
490 local chunk, object = self:parse_delimiter(chunk, "%]")
497 chunk, object = self:dispatch(chunk, nil, true)
498 table.insert(array, nextp, object)
501 chunk, object = self:parse_delimiter(chunk, ",%]")
502 assert(object, "Delimiter expected")
509 function Decoder.parse_object(self, chunk)
514 local chunk, object = self:parse_delimiter(chunk, "}")
521 chunk = self:parse_space(chunk)
522 assert(chunk, "Unexpected EOS")
524 chunk, name = self:parse_string(chunk)
526 chunk, object = self:parse_delimiter(chunk, ":")
527 assert(object, "Separator expected")
529 chunk, object = self:dispatch(chunk, nil, true)
532 chunk, object = self:parse_delimiter(chunk, ",}")
533 assert(object, "Delimiter expected")
540 function Decoder.parse_delimiter(self, chunk, delimiter)
542 chunk = self:fetch_atleast(chunk, 1)
543 local char = chunk:sub(1, 1)
544 if char:match("%s") then
545 chunk = self:parse_space(chunk)
546 assert(chunk, "Unexpected EOS")
547 elseif char:match("[%s]" % delimiter) then
548 return chunk:sub(2), char
557 ['"'] = Decoder.parse_string,
558 ['t'] = Decoder.parse_true,
559 ['f'] = Decoder.parse_false,
560 ['n'] = Decoder.parse_null,
561 ['['] = Decoder.parse_array,
562 ['{'] = Decoder.parse_object
566 --- Create a new Active JSON-Decoder.
568 -- @name ActiveDecoder
569 -- @param customnull Use luci.json.null instead of nil for decoding null
570 -- @return Active JSON-Decoder
571 ActiveDecoder = util.class(Decoder)
573 function ActiveDecoder.__init__(self, source, customnull)
574 Decoder.__init__(self, customnull)
577 getmetatable(self).__call = self.get
581 --- Fetches one JSON-object from given source
582 -- @return Decoded object
583 function ActiveDecoder.get(self)
584 local chunk, src_err, object
585 if not self.chunk then
586 chunk, src_err = self.source()
591 self.chunk, object = self:dispatch(chunk, src_err, true)
596 function ActiveDecoder.fetch(self)
597 local chunk, src_err = self.source()
598 assert(chunk or not src_err, src_err)