DevSDK

Untitled

May 26th, 2018
331
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.16 KB | None | 0 0
  1. --[[ json.lua
  2. A compact pure-Lua JSON library.
  3. The main functions are: json.stringify, json.parse.
  4. ## json.stringify:
  5. This expects the following to be true of any tables being encoded:
  6. * They only have string or number keys. Number keys must be represented as
  7. strings in json; this is part of the json spec.
  8. * They are not recursive. Such a structure cannot be specified in json.
  9. A Lua table is considered to be an array if and only if its set of keys is a
  10. consecutive sequence of positive integers starting at 1. Arrays are encoded like
  11. so: `[2, 3, false, "hi"]`. Any other type of Lua table is encoded as a json
  12. object, encoded like so: `{"key1": 2, "key2": false}`.
  13. Because the Lua nil value cannot be a key, and as a table value is considerd
  14. equivalent to a missing key, there is no way to express the json "null" value in
  15. a Lua table. The only way this will output "null" is if your entire input obj is
  16. nil itself.
  17. An empty Lua table, {}, could be considered either a json object or array -
  18. it's an ambiguous edge case. We choose to treat this as an object as it is the
  19. more general type.
  20. To be clear, none of the above considerations is a limitation of this code.
  21. Rather, it is what we get when we completely observe the json specification for
  22. as arbitrary a Lua object as json is capable of expressing.
  23. ## json.parse:
  24. This function parses json, with the exception that it does not pay attention to
  25. \u-escaped unicode code points in strings.
  26. It is difficult for Lua to return null as a value. In order to prevent the loss
  27. of keys with a null value in a json string, this function uses the one-off
  28. table value json.null (which is just an empty table) to indicate null values.
  29. This way you can check if a value is null with the conditional
  30. `val == json.null`.
  31. If you have control over the data and are using Lua, I would recommend just
  32. avoiding null values in your data to begin with.
  33. --]]
  34.  
  35.  
  36. local json = {}
  37.  
  38.  
  39. -- Internal functions.
  40.  
  41. local function kind_of(obj)
  42. if type(obj) ~= 'table' then return type(obj) end
  43. local i = 1
  44. for _ in pairs(obj) do
  45. if obj[i] ~= nil then i = i + 1 else return 'table' end
  46. end
  47. if i == 1 then return 'table' else return 'array' end
  48. end
  49.  
  50. local function escape_str(s)
  51. local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'}
  52. local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'}
  53. for i, c in ipairs(in_char) do
  54. s = s:gsub(c, '\\' .. out_char[i])
  55. end
  56. return s
  57. end
  58.  
  59. -- Returns pos, did_find; there are two cases:
  60. -- 1. Delimiter found: pos = pos after leading space + delim; did_find = true.
  61. -- 2. Delimiter not found: pos = pos after leading space; did_find = false.
  62. -- This throws an error if err_if_missing is true and the delim is not found.
  63. local function skip_delim(str, pos, delim, err_if_missing)
  64. pos = pos + #str:match('^%s*', pos)
  65. if str:sub(pos, pos) ~= delim then
  66. if err_if_missing then
  67. error('Expected ' .. delim .. ' near position ' .. pos)
  68. end
  69. return pos, false
  70. end
  71. return pos + 1, true
  72. end
  73.  
  74. -- Expects the given pos to be the first character after the opening quote.
  75. -- Returns val, pos; the returned pos is after the closing quote character.
  76. local function parse_str_val(str, pos, val)
  77. val = val or ''
  78. local early_end_error = 'End of input found while parsing string.'
  79. if pos > #str then error(early_end_error) end
  80. local c = str:sub(pos, pos)
  81. if c == '"' then return val, pos + 1 end
  82. if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end
  83. -- We must have a \ character.
  84. local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}
  85. local nextc = str:sub(pos + 1, pos + 1)
  86. if not nextc then error(early_end_error) end
  87. return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc))
  88. end
  89.  
  90. -- Returns val, pos; the returned pos is after the number's final character.
  91. local function parse_num_val(str, pos)
  92. local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)
  93. local val = tonumber(num_str)
  94. if not val then error('Error parsing number at position ' .. pos .. '.') end
  95. return val, pos + #num_str
  96. end
  97.  
  98.  
  99. -- Public values and functions.
  100.  
  101. function json.stringify(obj, as_key)
  102. local s = {} -- We'll build the string as an array of strings to be concatenated.
  103. local kind = kind_of(obj) -- This is 'array' if it's an array or type(obj) otherwise.
  104. if kind == 'array' then
  105. if as_key then error('Can\'t encode array as key.') end
  106. s[#s + 1] = '['
  107. for i, val in ipairs(obj) do
  108. if i > 1 then s[#s + 1] = ', ' end
  109. s[#s + 1] = json.stringify(val)
  110. end
  111. s[#s + 1] = ']'
  112. elseif kind == 'table' then
  113. if as_key then error('Can\'t encode table as key.') end
  114. s[#s + 1] = '{'
  115. for k, v in pairs(obj) do
  116. if #s > 1 then s[#s + 1] = ', ' end
  117. s[#s + 1] = json.stringify(k, true)
  118. s[#s + 1] = ':'
  119. s[#s + 1] = json.stringify(v)
  120. end
  121. s[#s + 1] = '}'
  122. elseif kind == 'string' then
  123. return '"' .. escape_str(obj) .. '"'
  124. elseif kind == 'number' then
  125. if as_key then return '"' .. tostring(obj) .. '"' end
  126. return tostring(obj)
  127. elseif kind == 'boolean' then
  128. return tostring(obj)
  129. elseif kind == 'nil' then
  130. return 'null'
  131. else
  132. error('Unjsonifiable type: ' .. kind .. '.')
  133. end
  134. return table.concat(s)
  135. end
  136.  
  137. json.null = {} -- This is a one-off table to represent the null value.
  138.  
  139. function json.parse(str, pos, end_delim)
  140. pos = pos or 1
  141. if pos > #str then error('Reached unexpected end of input.') end
  142. local pos = pos + #str:match('^%s*', pos) -- Skip whitespace.
  143. local first = str:sub(pos, pos)
  144. if first == '{' then -- Parse an object.
  145. local obj, key, delim_found = {}, true, true
  146. pos = pos + 1
  147. while true do
  148. key, pos = json.parse(str, pos, '}')
  149. if key == nil then return obj, pos end
  150. if not delim_found then error('Comma missing between object items.') end
  151. pos = skip_delim(str, pos, ':', true) -- true -> error if missing.
  152. obj[key], pos = json.parse(str, pos)
  153. pos, delim_found = skip_delim(str, pos, ',')
  154. end
  155. elseif first == '[' then -- Parse an array.
  156. local arr, val, delim_found = {}, true, true
  157. pos = pos + 1
  158. while true do
  159. val, pos = json.parse(str, pos, ']')
  160. if val == nil then return arr, pos end
  161. if not delim_found then error('Comma missing between array items.') end
  162. arr[#arr + 1] = val
  163. pos, delim_found = skip_delim(str, pos, ',')
  164. end
  165. elseif first == '"' then -- Parse a string.
  166. return parse_str_val(str, pos + 1)
  167. elseif first == '-' or first:match('%d') then -- Parse a number.
  168. return parse_num_val(str, pos)
  169. elseif first == end_delim then -- End of an object or array.
  170. return nil, pos + 1
  171. else -- Parse true, false, or null.
  172. local literals = {['true'] = true, ['false'] = false, ['null'] = json.null}
  173. for lit_str, lit_val in pairs(literals) do
  174. local lit_end = pos + #lit_str - 1
  175. if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end
  176. end
  177. local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)
  178. error('Invalid json syntax starting at ' .. pos_info_str)
  179. end
  180. end
  181.  
  182. return json
Add Comment
Please, Sign In to add comment