85b85e1e890e9e2714116e4d265a64922be66b2e
[project/luci.git] / libs / json / luasrc / json.lua
1 --[[
2
3  JSON Encoder and Parser for Lua 5.1
4  
5  Copyright � 2007 Shaun Brown (http://www.chipmunkav.com).
6  All Rights Reserved.
7  
8  Permission is hereby granted, free of charge, to any person 
9  obtaining a copy of this software to deal in the Software without 
10  restriction, including without limitation the rights to use, 
11  copy, modify, merge, publish, distribute, sublicense, and/or 
12  sell copies of the Software, and to permit persons to whom the 
13  Software is furnished to do so, subject to the following conditions:
14
15  The above copyright notice and this permission notice shall be 
16  included in all copies or substantial portions of the Software.
17  If you find this software useful please give www.chipmunkav.com a mention.
18
19  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
20  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
21  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 
23  ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
24  CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
25  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
27  Usage:
28
29  -- Lua script:
30  local t = { 
31         ["name1"] = "value1",
32         ["name2"] = {1, false, true, 23.54, "a \021 string"},
33         name3 = Json.Null() 
34  }
35
36  local json = Json.Encode (t)
37  print (json) 
38  --> {"name1":"value1","name3":null,"name2":[1,false,true,23.54,"a \u0015 string"]}
39
40  local t = Json.Decode(json)
41  print(t.name2[4])
42  --> 23.54
43  
44  Notes:
45  1) Encodable Lua types: string, number, boolean, table, nil
46  2) Use Json.Null() to insert a null value into a Json object
47  3) All control chars are encoded to \uXXXX format eg "\021" encodes to "\u0015"
48  4) All Json \uXXXX chars are decoded to chars (0-255 byte range only)
49  5) Json single line // and /* */ block comments are discarded during decoding
50  6) Numerically indexed Lua arrays are encoded to Json Lists eg [1,2,3]
51  7) Lua dictionary tables are converted to Json objects eg {"one":1,"two":2}
52  8) Json nulls are decoded to Lua nil and treated by Lua in the normal way
53
54 --]]
55
56 local string = string
57 local math = math
58 local table = table
59 local error = error
60 local tonumber = tonumber
61 local tostring = tostring
62 local type = type
63 local setmetatable = setmetatable
64 local pairs = pairs
65 local ipairs = ipairs
66 local assert = assert
67 local Chipmunk = Chipmunk
68
69 module("luci.json")
70
71 local StringBuilder = {
72         buffer = {}
73 }
74
75 function StringBuilder:New()
76         local o = {}
77         setmetatable(o, self)
78         self.__index = self
79         o.buffer = {}
80         return o
81 end
82
83 function StringBuilder:Append(s)
84         self.buffer[#self.buffer+1] = s
85 end
86
87 function StringBuilder:ToString()
88         return table.concat(self.buffer)
89 end
90
91 local JsonWriter = {
92         backslashes = {
93                 ['\b'] = "\\b",
94                 ['\t'] = "\\t", 
95                 ['\n'] = "\\n", 
96                 ['\f'] = "\\f",
97                 ['\r'] = "\\r", 
98                 ['"']  = "\\\"", 
99                 ['\\'] = "\\\\", 
100                 ['/']  = "\\/"
101         }
102 }
103
104 function JsonWriter:New()
105         local o = {}
106         o.writer = StringBuilder:New()
107         setmetatable(o, self)
108         self.__index = self
109         return o
110 end
111
112 function JsonWriter:Append(s)
113         self.writer:Append(s)
114 end
115
116 function JsonWriter:ToString()
117         return self.writer:ToString()
118 end
119
120 function JsonWriter:Write(o)
121         local t = type(o)
122         if t == "nil" then
123                 self:WriteNil()
124         elseif t == "boolean" then
125                 self:WriteString(o)
126         elseif t == "number" then
127                 self:WriteString(o)
128         elseif t == "string" then
129                 self:ParseString(o)
130         elseif t == "table" then
131                 self:WriteTable(o)
132         elseif t == "function" then
133                 self:WriteFunction(o)
134         elseif t == "thread" then
135                 self:WriteError(o)
136         elseif t == "userdata" then
137                 self:WriteError(o)
138         end
139 end
140
141 function JsonWriter:WriteNil()
142         self:Append("null")
143 end
144
145 function JsonWriter:WriteString(o)
146         self:Append(tostring(o))
147 end
148
149 function JsonWriter:ParseString(s)
150         self:Append('"')
151         self:Append(string.gsub(s, "[%z%c\\\"/]", function(n)
152                 local c = self.backslashes[n]
153                 if c then return c end
154                 return string.format("\\u%.4X", string.byte(n))
155         end))
156         self:Append('"')
157 end
158
159 function JsonWriter:IsArray(t)
160         local count = 0
161         local isindex = function(k) 
162                 if type(k) == "number" and k > 0 then
163                         if math.floor(k) == k then
164                                 return true
165                         end
166                 end
167                 return false
168         end
169         for k,v in pairs(t) do
170                 if not isindex(k) then
171                         return false, '{', '}'
172                 else
173                         count = math.max(count, k)
174                 end
175         end
176         return true, '[', ']', count
177 end
178
179 function JsonWriter:WriteTable(t)
180         local ba, st, et, n = self:IsArray(t)
181         self:Append(st) 
182         if ba then              
183                 for i = 1, n do
184                         self:Write(t[i])
185                         if i < n then
186                                 self:Append(',')
187                         end
188                 end
189         else
190                 local first = true;
191                 for k, v in pairs(t) do
192                         if not first then
193                                 self:Append(',')
194                         end
195                         first = false;                  
196                         self:ParseString(k)
197                         self:Append(':')
198                         self:Write(v)                   
199                 end
200         end
201         self:Append(et)
202 end
203
204 function JsonWriter:WriteError(o)
205         error(string.format(
206                 "Encoding of %s unsupported", 
207                 tostring(o)))
208 end
209
210 function JsonWriter:WriteFunction(o)
211         if o == Null then 
212                 self:WriteNil()
213         else
214                 self:WriteError(o)
215         end
216 end
217
218 local StringReader = {
219         s = "",
220         i = 0
221 }
222
223 function StringReader:New(s)
224         local o = {}
225         setmetatable(o, self)
226         self.__index = self
227         o.s = s or o.s
228         return o        
229 end
230
231 function StringReader:Peek()
232         local i = self.i + 1
233         if i <= #self.s then
234                 return string.sub(self.s, i, i)
235         end
236         return nil
237 end
238
239 function StringReader:Next()
240         self.i = self.i+1
241         if self.i <= #self.s then
242                 return string.sub(self.s, self.i, self.i)
243         end
244         return nil
245 end
246
247 function StringReader:All()
248         return self.s
249 end
250
251 local JsonReader = {
252         escapes = {
253                 ['t'] = '\t',
254                 ['n'] = '\n',
255                 ['f'] = '\f',
256                 ['r'] = '\r',
257                 ['b'] = '\b',
258         }
259 }
260
261 function JsonReader:New(s)
262         local o = {}
263         o.reader = StringReader:New(s)
264         setmetatable(o, self)
265         self.__index = self
266         return o;
267 end
268
269 function JsonReader:Read()
270         self:SkipWhiteSpace()
271         local peek = self:Peek()
272         if peek == nil then
273                 error(string.format(
274                         "Nil string: '%s'", 
275                         self:All()))
276         elseif peek == '{' then
277                 return self:ReadObject()
278         elseif peek == '[' then
279                 return self:ReadArray()
280         elseif peek == '"' then
281                 return self:ReadString()
282         elseif string.find(peek, "[%+%-%d]") then
283                 return self:ReadNumber()
284         elseif peek == 't' then
285                 return self:ReadTrue()
286         elseif peek == 'f' then
287                 return self:ReadFalse()
288         elseif peek == 'n' then
289                 return self:ReadNull()
290         elseif peek == '/' then
291                 self:ReadComment()
292                 return self:Read()
293         else
294                 error(string.format(
295                         "Invalid input: '%s'", 
296                         self:All()))
297         end
298 end
299                 
300 function JsonReader:ReadTrue()
301         self:TestReservedWord{'t','r','u','e'}
302         return true
303 end
304
305 function JsonReader:ReadFalse()
306         self:TestReservedWord{'f','a','l','s','e'}
307         return false
308 end
309
310 function JsonReader:ReadNull()
311         self:TestReservedWord{'n','u','l','l'}
312         return nil
313 end
314
315 function JsonReader:TestReservedWord(t)
316         for i, v in ipairs(t) do
317                 if self:Next() ~= v then
318                          error(string.format(
319                                 "Error reading '%s': %s", 
320                                 table.concat(t), 
321                                 self:All()))
322                 end
323         end
324 end
325
326 function JsonReader:ReadNumber()
327         local result = self:Next()
328         local peek = self:Peek()
329         while peek ~= nil and string.find(
330                 peek, 
331                 "[%+%-%d%.eE]") do
332             result = result .. self:Next()
333             peek = self:Peek()
334         end
335         result = tonumber(result)
336         if result == nil then
337                 error(string.format(
338                         "Invalid number: '%s'", 
339                         result))
340         else
341                 return result
342         end
343 end
344
345 function JsonReader:ReadString()
346         local result = ""
347         assert(self:Next() == '"')
348         while self:Peek() ~= '"' do
349                 local ch = self:Next()
350                 if ch == '\\' then
351                         ch = self:Next()
352                         if self.escapes[ch] then
353                                 ch = self.escapes[ch]
354                         end
355                 end
356                 result = result .. ch
357         end
358         assert(self:Next() == '"')
359         local fromunicode = function(m)
360                 return string.char(tonumber(m, 16))
361         end
362         return string.gsub(
363                 result, 
364                 "u%x%x(%x%x)", 
365                 fromunicode)
366 end
367
368 function JsonReader:ReadComment()
369         assert(self:Next() == '/')
370         local second = self:Next()
371         if second == '/' then
372             self:ReadSingleLineComment()
373         elseif second == '*' then
374             self:ReadBlockComment()
375         else
376             error(string.format(
377                 "Invalid comment: %s", 
378                 self:All()))
379         end
380 end
381
382 function JsonReader:ReadBlockComment()
383         local done = false
384         while not done do
385                 local ch = self:Next()          
386                 if ch == '*' and self:Peek() == '/' then
387                         done = true
388                 end
389                 if not done and 
390                         ch == '/' and 
391                         self:Peek() == "*" then
392                     error(string.format(
393                         "Invalid comment: %s, '/*' illegal.",  
394                         self:All()))
395                 end
396         end
397         self:Next()
398 end
399
400 function JsonReader:ReadSingleLineComment()
401         local ch = self:Next()
402         while ch ~= '\r' and ch ~= '\n' do
403                 ch = self:Next()
404         end
405 end
406
407 function JsonReader:ReadArray()
408         local result = {}
409         assert(self:Next() == '[')
410         self:SkipWhiteSpace()
411         local done = false
412         if self:Peek() == ']' then
413                 done = true;
414         end
415         while not done do
416                 local item = self:Read()
417                 result[#result+1] = item
418                 self:SkipWhiteSpace()
419                 if self:Peek() == ']' then
420                         done = true
421                 end
422                 if not done then
423                         local ch = self:Next()
424                         if ch ~= ',' then
425                                 error(string.format(
426                                         "Invalid array: '%s' due to: '%s'", 
427                                         self:All(), ch))
428                         end
429                 end
430         end
431         assert(']' == self:Next())
432         return result
433 end
434
435 function JsonReader:ReadObject()
436         local result = {}
437         assert(self:Next() == '{')
438         self:SkipWhiteSpace()
439         local done = false
440         if self:Peek() == '}' then
441                 done = true
442         end
443         while not done do
444                 local key = self:Read()
445                 if type(key) ~= "string" then
446                         error(string.format(
447                                 "Invalid non-string object key: %s", 
448                                 key))
449                 end
450                 self:SkipWhiteSpace()
451                 local ch = self:Next()
452                 if ch ~= ':' then
453                         error(string.format(
454                                 "Invalid object: '%s' due to: '%s'", 
455                                 self:All(), 
456                                 ch))
457                 end
458                 self:SkipWhiteSpace()
459                 local val = self:Read()
460                 result[key] = val
461                 self:SkipWhiteSpace()
462                 if self:Peek() == '}' then
463                         done = true
464                 end
465                 if not done then
466                         ch = self:Next()
467                         if ch ~= ',' then
468                                 error(string.format(
469                                         "Invalid array: '%s' near: '%s'", 
470                                         self:All(), 
471                                         ch))
472                         end
473                 end
474         end
475         assert(self:Next() == "}")
476         return result
477 end
478
479 function JsonReader:SkipWhiteSpace()
480         local p = self:Peek()
481         while p ~= nil and string.find(p, "[%s/]") do
482                 if p == '/' then
483                         self:ReadComment()
484                 else
485                         self:Next()
486                 end
487                 p = self:Peek()
488         end
489 end
490
491 function JsonReader:Peek()
492         return self.reader:Peek()
493 end
494
495 function JsonReader:Next()
496         return self.reader:Next()
497 end
498
499 function JsonReader:All()
500         return self.reader:All()
501 end
502
503 function Encode(o)
504         local writer = JsonWriter:New()
505         writer:Write(o)
506         return writer:ToString()
507 end
508
509 function Decode(s)
510         local reader = JsonReader:New(s)
511         local object = reader:Read()
512         reader:SkipWhiteSpace()
513         assert(reader:Peek() == nil, "Invalid characters after JSON body")
514         return object
515 end
516
517 function Null()
518         return Null
519 end