encode: Addd support for NaN/Infinity + \r
[luajson.git] / src / json / encode.lua
blobf7f0fe0e58dd9fab236690cb479863ba3f02a8e0
1 --[[
2 Licensed according to the included 'LICENSE' document
3 Author: Thomas Harning Jr <harningt@gmail.com>
4 ]]
5 local externalIsArray = IsArray -- Support for the special IsArray external function...
7 local tostring, string, type = tostring, string, type
8 local tonumber, math, assert = tonumber, math, assert
9 local table, pairs, ipairs = table, pairs, ipairs
10 local getmetatable, setmetatable = getmetatable, setmetatable
11 local select = select
12 local print = print
13 local error = error
14 local null = require("json.util").null
16 module("json.encode")
18 local encodingMap = {
19 ['\\'] = '\\\\',
20 ['"'] = '\\"',
21 ['\n'] = '\\n',
22 ['\t'] = '\\t',
23 ['\b'] = '\\b',
24 ['\f'] = '\\f',
25 ['\r'] = '\\r',
26 ['/'] = '\\/'
29 -- Pre-encode the control characters to speed up encoding...
30 -- NOTE: UTF-8 may not work out right w/ JavaScript
31 -- JavaScript uses 2 bytes after a \u... yet UTF-8 is a
32 -- byte-stream encoding, not pairs of bytes (it does encode
33 -- some letters > 1 byte, but base case is 1)
34 for i = 1, 255 do
35 local c = string.char(i)
36 if c:match('%c') and not encodingMap[c] then
37 encodingMap[c] = string.format('\\u%.4X', i)
38 end
39 end
40 local function encodeString(s)
41 return '"' .. string.gsub(s, '[\\"/%c%z]', encodingMap) .. '"'
42 end
44 local function isArray(val)
45 if externalIsArray and externalIsArray(val) then
46 return true
47 end
48 -- Use the 'n' element if it's a number
49 if type(val.n) == 'number' and math.floor(val.n) == val.n and val.n >= 1 then
50 return true
51 end
52 local len = #val
53 for k,v in pairs(val) do
54 if type(k) == 'number' and select(2, math.modf(k)) == 0 and 1<=k then
55 assert(isEncodable(v), "Invalid array element type:" .. type(v))
56 if k > len then -- Use Lua's length as absolute determiner
57 return false
58 end
59 else -- Not an integral key...
60 return false
61 end
62 end
64 return true
65 end
67 local function tonull(val)
68 if val == null then
69 return 'null'
70 end
71 end
73 -- Forward reference for encodeValue function
74 local encodeValue
75 local alreadyEncoded -- Table set at the beginning of every
76 -- encoding operation to empty to detect recursiveness
77 local function encodeTable(tab)
78 if alreadyEncoded[tab] then
79 error("Recursive table detected")
80 end
81 alreadyEncoded[tab] = true
82 local retVal = {}
83 -- Try for array
84 if isArray(tab) then
85 for i = 1,(tab.n or #tab) do
86 retVal[#retVal + 1] = encodeValue(tab[i])
87 end
88 return '[' .. table.concat(retVal, ',') .. ']'
89 else
90 -- Is table
91 for i, v in pairs(tab) do
92 local ti = type(i)
93 if ti == 'string' or ti == 'number' or ti == 'boolean' then
94 i = encodeString(tostring(i))
95 else
96 error("Invalid object index type: " .. ti)
97 end
98 retVal[#retVal + 1] = i .. ':' .. encodeValue(v)
99 end
100 return '{' .. table.concat(retVal, ',') .. '}'
104 local function encodeNumber(number)
105 local str = tostring(number)
106 if str == "nan" then return "NaN" end
107 if str == "inf" then return "Infinity" end
108 if str == "-inf" then return "-Infinity" end
109 return str
112 local allowAllNumbers = true
114 local encodeMapping = {
115 ['table' ] = encodeTable,
116 ['number' ] = allowAllNumbers and encodeNumber or tostring,
117 ['boolean'] = tostring,
118 ['function'] = tonull,
119 ['string' ] = encodeString,
120 ['nil'] = function() return 'null' end -- For the case that nils are encountered count them as nulls
122 function isEncodable(item)
123 return encodeMapping[type(item)] and not (type(item) == 'function' and item ~= null)
126 --[[local ]] function encodeValue(item)
127 local encoder = encodeMapping[type(item)]
128 if not encoder then
129 error("Invalid item to encode: " .. type(item))
131 return encoder(item)
134 function encode(data)
135 alreadyEncoded = {}
136 return encodeValue(data)
139 local mt = getmetatable(_M) or {}
140 mt.__call = function(self, ...)
141 return encode(...)
143 setmetatable(_M, mt)