json.lua.txt 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. -----------------------------------------------------------------------------
  2. -- Imports and dependencies
  3. -----------------------------------------------------------------------------
  4. local math = require("math")
  5. local string = require("string")
  6. local table = require("table")
  7. -----------------------------------------------------------------------------
  8. -- Module declaration
  9. -----------------------------------------------------------------------------
  10. local json = {} -- Public namespace
  11. local json_private = {} -- Private namespace
  12. -- Public constants
  13. json.EMPTY_ARRAY = {}
  14. json.EMPTY_OBJECT = {}
  15. json_private.attributeMap = {}
  16. -- Public functions
  17. -- Private functions
  18. local decode_scanArray
  19. local decode_scanComment
  20. local decode_scanConstant
  21. local decode_scanNumber
  22. local decode_scanObject
  23. local setObjectType
  24. local decode_scanString
  25. local decode_scanWhitespace
  26. local isArray
  27. local isEncodable
  28. local avoidLoopOver
  29. local index
  30. local getTableType
  31. local checkAttributeInfo
  32. local setClassType
  33. local bsonIgnore = "MongoDB.Bson.Serialization.Attributes.BsonIgnoreAttribute"
  34. local bsonRequired = "MongoDB.Bson.Serialization.Attributes.BsonRequiredAttribute"
  35. -----------------------------------------------------------------------------
  36. -- PUBLIC FUNCTIONS
  37. -----------------------------------------------------------------------------
  38. --- Encodes an arbitrary Lua object / variable.
  39. -- @param v The Lua object / variable to be JSON encoded.
  40. -- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode)
  41. function json.encode(v)
  42. avoidLoopOver()
  43. -- Handle nil values
  44. if v == nil then
  45. return "null"
  46. end
  47. local vtype = type(v)
  48. -- Handle strings
  49. if vtype == "string" then
  50. return '"' .. json_private.encodeString(v) .. '"' -- Need to handle encoding in string
  51. end
  52. -- Handle booleans
  53. if vtype == "number" or vtype == "boolean" then
  54. return tostring(v)
  55. end
  56. -- Handle tables
  57. if vtype == "table" then
  58. local rval = {}
  59. if not getmetatable(v) then
  60. return "{}"
  61. end
  62. table.insert(rval,'"_t":"'.. getmetatable(v).__name__ .. '"')
  63. -- Consider arrays separately
  64. local tType = getTableType(v)
  65. if (tType == "array") then
  66. for i = 1, #v do
  67. table.insert(rval, json.encode(v[i]))
  68. end
  69. elseif tType == "hashset" then
  70. for i, _ in pairs(v) do
  71. table.insert(rval, json.encode(i))
  72. end
  73. elseif tType == "dictionary" then
  74. for i, j in pairs(v) do
  75. if isEncodable(i) and isEncodable(j) then
  76. table.insert(rval, '"' .. json_private.encodeString(i) .. '":' .. json.encode(j))
  77. end
  78. end
  79. else
  80. local map = {}
  81. local needMap = {}
  82. checkAttributeInfo(v, map, needMap)
  83. map = table.unique(map, true)
  84. needMap = table.unique(needMap, true)
  85. for i, j in pairs(v) do
  86. if isEncodable(i) and isEncodable(j) and not IsVlaueInTable(map, i) then
  87. table.insert(rval, '"' .. json_private.encodeString(i) .. '":' .. json.encode(j))
  88. end
  89. end
  90. for i = 1, #needMap do
  91. local kk = needMap[i]
  92. local vv = v[kk]
  93. if isEncodable(kk) and isEncodable(vv) then
  94. table.insert(rval, '"' .. json_private.encodeString(kk) .. '":' .. json.encode(vv))
  95. end
  96. end
  97. end
  98. if tType == "array" or tType == "hashset" then
  99. return "[" .. table.concat(rval, ",") .. "]"
  100. else
  101. return "{" .. table.concat(rval, ",") .. "}"
  102. end
  103. end
  104. -- Handle null values
  105. if vtype == "function" and v == json.null then
  106. return "null"
  107. end
  108. assert(false, "encode attempt to encode unsupported type " .. vtype .. ":" .. tostring(v))
  109. end
  110. function avoidLoopOver()
  111. index = index + 1
  112. if index > 1000 then
  113. assert(false, "stack overflow")
  114. end
  115. end
  116. function getTableType(t)
  117. if not t then
  118. return nil
  119. end
  120. local meta = getmetatable(t)
  121. if (meta.__genericT__) then
  122. if (meta.__genericTKey__) then
  123. if (meta.__genericTValue__) then
  124. return "dictionary"
  125. else
  126. return "hashset"
  127. end
  128. else
  129. return "array"
  130. end
  131. else
  132. return nil
  133. end
  134. end
  135. function checkAttributeInfo(t, map, needMap)
  136. local base, meta, fields, properties, attributeName
  137. base = t
  138. meta = getmetatable(t)
  139. repeat
  140. avoidLoopOver()
  141. if (meta and meta.__metadata__) then
  142. fields = meta.__metadata__.fields
  143. if (fields) then
  144. for _, v in pairs(fields) do
  145. for i = 4, #v do
  146. attributeName = getmetatable(v[i]).__name
  147. if (attributeName == bsonIgnore) then
  148. table.insert(map, v[1])
  149. elseif attributeName == bsonRequired then
  150. table.insert(needMap, v[1])
  151. end
  152. end
  153. end
  154. end
  155. end
  156. if (meta and meta.__metadata__) then
  157. properties = meta.__metadata__.properties
  158. if (properties) then
  159. for _, v in pairs(properties) do
  160. for i = 5, #v do
  161. attributeName = getmetatable(v[i]).__name
  162. if (attributeName == bsonIgnore) then
  163. table.insert(map, v[1])
  164. elseif attributeName == bsonRequired then
  165. table.insert(needMap, v[1])
  166. end
  167. end
  168. end
  169. end
  170. end
  171. base = System.base(base)
  172. if (not base or not base.__name__) then
  173. break
  174. end
  175. meta = base
  176. until false
  177. end
  178. --- Decodes a JSON string and returns the decoded value as a Lua data structure / value.
  179. -- @param s The string to scan.
  180. -- @param startPos Optional starting position where the JSON string is located. Defaults to 1.
  181. -- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil,
  182. -- and the position of the first character after
  183. -- the scanned JSON object.
  184. function json.decode(s, startPos)
  185. startPos = startPos and startPos or 1
  186. startPos = decode_scanWhitespace(s, startPos)
  187. assert(startPos <= string.len(s), "Unterminated JSON encoded object found at position in [" .. s .. "]")
  188. local curChar = string.sub(s, startPos, startPos)
  189. -- Object
  190. if curChar == "{" then
  191. return decode_scanObject(s, startPos)
  192. end
  193. -- Array
  194. if curChar == "[" then
  195. return decode_scanArray(s, startPos)
  196. end
  197. -- Number
  198. if string.find("+-0123456789.e", curChar, 1, true) then
  199. return decode_scanNumber(s, startPos)
  200. end
  201. -- String
  202. if curChar == [["]] or curChar == [[']] then
  203. return decode_scanString(s, startPos)
  204. end
  205. if string.sub(s, startPos, startPos + 1) == "/*" then
  206. return json.decode(s, decode_scanComment(s, startPos))
  207. end
  208. -- Otherwise, it must be a constant
  209. return decode_scanConstant(s, startPos)
  210. end
  211. --- The null function allows one to specify a null value in an associative array (which is otherwise
  212. -- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null }
  213. function json.null()
  214. return json.null -- so json.null() will also return null ;-)
  215. end
  216. -----------------------------------------------------------------------------
  217. -- Internal, PRIVATE functions.
  218. -- Following a Python-like convention, I have prefixed all these 'PRIVATE'
  219. -- functions with an underscore.
  220. -----------------------------------------------------------------------------
  221. --- Scans an array from JSON into a Lua object
  222. -- startPos begins at the start of the array.
  223. -- Returns the array and the next starting position
  224. -- @param s The string being scanned.
  225. -- @param startPos The starting position for the scan.
  226. -- @return table, int The scanned array as a table, and the position of the next character to scan.
  227. function decode_scanArray(s, startPos)
  228. local array = {} -- The return value
  229. local stringLen = string.len(s)
  230. local objectType
  231. assert(
  232. string.sub(s, startPos, startPos) == "[",
  233. "decode_scanArray called but array does not start at position " .. startPos .. " in string:\n" .. s
  234. )
  235. startPos = startPos + 1
  236. -- Infinite loop for array elements
  237. local index = 1
  238. repeat
  239. startPos = decode_scanWhitespace(s, startPos)
  240. assert(startPos <= stringLen, "JSON String ended unexpectedly scanning array.")
  241. local curChar = string.sub(s, startPos, startPos)
  242. if (curChar == "]") then
  243. array = setObjectType(array,objectType)
  244. return array, startPos + 1
  245. end
  246. if (curChar == "\"") then
  247. assert(startPos <= stringLen, "JSON string ended unexpectedly scanning object.")
  248. -- Scan the key
  249. local key
  250. key, startPos = json.decode(s, startPos)
  251. assert(startPos <= stringLen, "JSON string ended unexpectedly searching for value of key " .. key)
  252. startPos = decode_scanWhitespace(s, startPos)
  253. assert(startPos <= stringLen, "JSON string ended unexpectedly searching for value of key " .. key)
  254. assert(string.sub(s, startPos, startPos) == ":", "JSON object key-value assignment mal-formed at " .. startPos)
  255. startPos = decode_scanWhitespace(s, startPos + 1)
  256. assert(startPos <= stringLen, "JSON string ended unexpectedly searching for value of key " .. key)
  257. local value
  258. value, startPos = json.decode(s, startPos)
  259. if key =="_t" then
  260. objectType = value
  261. end
  262. else
  263. if (curChar == ",") then
  264. startPos = decode_scanWhitespace(s, startPos + 1)
  265. end
  266. assert(startPos <= stringLen, "JSON String ended unexpectedly scanning array.")
  267. local object
  268. object, startPos = json.decode(s, startPos)
  269. array[index] = object
  270. index = index + 1
  271. end
  272. until false
  273. end
  274. --- Scans a comment and discards the comment.
  275. -- Returns the position of the next character following the comment.
  276. -- @param string s The JSON string to scan.
  277. -- @param int startPos The starting position of the comment
  278. function decode_scanComment(s, startPos)
  279. assert(
  280. string.sub(s, startPos, startPos + 1) == "/*",
  281. "decode_scanComment called but comment does not start at position " .. startPos
  282. )
  283. local endPos = string.find(s, "*/", startPos + 2)
  284. assert(endPos ~= nil, "Unterminated comment in string at " .. startPos)
  285. return endPos + 2
  286. end
  287. --- Scans for given constants: true, false or null
  288. -- Returns the appropriate Lua type, and the position of the next character to read.
  289. -- @param s The string being scanned.
  290. -- @param startPos The position in the string at which to start scanning.
  291. -- @return object, int The object (true, false or nil) and the position at which the next character should be
  292. -- scanned.
  293. function decode_scanConstant(s, startPos)
  294. local consts = {["true"] = true, ["false"] = false, ["null"] = nil}
  295. local constNames = {"true", "false", "null"}
  296. for i, k in pairs(constNames) do
  297. if string.sub(s, startPos, startPos + string.len(k) - 1) == k then
  298. return consts[k], startPos + string.len(k)
  299. end
  300. end
  301. assert(nil, "Failed to scan constant from string " .. s .. " at starting position " .. startPos)
  302. end
  303. --- Scans a number from the JSON encoded string.
  304. -- (in fact, also is able to scan numeric +- eqns, which is not
  305. -- in the JSON spec.)
  306. -- Returns the number, and the position of the next character
  307. -- after the number.
  308. -- @param s The string being scanned.
  309. -- @param startPos The position at which to start scanning.
  310. -- @return number, int The extracted number and the position of the next character to scan.
  311. function decode_scanNumber(s, startPos)
  312. local endPos = startPos + 1
  313. local stringLen = string.len(s)
  314. local acceptableChars = "+-0123456789.e"
  315. while (string.find(acceptableChars, string.sub(s, endPos, endPos), 1, true) and endPos <= stringLen) do
  316. endPos = endPos + 1
  317. end
  318. -- local stringValue = 'return ' .. string.sub(s, startPos, endPos - 1)
  319. -- local stringEval = loadstring(stringValue)
  320. -- assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos)
  321. local numberValue = string.sub(s, startPos, endPos - 1)
  322. return tonumber(numberValue), endPos
  323. end
  324. --- Scans a JSON object into a Lua object.
  325. -- startPos begins at the start of the object.
  326. -- Returns the object and the next starting position.
  327. -- @param s The string being scanned.
  328. -- @param startPos The starting position of the scan.
  329. -- @return table, int The scanned object as a table and the position of the next character to scan.
  330. function decode_scanObject(s, startPos)
  331. local object = {}
  332. local stringLen = string.len(s)
  333. local key, value,objectType
  334. assert(
  335. string.sub(s, startPos, startPos) == "{",
  336. "decode_scanObject called but object does not start at position " .. startPos .. " in string:\n" .. s
  337. )
  338. startPos = startPos + 1
  339. repeat
  340. startPos = decode_scanWhitespace(s, startPos)
  341. assert(startPos <= stringLen, "JSON string ended unexpectedly while scanning object.")
  342. local curChar = string.sub(s, startPos, startPos)
  343. if (curChar == "}") then
  344. object = setObjectType(object,objectType)
  345. return object, startPos + 1
  346. end
  347. if (curChar == ",") then
  348. startPos = decode_scanWhitespace(s, startPos + 1)
  349. end
  350. assert(startPos <= stringLen, "JSON string ended unexpectedly scanning object.")
  351. -- Scan the key
  352. key, startPos = json.decode(s, startPos)
  353. assert(startPos <= stringLen, "JSON string ended unexpectedly searching for value of key " .. key)
  354. startPos = decode_scanWhitespace(s, startPos)
  355. assert(startPos <= stringLen, "JSON string ended unexpectedly searching for value of key " .. key)
  356. assert(string.sub(s, startPos, startPos) == ":", "JSON object key-value assignment mal-formed at " .. startPos)
  357. startPos = decode_scanWhitespace(s, startPos + 1)
  358. assert(startPos <= stringLen, "JSON string ended unexpectedly searching for value of key " .. key)
  359. value, startPos = json.decode(s, startPos)
  360. if key =="_t" then
  361. objectType = value
  362. else
  363. object[key] = value
  364. end
  365. until false -- infinite loop while key-value pairs are found
  366. end
  367. function setObjectType(t, typeStr)
  368. if not typeStr then
  369. return t
  370. end
  371. t = setClassType(t, typeStr)
  372. return t
  373. end
  374. local getGenericTtype
  375. function setClassType(t, name)
  376. if not t then
  377. return nil
  378. end
  379. local meta = System.getClass(name)
  380. if meta then
  381. return setmetatable(t, meta)
  382. else
  383. local obj
  384. local mapMeta = json_private.attributeMap[name]
  385. if mapMeta then
  386. obj = setmetatable(t, mapMeta)
  387. else
  388. mapMeta = getGenericTtype(name)
  389. json_private.attributeMap[name] = mapMeta
  390. obj = setmetatable(t, mapMeta)
  391. end
  392. --后处理
  393. if (mapMeta.__genericT__) then
  394. if (mapMeta.__genericTKey__) then
  395. if (mapMeta.__genericTValue__) then
  396. --dictionary
  397. if (mapMeta.__genericTKey__.__name__ ~= "System.String") then
  398. local newT = {}
  399. for k, v in pairs(obj) do
  400. local i = tonumber(k)
  401. newT[i] = v
  402. end
  403. obj = setmetatable(newT, mapMeta)
  404. end
  405. else
  406. --hashset
  407. local newT = {}
  408. newT = setmetatable(newT, mapMeta)
  409. for _, v in pairs(obj) do
  410. newT:Add(v)
  411. end
  412. obj = newT
  413. end
  414. else
  415. --array/list
  416. end
  417. end
  418. return obj
  419. end
  420. end
  421. getGenericTtype = function(name)
  422. avoidLoopOver()
  423. local typeBase, typeT
  424. local arr = name:Split("`", 2)
  425. if (#arr > 1) then
  426. typeBase = arr[1]
  427. local s = arr[2]
  428. typeT = string.sub(s, string.find(s, "%[") + 1, string.find(s, "%]$") - 1)
  429. local tArr = typeT:Split(",")
  430. local tTable = {}
  431. for k, v in pairs(tArr) do
  432. tTable[k] = getGenericTtype(v)
  433. end
  434. return System.getClass(typeBase)(table.unpack(tTable))
  435. else
  436. if (name:EndsWith("[]")) then
  437. typeBase = "System.Array"
  438. typeT = string.sub(name, 1, string.find(name, "%[") - 1)
  439. return System.getClass(typeBase)(getGenericTtype(typeT))
  440. else
  441. return System.getClass(name)
  442. end
  443. end
  444. end
  445. -- START SoniEx2
  446. -- Initialize some things used by decode_scanString
  447. -- You know, for efficiency
  448. local escapeSequences = {
  449. ["\\t"] = "\t",
  450. ["\\f"] = "\f",
  451. ["\\r"] = "\r",
  452. ["\\n"] = "\n",
  453. ["\\b"] = "\b"
  454. }
  455. setmetatable(
  456. escapeSequences,
  457. {
  458. __index = function(t, k)
  459. -- skip "\" aka strip escape
  460. return string.sub(k, 2)
  461. end
  462. }
  463. )
  464. -- END SoniEx2
  465. --- Scans a JSON string from the opening inverted comma or single quote to the
  466. -- end of the string.
  467. -- Returns the string extracted as a Lua string,
  468. -- and the position of the next non-string character
  469. -- (after the closing inverted comma or single quote).
  470. -- @param s The string being scanned.
  471. -- @param startPos The starting position of the scan.
  472. -- @return string, int The extracted string as a Lua string, and the next character to parse.
  473. function decode_scanString(s, startPos)
  474. assert(startPos, "decode_scanString(..) called without start position")
  475. local startChar = string.sub(s, startPos, startPos)
  476. -- START SoniEx2
  477. -- PS: I don't think single quotes are valid JSON
  478. assert(startChar == [["]] or startChar == [[']], "decode_scanString called for a non-string")
  479. --assert(startPos, "String decoding failed: missing closing " .. startChar .. " for string at position " .. oldStart)
  480. local t = {}
  481. local i, j = startPos, startPos
  482. while string.find(s, startChar, j + 1) ~= j + 1 do
  483. local oldj = j
  484. i, j = string.find(s, "\\.", j + 1)
  485. local x, y = string.find(s, startChar, oldj + 1)
  486. if not i or x < i then
  487. i, j = x, y - 1
  488. end
  489. table.insert(t, string.sub(s, oldj + 1, i - 1))
  490. if string.sub(s, i, j) == "\\u" then
  491. local a = string.sub(s, j + 1, j + 4)
  492. j = j + 4
  493. local n = tonumber(a, 16)
  494. assert(n, "String decoding failed: bad Unicode escape " .. a .. " at position " .. i .. " : " .. j)
  495. -- math.floor(x/2^y) == lazy right shift
  496. -- a % 2^b == bitwise_and(a, (2^b)-1)
  497. -- 64 = 2^6
  498. -- 4096 = 2^12 (or 2^6 * 2^6)
  499. local x
  500. if n < 0x80 then
  501. x = string.char(n % 0x80)
  502. elseif n < 0x800 then
  503. -- [110x xxxx] [10xx xxxx]
  504. x = string.char(0xC0 + (math.floor(n / 64) % 0x20), 0x80 + (n % 0x40))
  505. else
  506. -- [1110 xxxx] [10xx xxxx] [10xx xxxx]
  507. x =
  508. string.char(
  509. 0xE0 + (math.floor(n / 4096) % 0x10),
  510. 0x80 + (math.floor(n / 64) % 0x40),
  511. 0x80 + (n % 0x40)
  512. )
  513. end
  514. table.insert(t, x)
  515. else
  516. table.insert(t, escapeSequences[string.sub(s, i, j)])
  517. end
  518. end
  519. table.insert(t, string.sub(j, j + 1))
  520. assert(
  521. string.find(s, startChar, j + 1),
  522. "String decoding failed: missing closing " ..
  523. startChar .. " at position " .. j .. "(for string at position " .. startPos .. ")"
  524. )
  525. return table.concat(t, ""), j + 2
  526. -- END SoniEx2
  527. end
  528. --- Scans a JSON string skipping all whitespace from the current start position.
  529. -- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached.
  530. -- @param s The string being scanned
  531. -- @param startPos The starting position where we should begin removing whitespace.
  532. -- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string
  533. -- was reached.
  534. function decode_scanWhitespace(s, startPos)
  535. local whitespace = " \n\r\t"
  536. local stringLen = string.len(s)
  537. while (string.find(whitespace, string.sub(s, startPos, startPos), 1, true) and startPos <= stringLen) do
  538. startPos = startPos + 1
  539. end
  540. return startPos
  541. end
  542. --- Encodes a string to be JSON-compatible.
  543. -- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-)
  544. -- @param s The string to return as a JSON encoded (i.e. backquoted string)
  545. -- @return The string appropriately escaped.
  546. local escapeList = {
  547. ['"'] = '\\"',
  548. ["\\"] = "\\\\",
  549. ["/"] = "\\/",
  550. ["\b"] = "\\b",
  551. ["\f"] = "\\f",
  552. ["\n"] = "\\n",
  553. ["\r"] = "\\r",
  554. ["\t"] = "\\t"
  555. }
  556. function json_private.encodeString(s)
  557. local s = tostring(s)
  558. return s:gsub(
  559. ".",
  560. function(c)
  561. return escapeList[c]
  562. end
  563. ) -- SoniEx2: 5.0 compat
  564. end
  565. -- Determines whether the given Lua type is an array or a table / dictionary.
  566. -- We consider any table an array if it has indexes 1..n for its n items, and no
  567. -- other data in the table.
  568. -- I think this method is currently a little 'flaky', but can't think of a good way around it yet...
  569. -- @param t The table to evaluate as an array
  570. -- @return boolean, number True if the table can be represented as an array, false otherwise. If true,
  571. -- the second returned value is the maximum
  572. -- number of indexed elements in the array.
  573. function isArray(t)
  574. -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable
  575. -- (with the possible exception of 'n')
  576. if (t == json.EMPTY_ARRAY) then
  577. return true, 0
  578. end
  579. if (t == json.EMPTY_OBJECT) then
  580. return false
  581. end
  582. local maxIndex = 0
  583. for k, v in pairs(t) do
  584. if (type(k) == "number" and math.floor(k) == k and 1 <= k) then -- k,v is an indexed pair
  585. if (not isEncodable(v)) then
  586. return false
  587. end -- All array elements must be encodable
  588. maxIndex = math.max(maxIndex, k)
  589. else
  590. if (k == "n") then
  591. if v ~= (t.n or #t) then
  592. return false
  593. end -- False if n does not hold the number of elements
  594. else -- Else of (k=='n')
  595. if isEncodable(v) then
  596. return false
  597. end
  598. end -- End of (k~='n')
  599. end -- End of k,v not an indexed pair
  600. end -- End of loop across all pairs
  601. return true, maxIndex
  602. end
  603. --- Determines whether the given Lua object / table / variable can be JSON encoded. The only
  604. -- types that are JSON encodable are: string, boolean, number, nil, table and json.null.
  605. -- In this implementation, all other types are ignored.
  606. -- @param o The object to examine.
  607. -- @return boolean True if the object should be JSON encoded, false if it should be ignored.
  608. function isEncodable(o)
  609. local t = type(o)
  610. return (t == "string" or t == "boolean" or t == "number" or t == "nil" or t == "table") or
  611. (t == "function" and o == json.null)
  612. end
  613. function table.unique(t, bArray)
  614. local check = {}
  615. local n = {}
  616. local idx = 1
  617. for k, v in pairs(t) do
  618. if not check[v] then
  619. if bArray then
  620. n[idx] = v
  621. idx = idx + 1
  622. else
  623. n[k] = v
  624. end
  625. check[v] = true
  626. end
  627. end
  628. return n
  629. end
  630. function IsVlaueInTable(t, v)
  631. if not t then
  632. return false
  633. end
  634. for _, vv in pairs(t) do
  635. if vv == v then
  636. return true
  637. end
  638. end
  639. return false
  640. end
  641. function IsKeyInTable(t, key)
  642. return not t and not t[key]
  643. end
  644. return json