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
61 --- Null replacement function
70 -- @name luci.json.Encoder
71 Encoder = util.class()
73 --- Creates a new Encoder.
74 -- @param data Lua-Object to be encoded.
75 -- @param buffersize Blocksize of returned data source.
76 -- @param fastescape Use non-standard escaping (don't escape control chars)
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
187 for i, entry in pairs(obj) do
188 first = first or self:put(",")
189 first = first and nil
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
217 -- @name luci.json.Decoder
218 Decoder = util.class()
220 --- Create a new Decoder object.
221 -- @param customnull Use luci.json.null instead of nil
222 function Decoder.__init__(self, customnull)
223 self.cnull = customnull
224 getmetatable(self).__call = Decoder.sink
227 --- Create an LTN12 sink from the decoder object which accepts the JSON-Data.
228 -- @return LTN12 sink
229 function Decoder.sink(self)
230 local sink = coroutine.create(self.dispatch)
232 return coroutine.resume(sink, self, ...)
237 --- Get the decoded data packets after the rawdata has been sent to the sink.
238 -- @return Decoded data
239 function Decoder.get(self)
243 function Decoder.dispatch(self, chunk, src_err, strict)
244 local robject, object
248 while chunk and #chunk < 1 do
252 assert(not strict or chunk, "Unexpected EOS")
253 if not chunk then break end
255 local char = chunk:sub(1, 1)
256 local parser = self.parsers[char]
257 or (char:match("%s") and self.parse_space)
258 or (char:match("[0-9-]") and self.parse_number)
259 or error("Unexpected char '%s'" % char)
261 chunk, robject = parser(self, chunk)
263 if parser ~= self.parse_space then
264 assert(not oset, "Scope violation: Too many objects")
274 assert(not src_err, src_err)
275 assert(oset, "Unexpected EOS")
281 function Decoder.fetch(self)
282 local tself, chunk, src_err = coroutine.yield()
283 assert(chunk or not src_err, src_err)
288 function Decoder.fetch_atleast(self, chunk, bytes)
289 while #chunk < bytes do
290 local nchunk = self:fetch()
291 assert(nchunk, "Unexpected EOS")
292 chunk = chunk .. nchunk
299 function Decoder.fetch_until(self, chunk, pattern)
300 local start = chunk:find(pattern)
303 local nchunk = self:fetch()
304 assert(nchunk, "Unexpected EOS")
305 chunk = chunk .. nchunk
306 start = chunk:find(pattern)
313 function Decoder.parse_space(self, chunk)
314 local start = chunk:find("[^%s]")
321 start = chunk:find("[^%s]")
324 return chunk:sub(start)
328 function Decoder.parse_literal(self, chunk, literal, value)
329 chunk = self:fetch_atleast(chunk, #literal)
330 assert(chunk:sub(1, #literal) == literal, "Invalid character sequence")
331 return chunk:sub(#literal + 1), value
335 function Decoder.parse_null(self, chunk)
336 return self:parse_literal(chunk, "null", self.cnull and null)
340 function Decoder.parse_true(self, chunk)
341 return self:parse_literal(chunk, "true", true)
345 function Decoder.parse_false(self, chunk)
346 return self:parse_literal(chunk, "false", false)
350 function Decoder.parse_number(self, chunk)
351 local chunk, start = self:fetch_until(chunk, "[^0-9eE.+-]")
352 local number = tonumber(chunk:sub(1, start - 1))
353 assert(number, "Invalid number specification")
354 return chunk:sub(start), number
358 function Decoder.parse_string(self, chunk)
361 assert(chunk:sub(1, 1) == '"', 'Expected "')
365 local spos = chunk:find('[\\"]')
367 str = str .. chunk:sub(1, spos - 1)
369 local char = chunk:sub(spos, spos)
370 if char == '"' then -- String end
371 chunk = chunk:sub(spos + 1)
373 elseif char == "\\" then -- Escape sequence
374 chunk, object = self:parse_escape(chunk:sub(spos))
380 assert(chunk, "Unexpected EOS while parsing a string")
388 function Decoder.parse_escape(self, chunk)
390 chunk = self:fetch_atleast(chunk:sub(2), 1)
391 local char = chunk:sub(1, 1)
396 elseif char == "\\" then
398 elseif char == "u" then
399 chunk = self:fetch_atleast(chunk, 4)
400 local s1, s2 = chunk:sub(1, 2), chunk:sub(3, 4)
401 s1, s2 = tonumber(s1, 16), tonumber(s2, 16)
402 assert(s1 and s2, "Invalid Unicode character")
404 -- ToDo: Unicode support
405 return chunk:sub(5), s1 == 0 and string.char(s2) or ""
406 elseif char == "/" then
408 elseif char == "b" then
410 elseif char == "f" then
412 elseif char == "n" then
414 elseif char == "r" then
416 elseif char == "t" then
419 error("Unexpected escaping sequence '\\%s'" % char)
424 function Decoder.parse_array(self, chunk)
429 local chunk, object = self:parse_delimiter(chunk, "%]")
436 chunk, object = self:dispatch(chunk, nil, true)
437 table.insert(array, nextp, object)
440 chunk, object = self:parse_delimiter(chunk, ",%]")
441 assert(object, "Delimiter expected")
448 function Decoder.parse_object(self, chunk)
453 local chunk, object = self:parse_delimiter(chunk, "}")
460 chunk = self:parse_space(chunk)
461 assert(chunk, "Unexpected EOS")
463 chunk, name = self:parse_string(chunk)
465 chunk, object = self:parse_delimiter(chunk, ":")
466 assert(object, "Separator expected")
468 chunk, object = self:dispatch(chunk, nil, true)
471 chunk, object = self:parse_delimiter(chunk, ",}")
472 assert(object, "Delimiter expected")
479 function Decoder.parse_delimiter(self, chunk, delimiter)
481 chunk = self:fetch_atleast(chunk, 1)
482 local char = chunk:sub(1, 1)
483 if char:match("%s") then
484 chunk = self:parse_space(chunk)
485 assert(chunk, "Unexpected EOS")
486 elseif char:match("[%s]" % delimiter) then
487 return chunk:sub(2), char
496 ['"'] = Decoder.parse_string,
497 ['t'] = Decoder.parse_true,
498 ['f'] = Decoder.parse_false,
499 ['n'] = Decoder.parse_null,
500 ['['] = Decoder.parse_array,
501 ['{'] = Decoder.parse_object