Optimized encoding of arrays containing nil values
[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 }