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 util = require "luci.util"
43 local table = require "table"
44 local string = require "string"
45 local coroutine = require "coroutine"
48 local tonumber = tonumber
49 local tostring = tostring
56 local getmetatable = getmetatable
62 --- Null replacement function
68 --- Create a new JSON-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()
77 function Encoder.__init__(self, data, buffersize, fastescape)
79 self.buffersize = buffersize or 512
81 self.fastescape = fastescape
83 getmetatable(self).__call = Encoder.source
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)
91 local res, data = coroutine.resume(source, self, self.data, true)
100 function Encoder.dispatch(self, data, start)
101 local parser = self.parsers[type(data)]
106 if #self.buffer > 0 then
107 coroutine.yield(self.buffer)
114 function Encoder.put(self, chunk)
115 if self.buffersize < 2 then
116 corountine.yield(chunk)
118 if #self.buffer + #chunk > self.buffersize then
120 local fbuffer = self.buffersize - #self.buffer
122 coroutine.yield(self.buffer .. chunk:sub(written + 1, fbuffer))
125 while #chunk - written > self.buffersize do
126 fbuffer = written + self.buffersize
127 coroutine.yield(chunk:sub(written + 1, fbuffer))
131 self.buffer = chunk:sub(written + 1)
133 self.buffer = self.buffer .. chunk
138 function Encoder.parse_nil(self)
142 function Encoder.parse_bool(self, obj)
143 self:put(obj and "true" or "false")
146 function Encoder.parse_number(self, obj)
147 self:put(tostring(obj))
150 function Encoder.parse_string(self, obj)
151 if self.fastescape then
152 self:put('"' .. obj:gsub('\\', '\\\\'):gsub('"', '\\"') .. '"')
157 return '\\u00%02x' % char:byte()
164 function Encoder.parse_iter(self, obj)
166 return self:put("null")
169 if type(obj) == "table" and (#obj == 0 and next(obj)) then
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))
186 if type(obj) == "table" then
188 first = first or self:put(",")
189 first = first and nil
190 self:dispatch(obj[i])
194 first = first or self:put(",")
195 first = first and nil
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
214 --- Create a new JSON-Decoder.
217 -- @param customnull Use luci.json.null instead of nil for decoding null
218 -- @return JSON-Decoder
219 Decoder = util.class()
221 function Decoder.__init__(self, customnull)
222 self.cnull = customnull
223 getmetatable(self).__call = Decoder.sink
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)
231 return coroutine.resume(sink, self, ...)
236 --- Get the decoded data packets after the rawdata has been sent to the sink.
237 -- @return Decoded data
238 function Decoder.get(self)
242 function Decoder.dispatch(self, chunk, src_err, strict)
243 local robject, object
247 while chunk and #chunk < 1 do
251 assert(not strict or chunk, "Unexpected EOS")
252 if not chunk then break end
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)
260 chunk, robject = parser(self, chunk)
262 if parser ~= self.parse_space then
263 assert(not oset, "Scope violation: Too many objects")
273 assert(not src_err, src_err)
274 assert(oset, "Unexpected EOS")
280 function Decoder.fetch(self)
281 local tself, chunk, src_err = coroutine.yield()
282 assert(chunk or not src_err, src_err)
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
298 function Decoder.fetch_until(self, chunk, pattern)
299 local start = chunk:find(pattern)
302 local nchunk = self:fetch()
303 assert(nchunk, "Unexpected EOS")
304 chunk = chunk .. nchunk
305 start = chunk:find(pattern)
312 function Decoder.parse_space(self, chunk)
313 local start = chunk:find("[^%s]")
320 start = chunk:find("[^%s]")
323 return chunk:sub(start)
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
334 function Decoder.parse_null(self, chunk)
335 return self:parse_literal(chunk, "null", self.cnull and null)
339 function Decoder.parse_true(self, chunk)
340 return self:parse_literal(chunk, "true", true)
344 function Decoder.parse_false(self, chunk)
345 return self:parse_literal(chunk, "false", false)
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
357 function Decoder.parse_string(self, chunk)
360 assert(chunk:sub(1, 1) == '"', 'Expected "')
364 local spos = chunk:find('[\\"]')
366 str = str .. chunk:sub(1, spos - 1)
368 local char = chunk:sub(spos, spos)
369 if char == '"' then -- String end
370 chunk = chunk:sub(spos + 1)
372 elseif char == "\\" then -- Escape sequence
373 chunk, object = self:parse_escape(chunk:sub(spos))
379 assert(chunk, "Unexpected EOS while parsing a string")
387 function Decoder.parse_escape(self, chunk)
389 chunk = self:fetch_atleast(chunk:sub(2), 1)
390 local char = chunk:sub(1, 1)
395 elseif char == "\\" then
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")
403 -- ToDo: Unicode support
404 return chunk:sub(5), s1 == 0 and string.char(s2) or ""
405 elseif char == "/" then
407 elseif char == "b" then
409 elseif char == "f" then
411 elseif char == "n" then
413 elseif char == "r" then
415 elseif char == "t" then
418 error("Unexpected escaping sequence '\\%s'" % char)
423 function Decoder.parse_array(self, chunk)
428 local chunk, object = self:parse_delimiter(chunk, "%]")
435 chunk, object = self:dispatch(chunk, nil, true)
436 table.insert(array, nextp, object)
439 chunk, object = self:parse_delimiter(chunk, ",%]")
440 assert(object, "Delimiter expected")
447 function Decoder.parse_object(self, chunk)
452 local chunk, object = self:parse_delimiter(chunk, "}")
459 chunk = self:parse_space(chunk)
460 assert(chunk, "Unexpected EOS")
462 chunk, name = self:parse_string(chunk)
464 chunk, object = self:parse_delimiter(chunk, ":")
465 assert(object, "Separator expected")
467 chunk, object = self:dispatch(chunk, nil, true)
470 chunk, object = self:parse_delimiter(chunk, ",}")
471 assert(object, "Delimiter expected")
478 function Decoder.parse_delimiter(self, chunk, delimiter)
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
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