Advertisement
wolfd

JSON

Aug 1st, 2014
212
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 15.17 KB | None | 0 0
  1. -----------------------------------------------------------------------------
  2. -- JSON4Lua: JSON encoding / decoding support for the Lua language.
  3. -- json Module.
  4. -- Author: Craig Mason-Jones
  5. -- Homepage: http://json.luaforge.net/
  6. -- Version: 0.9.40
  7. -- This module is released under the MIT License (MIT).
  8. -- Please see LICENCE.txt for details.
  9. --
  10. -- USAGE:
  11. -- This module exposes two functions:
  12. --   encode(o)
  13. --     Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string.
  14. --   decode(json_string)
  15. --     Returns a Lua object populated with the data encoded in the JSON string json_string.
  16. --
  17. -- REQUIREMENTS:
  18. --   compat-5.1 if using Lua 5.0
  19. --
  20. -- CHANGELOG
  21. --   0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix).
  22. --          Fixed Lua 5.1 compatibility issues.
  23. --          Introduced json.null to have null values in associative arrays.
  24. --          encode() performance improvement (more than 50%) through table.concat rather than ..
  25. --          Introduced decode ability to ignore /**/ comments in the JSON string.
  26. --   0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays.
  27. -----------------------------------------------------------------------------
  28.  
  29. -----------------------------------------------------------------------------
  30. -- Imports and dependencies
  31. -----------------------------------------------------------------------------
  32.  
  33. local base = _G
  34.  
  35.  
  36. -- Public functions
  37.  
  38. -- Private functions
  39. local decode_scanArray
  40. local decode_scanComment
  41. local decode_scanConstant
  42. local decode_scanNumber
  43. local decode_scanObject
  44. local decode_scanString
  45. local decode_scanWhitespace
  46. local encodeString
  47. local isArray
  48. local isEncodable
  49.  
  50. -----------------------------------------------------------------------------
  51. -- PUBLIC FUNCTIONS
  52. -----------------------------------------------------------------------------
  53. --- Encodes an arbitrary Lua object / variable.
  54. -- @param v The Lua object / variable to be JSON encoded.
  55. -- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode)
  56. function encode (v)
  57.   -- Handle nil values
  58.   if v==nil then
  59.     return "null"
  60.   end
  61.  
  62.   local vtype = base.type(v)  
  63.  
  64.   -- Handle strings
  65.   if vtype=='string' then    
  66.     return '"' .. encodeString(v) .. '"'        -- Need to handle encoding in string
  67.   end
  68.  
  69.   -- Handle booleans
  70.   if vtype=='number' or vtype=='boolean' then
  71.     return base.tostring(v)
  72.   end
  73.  
  74.   -- Handle tables
  75.   if vtype=='table' then
  76.     local rval = {}
  77.     -- Consider arrays separately
  78.     local bArray, maxCount = isArray(v)
  79.     if bArray then
  80.       for i = 1,maxCount do
  81.         table.insert(rval, encode(v[i]))
  82.       end
  83.     else    -- An object, not an array
  84.       for i,j in base.pairs(v) do
  85.         if isEncodable(i) and isEncodable(j) then
  86.           table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j))
  87.         end
  88.       end
  89.     end
  90.     if bArray then
  91.       return '[' .. table.concat(rval,',') ..']'
  92.     else
  93.       return '{' .. table.concat(rval,',') .. '}'
  94.     end
  95.   end
  96.  
  97.   -- Handle null values
  98.   if vtype=='function' and v==null then
  99.     return 'null'
  100.   end
  101.  
  102.   base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v))
  103. end
  104.  
  105.  
  106. --- Decodes a JSON string and returns the decoded value as a Lua data structure / value.
  107. -- @param s The string to scan.
  108. -- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1.
  109. -- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil,
  110. -- and the position of the first character after
  111. -- the scanned JSON object.
  112. function decode(s, startPos)
  113.   startPos = startPos and startPos or 1
  114.   startPos = decode_scanWhitespace(s,startPos)
  115.   base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']')
  116.   local curChar = string.sub(s,startPos,startPos)
  117.   -- Object
  118.   if curChar=='{' then
  119.     return decode_scanObject(s,startPos)
  120.   end
  121.   -- Array
  122.   if curChar=='[' then
  123.     return decode_scanArray(s,startPos)
  124.   end
  125.   -- Number
  126.   if string.find("+-0123456789.e", curChar, 1, true) then
  127.     return decode_scanNumber(s,startPos)
  128.   end
  129.   -- String
  130.   if curChar==[["]] or curChar==[[']] then
  131.    return decode_scanString(s,startPos)
  132.  end
  133.  if string.sub(s,startPos,startPos+1)=='/*' then
  134.    return decode(s, decode_scanComment(s,startPos))
  135.  end
  136.  -- Otherwise, it must be a constant
  137.  return decode_scanConstant(s,startPos)
  138. end
  139.  
  140. --- The null function allows one to specify a null value in an associative array (which is otherwise
  141. -- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null }
  142. function null()
  143.  return null -- so json.null() will also return null ;-)
  144. end
  145. -----------------------------------------------------------------------------
  146. -- Internal, PRIVATE functions.
  147. -- Following a Python-like convention, I have prefixed all these 'PRIVATE'
  148. -- functions with an underscore.
  149. -----------------------------------------------------------------------------
  150.  
  151. --- Scans an array from JSON into a Lua object
  152. -- startPos begins at the start of the array.
  153. -- Returns the array and the next starting position
  154. -- @param s The string being scanned.
  155. -- @param startPos The starting position for the scan.
  156. -- @return table, int The scanned array as a table, and the position of the next character to scan.
  157. function decode_scanArray(s,startPos)
  158.   local array = {}  -- The return value
  159.  local stringLen = string.len(s)
  160.  base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s )
  161.  startPos = startPos + 1
  162.  -- Infinite loop for array elements
  163.  repeat
  164.    startPos = decode_scanWhitespace(s,startPos)
  165.    base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.')
  166.    local curChar = string.sub(s,startPos,startPos)
  167.    if (curChar==']') then
  168.      return array, startPos+1
  169.    end
  170.    if (curChar==',') then
  171.      startPos = decode_scanWhitespace(s,startPos+1)
  172.    end
  173.    base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.')
  174.    object, startPos = decode(s,startPos)
  175.    table.insert(array,object)
  176.  until false
  177. end
  178.  
  179. --- Scans a comment and discards the comment.
  180. -- Returns the position of the next character following the comment.
  181. -- @param string s The JSON string to scan.
  182. -- @param int startPos The starting position of the comment
  183. function decode_scanComment(s, startPos)
  184.  base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos)
  185.  local endPos = string.find(s,'*/',startPos+2)
  186.  base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos)
  187.  return endPos+2  
  188. end
  189.  
  190. --- Scans for given constants: true, false or null
  191. -- Returns the appropriate Lua type, and the position of the next character to read.
  192. -- @param s The string being scanned.
  193. -- @param startPos The position in the string at which to start scanning.
  194. -- @return object, int The object (true, false or nil) and the position at which the next character should be
  195. -- scanned.
  196. function decode_scanConstant(s, startPos)
  197.  local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
  198.  local constNames = {"true","false","null"}
  199.  
  200.  for i,k in base.pairs(constNames) do
  201.    --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k)
  202.    if string.sub(s,startPos, startPos + string.len(k) -1 )==k then
  203.      return consts[k], startPos + string.len(k)
  204.    end
  205.  end
  206.  base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos)
  207. end
  208.  
  209. --- Scans a number from the JSON encoded string.
  210. -- (in fact, also is able to scan numeric +- eqns, which is not
  211. -- in the JSON spec.)
  212. -- Returns the number, and the position of the next character
  213. -- after the number.
  214. -- @param s The string being scanned.
  215. -- @param startPos The position at which to start scanning.
  216. -- @return number, int The extracted number and the position of the next character to scan.
  217. function decode_scanNumber(s,startPos)
  218.  local endPos = startPos+1
  219.  local stringLen = string.len(s)
  220.  local acceptableChars = "+-0123456789.e"
  221.  while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true)
  222.     and endPos<=stringLen
  223.     ) do
  224.    endPos = endPos + 1
  225.  end
  226.  local stringValue = 'return ' .. string.sub(s,startPos, endPos-1)
  227.  local stringEval = base.loadstring(stringValue)
  228.  base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos)
  229.  return stringEval(), endPos
  230. end
  231.  
  232. --- Scans a JSON object into a Lua object.
  233. -- startPos begins at the start of the object.
  234. -- Returns the object and the next starting position.
  235. -- @param s The string being scanned.
  236. -- @param startPos The starting position of the scan.
  237. -- @return table, int The scanned object as a table and the position of the next character to scan.
  238. function decode_scanObject(s,startPos)
  239.  local object = {}
  240.  local stringLen = string.len(s)
  241.  local key, value
  242.  base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s)
  243.  startPos = startPos + 1
  244.  repeat
  245.    startPos = decode_scanWhitespace(s,startPos)
  246.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.')
  247.    local curChar = string.sub(s,startPos,startPos)
  248.    if (curChar=='}') then
  249.      return object,startPos+1
  250.    end
  251.    if (curChar==',') then
  252.      startPos = decode_scanWhitespace(s,startPos+1)
  253.    end
  254.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.')
  255.    -- Scan the key
  256.    key, startPos = decode(s,startPos)
  257.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
  258.    startPos = decode_scanWhitespace(s,startPos)
  259.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
  260.    base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos)
  261.    startPos = decode_scanWhitespace(s,startPos+1)
  262.    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
  263.    value, startPos = decode(s,startPos)
  264.    object[key]=value
  265.   until false   -- infinite loop while key-value pairs are found
  266. end
  267.  
  268. --- Scans a JSON string from the opening inverted comma or single quote to the
  269. -- end of the string.
  270. -- Returns the string extracted as a Lua string,
  271. -- and the position of the next non-string character
  272. -- (after the closing inverted comma or single quote).
  273. -- @param s The string being scanned.
  274. -- @param startPos The starting position of the scan.
  275. -- @return string, int The extracted string as a Lua string, and the next character to parse.
  276. function decode_scanString(s,startPos)
  277.  base.assert(startPos, 'decode_scanString(..) called without start position')
  278.  local startChar = string.sub(s,startPos,startPos)
  279.  base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string')
  280.   local escaped = false
  281.   local endPos = startPos + 1
  282.   local bEnded = false
  283.   local stringLen = string.len(s)
  284.   repeat
  285.     local curChar = string.sub(s,endPos,endPos)
  286.     -- Character escaping is only used to escape the string delimiters
  287.     if not escaped then
  288.       if curChar==[[\]] then
  289.         escaped = true
  290.       else
  291.         bEnded = curChar==startChar
  292.       end
  293.     else
  294.       -- If we're escaped, we accept the current character come what may
  295.       escaped = false
  296.     end
  297.     endPos = endPos + 1
  298.     base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos)
  299.   until bEnded
  300.   local stringValue = 'return ' .. string.sub(s, startPos, endPos-1)
  301.   local stringEval = base.loadstring(stringValue)
  302.   base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos)
  303.   return stringEval(), endPos  
  304. end
  305.  
  306. --- Scans a JSON string skipping all whitespace from the current start position.
  307. -- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached.
  308. -- @param s The string being scanned
  309. -- @param startPos The starting position where we should begin removing whitespace.
  310. -- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string
  311. -- was reached.
  312. function decode_scanWhitespace(s,startPos)
  313.   local whitespace=" \n\r\t"
  314.   local stringLen = string.len(s)
  315.   while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true)  and startPos <= stringLen) do
  316.     startPos = startPos + 1
  317.   end
  318.   return startPos
  319. end
  320.  
  321. --- Encodes a string to be JSON-compatible.
  322. -- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-)
  323. -- @param s The string to return as a JSON encoded (i.e. backquoted string)
  324. -- @return The string appropriately escaped.
  325. function encodeString(s)
  326.   s = string.gsub(s,'\\','\\\\')
  327.   s = string.gsub(s,'"','\\"')
  328.   s = string.gsub(s,"'","\\'")
  329.   s = string.gsub(s,'\n','\\n')
  330.   s = string.gsub(s,'\t','\\t')
  331.   return s
  332. end
  333.  
  334. -- Determines whether the given Lua type is an array or a table / dictionary.
  335. -- We consider any table an array if it has indexes 1..n for its n items, and no
  336. -- other data in the table.
  337. -- I think this method is currently a little 'flaky', but can't think of a good way around it yet...
  338. -- @param t The table to evaluate as an array
  339. -- @return boolean, number True if the table can be represented as an array, false otherwise. If true,
  340. -- the second returned value is the maximum
  341. -- number of indexed elements in the array.
  342. function isArray(t)
  343.   -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable
  344.   -- (with the possible exception of 'n')
  345.   local maxIndex = 0
  346.   for k,v in base.pairs(t) do
  347.     if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then  -- k,v is an indexed pair
  348.       if (not isEncodable(v)) then return false end -- All array elements must be encodable
  349.       maxIndex = math.max(maxIndex,k)
  350.     else
  351.       if (k=='n') then
  352.         if v ~= table.getn(t) then return false end  -- False if n does not hold the number of elements
  353.       else -- Else of (k=='n')
  354.         if isEncodable(v) then return false end
  355.       end  -- End of (k~='n')
  356.     end -- End of k,v not an indexed pair
  357.   end  -- End of loop across all pairs
  358.   return true, maxIndex
  359. end
  360.  
  361. --- Determines whether the given Lua object / table / variable can be JSON encoded. The only
  362. -- types that are JSON encodable are: string, boolean, number, nil, table and json.null.
  363. -- In this implementation, all other types are ignored.
  364. -- @param o The object to examine.
  365. -- @return boolean True if the object should be JSON encoded, false if it should be ignored.
  366. function isEncodable(o)
  367.   local t = base.type(o)
  368.   return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null)
  369. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement