--- /dev/null
+--[[
+
+ JSON Encoder and Parser for Lua 5.1
+
+ Copyright © 2007 Shaun Brown (http://www.chipmunkav.com).
+ All Rights Reserved.
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+ If you find this software useful please give www.chipmunkav.com a mention.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ Usage:
+
+ -- Lua script:
+ local t = {
+ ["name1"] = "value1",
+ ["name2"] = {1, false, true, 23.54, "a \021 string"},
+ name3 = Json.Null()
+ }
+
+ local json = Json.Encode (t)
+ print (json)
+ --> {"name1":"value1","name3":null,"name2":[1,false,true,23.54,"a \u0015 string"]}
+
+ local t = Json.Decode(json)
+ print(t.name2[4])
+ --> 23.54
+
+ Notes:
+ 1) Encodable Lua types: string, number, boolean, table, nil
+ 2) Use Json.Null() to insert a null value into a Json object
+ 3) All control chars are encoded to \uXXXX format eg "\021" encodes to "\u0015"
+ 4) All Json \uXXXX chars are decoded to chars (0-255 byte range only)
+ 5) Json single line // and /* */ block comments are discarded during decoding
+ 6) Numerically indexed Lua arrays are encoded to Json Lists eg [1,2,3]
+ 7) Lua dictionary tables are converted to Json objects eg {"one":1,"two":2}
+ 8) Json nulls are decoded to Lua nil and treated by Lua in the normal way
+
+--]]
+
+local string = string
+local math = math
+local table = table
+local error = error
+local tonumber = tonumber
+local tostring = tostring
+local type = type
+local setmetatable = setmetatable
+local pairs = pairs
+local ipairs = ipairs
+local assert = assert
+local Chipmunk = Chipmunk
+
+module("Json")
+
+local StringBuilder = {
+ buffer = {}
+}
+
+function StringBuilder:New()
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.buffer = {}
+ return o
+end
+
+function StringBuilder:Append(s)
+ self.buffer[#self.buffer+1] = s
+end
+
+function StringBuilder:ToString()
+ return table.concat(self.buffer)
+end
+
+local JsonWriter = {
+ backslashes = {
+ ['\b'] = "\\b",
+ ['\t'] = "\\t",
+ ['\n'] = "\\n",
+ ['\f'] = "\\f",
+ ['\r'] = "\\r",
+ ['"'] = "\\\"",
+ ['\\'] = "\\\\",
+ ['/'] = "\\/"
+ }
+}
+
+function JsonWriter:New()
+ local o = {}
+ o.writer = StringBuilder:New()
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function JsonWriter:Append(s)
+ self.writer:Append(s)
+end
+
+function JsonWriter:ToString()
+ return self.writer:ToString()
+end
+
+function JsonWriter:Write(o)
+ local t = type(o)
+ if t == "nil" then
+ self:WriteNil()
+ elseif t == "boolean" then
+ self:WriteString(o)
+ elseif t == "number" then
+ self:WriteString(o)
+ elseif t == "string" then
+ self:ParseString(o)
+ elseif t == "table" then
+ self:WriteTable(o)
+ elseif t == "function" then
+ self:WriteFunction(o)
+ elseif t == "thread" then
+ self:WriteError(o)
+ elseif t == "userdata" then
+ self:WriteError(o)
+ end
+end
+
+function JsonWriter:WriteNil()
+ self:Append("null")
+end
+
+function JsonWriter:WriteString(o)
+ self:Append(tostring(o))
+end
+
+function JsonWriter:ParseString(s)
+ self:Append('"')
+ self:Append(string.gsub(s, "[%z%c\\\"/]", function(n)
+ local c = self.backslashes[n]
+ if c then return c end
+ return string.format("\\u%.4X", string.byte(n))
+ end))
+ self:Append('"')
+end
+
+function JsonWriter:IsArray(t)
+ local count = 0
+ local isindex = function(k)
+ if type(k) == "number" and k > 0 then
+ if math.floor(k) == k then
+ return true
+ end
+ end
+ return false
+ end
+ for k,v in pairs(t) do
+ if not isindex(k) then
+ return false, '{', '}'
+ else
+ count = math.max(count, k)
+ end
+ end
+ return true, '[', ']', count
+end
+
+function JsonWriter:WriteTable(t)
+ local ba, st, et, n = self:IsArray(t)
+ self:Append(st)
+ if ba then
+ for i = 1, n do
+ self:Write(t[i])
+ if i < n then
+ self:Append(',')
+ end
+ end
+ else
+ local first = true;
+ for k, v in pairs(t) do
+ if not first then
+ self:Append(',')
+ end
+ first = false;
+ self:ParseString(k)
+ self:Append(':')
+ self:Write(v)
+ end
+ end
+ self:Append(et)
+end
+
+function JsonWriter:WriteError(o)
+ error(string.format(
+ "Encoding of %s unsupported",
+ tostring(o)))
+end
+
+function JsonWriter:WriteFunction(o)
+ if o == Null then
+ self:WriteNil()
+ else
+ self:WriteError(o)
+ end
+end
+
+local StringReader = {
+ s = "",
+ i = 0
+}
+
+function StringReader:New(s)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.s = s or o.s
+ return o
+end
+
+function StringReader:Peek()
+ local i = self.i + 1
+ if i <= #self.s then
+ return string.sub(self.s, i, i)
+ end
+ return nil
+end
+
+function StringReader:Next()
+ self.i = self.i+1
+ if self.i <= #self.s then
+ return string.sub(self.s, self.i, self.i)
+ end
+ return nil
+end
+
+function StringReader:All()
+ return self.s
+end
+
+local JsonReader = {
+ escapes = {
+ ['t'] = '\t',
+ ['n'] = '\n',
+ ['f'] = '\f',
+ ['r'] = '\r',
+ ['b'] = '\b',
+ }
+}
+
+function JsonReader:New(s)
+ local o = {}
+ o.reader = StringReader:New(s)
+ setmetatable(o, self)
+ self.__index = self
+ return o;
+end
+
+function JsonReader:Read()
+ self:SkipWhiteSpace()
+ local peek = self:Peek()
+ if peek == nil then
+ error(string.format(
+ "Nil string: '%s'",
+ self:All()))
+ elseif peek == '{' then
+ return self:ReadObject()
+ elseif peek == '[' then
+ return self:ReadArray()
+ elseif peek == '"' then
+ return self:ReadString()
+ elseif string.find(peek, "[%+%-%d]") then
+ return self:ReadNumber()
+ elseif peek == 't' then
+ return self:ReadTrue()
+ elseif peek == 'f' then
+ return self:ReadFalse()
+ elseif peek == 'n' then
+ return self:ReadNull()
+ elseif peek == '/' then
+ self:ReadComment()
+ return self:Read()
+ else
+ error(string.format(
+ "Invalid input: '%s'",
+ self:All()))
+ end
+end
+
+function JsonReader:ReadTrue()
+ self:TestReservedWord{'t','r','u','e'}
+ return true
+end
+
+function JsonReader:ReadFalse()
+ self:TestReservedWord{'f','a','l','s','e'}
+ return false
+end
+
+function JsonReader:ReadNull()
+ self:TestReservedWord{'n','u','l','l'}
+ return nil
+end
+
+function JsonReader:TestReservedWord(t)
+ for i, v in ipairs(t) do
+ if self:Next() ~= v then
+ error(string.format(
+ "Error reading '%s': %s",
+ table.concat(t),
+ self:All()))
+ end
+ end
+end
+
+function JsonReader:ReadNumber()
+ local result = self:Next()
+ local peek = self:Peek()
+ while peek ~= nil and string.find(
+ peek,
+ "[%+%-%d%.eE]") do
+ result = result .. self:Next()
+ peek = self:Peek()
+ end
+ result = tonumber(result)
+ if result == nil then
+ error(string.format(
+ "Invalid number: '%s'",
+ result))
+ else
+ return result
+ end
+end
+
+function JsonReader:ReadString()
+ local result = ""
+ assert(self:Next() == '"')
+ while self:Peek() ~= '"' do
+ local ch = self:Next()
+ if ch == '\\' then
+ ch = self:Next()
+ if self.escapes[ch] then
+ ch = self.escapes[ch]
+ end
+ end
+ result = result .. ch
+ end
+ assert(self:Next() == '"')
+ local fromunicode = function(m)
+ return string.char(tonumber(m, 16))
+ end
+ return string.gsub(
+ result,
+ "u%x%x(%x%x)",
+ fromunicode)
+end
+
+function JsonReader:ReadComment()
+ assert(self:Next() == '/')
+ local second = self:Next()
+ if second == '/' then
+ self:ReadSingleLineComment()
+ elseif second == '*' then
+ self:ReadBlockComment()
+ else
+ error(string.format(
+ "Invalid comment: %s",
+ self:All()))
+ end
+end
+
+function JsonReader:ReadBlockComment()
+ local done = false
+ while not done do
+ local ch = self:Next()
+ if ch == '*' and self:Peek() == '/' then
+ done = true
+ end
+ if not done and
+ ch == '/' and
+ self:Peek() == "*" then
+ error(string.format(
+ "Invalid comment: %s, '/*' illegal.",
+ self:All()))
+ end
+ end
+ self:Next()
+end
+
+function JsonReader:ReadSingleLineComment()
+ local ch = self:Next()
+ while ch ~= '\r' and ch ~= '\n' do
+ ch = self:Next()
+ end
+end
+
+function JsonReader:ReadArray()
+ local result = {}
+ assert(self:Next() == '[')
+ self:SkipWhiteSpace()
+ local done = false
+ if self:Peek() == ']' then
+ done = true;
+ end
+ while not done do
+ local item = self:Read()
+ result[#result+1] = item
+ self:SkipWhiteSpace()
+ if self:Peek() == ']' then
+ done = true
+ end
+ if not done then
+ local ch = self:Next()
+ if ch ~= ',' then
+ error(string.format(
+ "Invalid array: '%s' due to: '%s'",
+ self:All(), ch))
+ end
+ end
+ end
+ assert(']' == self:Next())
+ return result
+end
+
+function JsonReader:ReadObject()
+ local result = {}
+ assert(self:Next() == '{')
+ self:SkipWhiteSpace()
+ local done = false
+ if self:Peek() == '}' then
+ done = true
+ end
+ while not done do
+ local key = self:Read()
+ if type(key) ~= "string" then
+ error(string.format(
+ "Invalid non-string object key: %s",
+ key))
+ end
+ self:SkipWhiteSpace()
+ local ch = self:Next()
+ if ch ~= ':' then
+ error(string.format(
+ "Invalid object: '%s' due to: '%s'",
+ self:All(),
+ ch))
+ end
+ self:SkipWhiteSpace()
+ local val = self:Read()
+ result[key] = val
+ self:SkipWhiteSpace()
+ if self:Peek() == '}' then
+ done = true
+ end
+ if not done then
+ ch = self:Next()
+ if ch ~= ',' then
+ error(string.format(
+ "Invalid array: '%s' near: '%s'",
+ self:All(),
+ ch))
+ end
+ end
+ end
+ assert(self:Next() == "}")
+ return result
+end
+
+function JsonReader:SkipWhiteSpace()
+ local p = self:Peek()
+ while p ~= nil and string.find(p, "[%s/]") do
+ if p == '/' then
+ self:ReadComment()
+ else
+ self:Next()
+ end
+ p = self:Peek()
+ end
+end
+
+function JsonReader:Peek()
+ return self.reader:Peek()
+end
+
+function JsonReader:Next()
+ return self.reader:Next()
+end
+
+function JsonReader:All()
+ return self.reader:All()
+end
+
+function Encode(o)
+ local writer = JsonWriter:New()
+ writer:Write(o)
+ return writer:ToString()
+end
+
+function Decode(s)
+ local reader = JsonReader:New(s)
+ local object = reader:Read()
+ reader:SkipWhiteSpace()
+ assert(reader:Peek() == nil, "Invalid characters after JSON body")
+ return object
+end
+
+function Null()
+ return Null
+end