Code snippet: JSON encoder

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

Code snippet: JSON encoder

danrrahmel
Plua is really, really great. It's wonderful to program the Palm ON
the Palm. Unfortunately, it seems like a lot of the Lua code out there
is for Lua 5.1, while Plua is compatible with 5.0. I've learned the
basic differences and have adapt some useful code. Here I've converted
a JSON library to work on Plua.

At the bottom you'll see a small example that decodes a string to a
Lua table and prints the results.

I'm currently developing using the Eclipse with a Lua plug-in. Does
anyone have a suggestion for a simple GUI for Windows? It would be
great to create a desktop equivalent for the screen and gui objects so
development could happen both on the Palm and the desktop.

Hope you find this useful!

Dan

----------------------------

Author notice:

 JSON Encoder and Parser for Lua 5.1
 Adapted to Lua 5.0 / Plua by Dan Rahmel
 
 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
--------------------------
-- jsonencoder.lua
 
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
 
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.getn()+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 <= string.len(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 <= string.len(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() == '[')
        local done = false
        if self:Peek() == ']' then
               done = true;
        end
        while not done do
               local item = self:Read()
               result[result.getn()+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() == '{')
        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)
        return reader:Read()
end
 
function Null()
        return Null
end



a = Decode("{'cmd':'Take out trash','rsp':'Not now, I need a nap.'}")
print("Result:"..a.cmd)
print("Result:"..a.rsp)
os.sleep(3)