3 JSON Encoder and Parser for Lua 5.1
5 Copyright � 2007 Shaun Brown (http://www.chipmunkav.com).
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:
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.
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.
32 ["name2"] = {1, false, true, 23.54, "a \021 string"},
36 local json = Json.Encode (t)
38 --> {"name1":"value1","name3":null,"name2":[1,false,true,23.54,"a \u0015 string"]}
40 local t = Json.Decode(json)
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
60 local tonumber = tonumber
61 local tostring = tostring
63 local setmetatable = setmetatable
67 local Chipmunk = Chipmunk
71 local StringBuilder = {
75 function StringBuilder:New()
83 function StringBuilder:Append(s)
84 self.buffer[#self.buffer+1] = s
87 function StringBuilder:ToString()
88 return table.concat(self.buffer)
104 function JsonWriter:New()
106 o.writer = StringBuilder:New()
107 setmetatable(o, self)
112 function JsonWriter:Append(s)
113 self.writer:Append(s)
116 function JsonWriter:ToString()
117 return self.writer:ToString()
120 function JsonWriter:Write(o)
124 elseif t == "boolean" then
126 elseif t == "number" then
128 elseif t == "string" then
130 elseif t == "table" then
132 elseif t == "function" then
133 self:WriteFunction(o)
134 elseif t == "thread" then
136 elseif t == "userdata" then
141 function JsonWriter:WriteNil()
145 function JsonWriter:WriteString(o)
146 self:Append(tostring(o))
149 function JsonWriter:ParseString(s)
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))
159 function JsonWriter:IsArray(t)
161 local isindex = function(k)
162 if type(k) == "number" and k > 0 then
163 if math.floor(k) == k then
169 for k,v in pairs(t) do
170 if not isindex(k) then
171 return false, '{', '}'
173 count = math.max(count, k)
176 return true, '[', ']', count
179 function JsonWriter:WriteTable(t)
180 local ba, st, et, n = self:IsArray(t)
191 for k, v in pairs(t) do
204 function JsonWriter:WriteError(o)
206 "Encoding of %s unsupported",
210 function JsonWriter:WriteFunction(o)
218 local StringReader = {
223 function StringReader:New(s)
225 setmetatable(o, self)
231 function StringReader:Peek()
234 return string.sub(self.s, i, i)
239 function StringReader:Next()
241 if self.i <= #self.s then
242 return string.sub(self.s, self.i, self.i)
247 function StringReader:All()
261 function JsonReader:New(s)
263 o.reader = StringReader:New(s)
264 setmetatable(o, self)
269 function JsonReader:Read()
270 self:SkipWhiteSpace()
271 local peek = self:Peek()
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
295 "Invalid input: '%s'",
300 function JsonReader:ReadTrue()
301 self:TestReservedWord{'t','r','u','e'}
305 function JsonReader:ReadFalse()
306 self:TestReservedWord{'f','a','l','s','e'}
310 function JsonReader:ReadNull()
311 self:TestReservedWord{'n','u','l','l'}
315 function JsonReader:TestReservedWord(t)
316 for i, v in ipairs(t) do
317 if self:Next() ~= v then
319 "Error reading '%s': %s",
326 function JsonReader:ReadNumber()
327 local result = self:Next()
328 local peek = self:Peek()
329 while peek ~= nil and string.find(
332 result = result .. self:Next()
335 result = tonumber(result)
336 if result == nil then
338 "Invalid number: '%s'",
345 function JsonReader:ReadString()
347 assert(self:Next() == '"')
348 while self:Peek() ~= '"' do
349 local ch = self:Next()
352 if self.escapes[ch] then
353 ch = self.escapes[ch]
356 result = result .. ch
358 assert(self:Next() == '"')
359 local fromunicode = function(m)
360 return string.char(tonumber(m, 16))
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()
377 "Invalid comment: %s",
382 function JsonReader:ReadBlockComment()
385 local ch = self:Next()
386 if ch == '*' and self:Peek() == '/' then
391 self:Peek() == "*" then
393 "Invalid comment: %s, '/*' illegal.",
400 function JsonReader:ReadSingleLineComment()
401 local ch = self:Next()
402 while ch ~= '\r' and ch ~= '\n' do
407 function JsonReader:ReadArray()
409 assert(self:Next() == '[')
410 self:SkipWhiteSpace()
412 if self:Peek() == ']' then
416 local item = self:Read()
417 result[#result+1] = item
418 self:SkipWhiteSpace()
419 if self:Peek() == ']' then
423 local ch = self:Next()
426 "Invalid array: '%s' due to: '%s'",
431 assert(']' == self:Next())
435 function JsonReader:ReadObject()
437 assert(self:Next() == '{')
438 self:SkipWhiteSpace()
440 if self:Peek() == '}' then
444 local key = self:Read()
445 if type(key) ~= "string" then
447 "Invalid non-string object key: %s",
450 self:SkipWhiteSpace()
451 local ch = self:Next()
454 "Invalid object: '%s' due to: '%s'",
458 self:SkipWhiteSpace()
459 local val = self:Read()
461 self:SkipWhiteSpace()
462 if self:Peek() == '}' then
469 "Invalid array: '%s' near: '%s'",
475 assert(self:Next() == "}")
479 function JsonReader:SkipWhiteSpace()
480 local p = self:Peek()
481 while p ~= nil and string.find(p, "[%s/]") do
491 function JsonReader:Peek()
492 return self.reader:Peek()
495 function JsonReader:Next()
496 return self.reader:Next()
499 function JsonReader:All()
500 return self.reader:All()
504 local writer = JsonWriter:New()
506 return writer:ToString()
510 local reader = JsonReader:New(s)
511 local object = reader:Read()
512 reader:SkipWhiteSpace()
513 assert(reader:Peek() == nil, "Invalid characters after JSON body")