RPC part #2
[project/luci.git] / libs / rpc / 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("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