diff --git a/README.md b/README.md index 9890719..c55e834 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,26 @@ print(json.encode({ 1, 2, 'fred', {first='mars',second='venus',third='earth'} }) ```json [1,2,"fred", {"first":"mars","second":"venus","third","earth"}] ``` +Order of object keys is not guaranteed. + +```lua +json = require('json') +print(json.encode( + { 1, 2, 'fred', {first='mars',second='venus',third='earth'} }, + { sort_keys=true, pretty=true, indent=4 })) +``` +```json +[ + 1, + 2, + "fred", + { + "first": "mars", + "second": "venus", + "third": "earth" + } +] +``` ## Decoding ## diff --git a/examples/tests.lua b/examples/tests.lua index 999bec3..67ed906 100755 --- a/examples/tests.lua +++ b/examples/tests.lua @@ -102,6 +102,13 @@ function testJSON4Lua() -- NB: This test can fail because of order: need to test further once -- decoding is supported. -- assert(r==[[{"age":35,"Name":"Craig","email":"craig@lateral.co.za"}]]) + r = json.encode(s, {sort_keys=true}) + assert(r==[[{"Name":"Craig","age":35,"email":"craig@lateral.co.za"}]], r) + + -- Test pretty-printing + s = { 2, 'joe', false, nil, 'hi' } + r = json.encode(s, { pretty=true, indent=2 }) + assert(r=='[\n 2,\n "joe",\n false,\n null,\n "hi"\n]', r) -- Test decode_scanWhitespace if nil then diff --git a/examples/timetrials.lua b/examples/timetrials.lua index cbda514..b278826 100755 --- a/examples/timetrials.lua +++ b/examples/timetrials.lua @@ -3,7 +3,7 @@ ]]-- -require('json') +json = require('json') require('os') require('table') @@ -43,4 +43,4 @@ print (jstr) --print(type(t1)) local t2 = os.clock() -print ("Elapsed time=" .. os.difftime(t2,t1) .. "s") \ No newline at end of file +print ("Elapsed time=" .. (t2 - t1) .. "s") diff --git a/json/json.lua b/json/json.lua index 9ab4abd..0200c0a 100755 --- a/json/json.lua +++ b/json/json.lua @@ -9,8 +9,12 @@ -- -- USAGE: -- This module exposes two functions: --- json.encode(o) +-- json.encode(o, options) -- Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string. +-- options is an optional table giving additional options: +-- sort_keys - sort object keys alphabetically +-- pretty - put spaces after ':' and ',', +-- indent - optional indent amount for pretty-printing -- json.decode(json_string) -- Returns a Lua object populated with the data encoded in the JSON string json_string. -- @@ -52,6 +56,9 @@ local decode_scanWhitespace local encodeString local isArray local isEncodable +local __genOrderedIndex +local orderedNext +local orderedPairs ----------------------------------------------------------------------------- -- PUBLIC FUNCTIONS @@ -59,7 +66,10 @@ local isEncodable --- Encodes an arbitrary Lua object / variable. -- @param v The Lua object / variable to be JSON encoded. -- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode) -function json.encode (v) +function json.encode (v, options, depth) + options = options or {} + depth = depth or 0 + -- Handle nil values if v==nil then return "null" @@ -80,23 +90,34 @@ function json.encode (v) -- Handle tables if vtype=='table' then local rval = {} + local indent = options.indent and ('\n' .. string.rep(' ', options.indent * (depth + 1))) or '' -- Consider arrays separately local bArray, maxCount = isArray(v) if bArray then for i = 1,maxCount do - table.insert(rval, json.encode(v[i])) + table.insert(rval, json.encode(v[i], options, depth + 1)) end else -- An object, not an array - for i,j in pairs(v) do + local iter = options.sort_keys and orderedPairs or pairs + local colon = options.pretty and '": ' or '":' + for i,j in iter(v) do if isEncodable(i) and isEncodable(j) then - table.insert(rval, '"' .. json_private.encodeString(i) .. '":' .. json.encode(j)) + table.insert(rval, '"' .. json_private.encodeString(i) .. colon .. json.encode(j, options, depth + 1)) end end end - if bArray then - return '[' .. table.concat(rval,',') ..']' + + local brackets = bArray and {'[',']'} or {'{','}'} + if options.indent then + local indent = string.rep(' ', options.indent) + return table.concat({ + brackets[1], + indent:rep(depth+1) .. table.concat(rval, ',\n' .. indent:rep(depth+1)), + indent:rep(depth) .. brackets[2] + }, '\n') else - return '{' .. table.concat(rval,',') .. '}' + local comma = options.pretty and ', ' or ',' + return brackets[1] .. table.concat(rval, comma) .. brackets[2] end end @@ -414,4 +435,54 @@ function isEncodable(o) return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null) end -return json \ No newline at end of file +--[[ +Ordered table iterator, allow to iterate on the natural order of the keys of a +table. + +Source: http://lua-users.org/wiki/SortedIteration +]] +function __genOrderedIndex( t ) + local orderedIndex = {} + for key in pairs(t) do + table.insert( orderedIndex, key ) + end + table.sort( orderedIndex ) + return orderedIndex +end + +function orderedNext(t, state) + -- Equivalent of the next function, but returns the keys in the alphabetic + -- order. We use a temporary ordered key table that is stored in the + -- table being iterated. + + --print("orderedNext: state = "..tostring(state) ) + if state == nil then + -- the first time, generate the index + t.__orderedIndex = __genOrderedIndex( t ) + key = t.__orderedIndex[1] + return key, t[key] + end + -- fetch the next value + key = nil + for i = 1,table.getn(t.__orderedIndex) do + if t.__orderedIndex[i] == state then + key = t.__orderedIndex[i+1] + end + end + + if key then + return key, t[key] + end + + -- no more value to return, cleanup + t.__orderedIndex = nil + return +end + +function orderedPairs(t) + -- Equivalent of the pairs() function on tables. Allows to iterate + -- in order + return orderedNext, t, nil +end + +return json