├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── ReleaseNotes.txt ├── dist └── Makefile ├── docs ├── LuaJSON.txt ├── ReleaseNotes-0.10.txt ├── ReleaseNotes-0.9.1.txt ├── ReleaseNotes-0.9.txt ├── ReleaseNotes-1.0.1.txt ├── ReleaseNotes-1.0.2.txt ├── ReleaseNotes-1.0.3.txt ├── ReleaseNotes-1.0.txt ├── ReleaseNotes-1.1.1.txt ├── ReleaseNotes-1.1.2.txt ├── ReleaseNotes-1.1.txt ├── ReleaseNotes-1.2.1.txt ├── ReleaseNotes-1.2.2.txt ├── ReleaseNotes-1.2.txt ├── ReleaseNotes-1.3.1.txt ├── ReleaseNotes-1.3.2.txt ├── ReleaseNotes-1.3.3.txt ├── ReleaseNotes-1.3.4.txt └── ReleaseNotes-1.3.txt ├── lua ├── json.lua └── json │ ├── decode.lua │ ├── decode │ ├── composite.lua │ ├── number.lua │ ├── others.lua │ ├── state.lua │ ├── strings.lua │ └── util.lua │ ├── encode.lua │ ├── encode │ ├── array.lua │ ├── calls.lua │ ├── number.lua │ ├── object.lua │ ├── others.lua │ ├── output.lua │ ├── output_utility.lua │ └── strings.lua │ └── util.lua ├── rockspecs ├── luajson-0.10-1.rockspec ├── luajson-0.10-2.rockspec ├── luajson-0.9.1-1.rockspec ├── luajson-1.0-1.rockspec ├── luajson-1.0.1-1.rockspec ├── luajson-1.0.3-1.rockspec ├── luajson-1.1-1.rockspec ├── luajson-1.1.2-1.rockspec ├── luajson-1.2-1.rockspec ├── luajson-1.2.1-1.rockspec ├── luajson-1.2.2-1.rockspec ├── luajson-1.3-1.rockspec ├── luajson-1.3.1-1.rockspec ├── luajson-1.3.2-1.rockspec ├── luajson-1.3.2-2.rockspec ├── luajson-1.3.3-1.rockspec ├── luajson-1.3.4-1.rockspec └── luajson-scm-4.rockspec ├── tests ├── .luacov ├── data.txt ├── dataTest.lua ├── dataTest.php ├── hook_require.lua ├── lunit-calls.lua ├── lunit-depth.lua ├── lunit-empties-decode.lua ├── lunit-encoderfunc.lua ├── lunit-encoding.lua ├── lunit-nothrow-decode.lua ├── lunit-numbers.lua ├── lunit-simple-decode.lua ├── lunit-strings.lua ├── lunit-tests.lua ├── regressionTest.lua ├── test.lua ├── test │ ├── fail_all │ │ ├── colonInArray.json │ │ ├── commaAfterValue.json │ │ ├── doubleColon.json │ │ ├── extraArrayClose.json │ │ ├── extraValueAfterClose.json │ │ ├── functionExpression.json │ │ ├── hexNumber.json │ │ ├── lineBreakInString-escaped.json │ │ ├── mathExpression.json │ │ ├── missingArrayValue.json │ │ ├── missingColon.json │ │ ├── missingColon2.json │ │ ├── octalEscape.json │ │ ├── octalNumber.json │ │ ├── tabInString-escaped.json │ │ ├── trailingDoubleComma.json │ │ ├── unclosed_array.json │ │ └── unknownLiteral.json │ ├── fail_strict │ │ ├── hexEscape.json │ │ ├── lineBreakInString.json │ │ ├── mustBeArrayOrObject.json │ │ ├── singleQuoteEscape.json │ │ ├── singleQuotes.json │ │ ├── tabInString.json │ │ ├── trailingArrayComma.json │ │ ├── trailingObjectComma.json │ │ ├── unquotedKey.json │ │ └── whitespace_before_value.json │ ├── fail_strict_encode │ │ ├── bool.json │ │ ├── inf.json │ │ ├── nan.json │ │ ├── null.json │ │ ├── number.json │ │ ├── string.json │ │ └── undefined.json │ ├── pass │ │ ├── extremeNesting.json │ │ ├── pass1.json │ │ ├── pass3.json │ │ ├── stringWithEscapedAndUnescapedSlash.json │ │ ├── whitespace_before_array.json │ │ └── whitespace_before_object.json │ └── roundtrip │ │ ├── deepArray.json │ │ ├── emptyArray.json │ │ ├── emptyObject.json │ │ └── simpleArrayInObject.json ├── testutil.lua ├── timetrials.lua └── utf8_processor.lua └── util ├── createRock.lua ├── prepareNextRelease.lua └── processShortlog.lua /.travis.yml: -------------------------------------------------------------------------------- 1 | # Pull in python for access to pip for hererocks 2 | language: python 3 | 4 | sudo: false 5 | 6 | branches: 7 | only: 8 | - master 9 | - next 10 | - 1.2.x 11 | - 1.1.x 12 | - 1.0.x 13 | 14 | env: 15 | global: 16 | - LUAROCKS=2.4.2 17 | matrix: 18 | - LPEG=1.0.1-1 LUA="luajit 2.1" 19 | - LPEG=1.0.1-1 LUA="luajit 2.0" 20 | - LPEG=1.0.1-1 LUA="lua 5.3" 21 | - LPEG=1.0.1-1 LUA="lua 5.2" 22 | - LPEG=1.0.1-1 LUA="lua 5.1" 23 | - LPEG=0.12.2-1 LUA="luajit 2.1" 24 | - LPEG=0.12.2-1 LUA="luajit 2.0" 25 | - LPEG=0.12.2-1 LUA="lua 5.3" 26 | - LPEG=0.12.2-1 LUA="lua 5.2" 27 | - LPEG=0.12.2-1 LUA="lua 5.1" 28 | - LPEG=0.10.2-1 LUA="luajit 2.0" 29 | - LPEG=0.10.2-1 LUA="lua 5.2" 30 | - LPEG=0.10.2-1 LUA="lua 5.1" 31 | - LPEG=0.9-1 LUA="luajit 2.0" 32 | - LPEG=0.9-1 LUA="lua 5.1" 33 | - LPEG=0.8.1-1 LUA="luajit 2.0" 34 | - LPEG=0.8.1-1 LUA="lua 5.1" 35 | - LPEG=0.7-3 LUA="luajit 2.0" 36 | - LPEG=0.7-3 LUA="lua 5.1" 37 | 38 | branches: 39 | only: 40 | - master 41 | - /^feature[/]/ 42 | 43 | before_install: 44 | - pip install hererocks 45 | - hererocks here -r $LUAROCKS --$LUA 46 | - export PATH=$(pwd)/here/bin:$PATH 47 | - luarocks install luacov 48 | - luarocks install lunitx 49 | - luarocks install luafilesystem 50 | - luarocks install lpeg $LPEG 51 | 52 | script: 53 | - make LUA_BIN=lua LUNIT_BIN=lunit.sh LUA_INIT="require('luarocks.loader');require('luacov')" check 54 | 55 | after_success: 56 | # Rework the stats file and generate 57 | - sed -e "s|../lua/|lua/|" < tests/luacov.stats.out > luacov.stats.out 58 | - luacov 59 | - bash <(curl -s https://codecov.io/bash) 60 | 61 | notifications: 62 | email: 63 | on_success: change 64 | on_failure: always 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The following license is applied to all documents in this project with the 2 | exception of the 'tests' directory at the root. 3 | The 'tests' directory is dual-licenses Public Domain / MIT, whichever is 4 | least restrictive in your legal jurisdiction. 5 | 6 | The MIT License 7 | 8 | Copyright (c) 2008-2017 Thomas Harning Jr. 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in 18 | all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | THE SOFTWARE. 27 | 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile to prepare releases and run tests 3 | # 4 | 5 | DISTOPS= dist distclean dist-all dist-bzip2 dist-gzip dist-tar dist-zip 6 | .PHONY: all clean check $(DISTOPS) distcheck install 7 | 8 | LUA_BIN=lua 9 | LUNIT_BIN=lunit 10 | VERSION=luajson-$(shell git describe --abbrev=4 HEAD 2>/dev/null) 11 | 12 | MKDIR=mkdir -p 13 | PREFIX ?= /usr/local 14 | INSTALL_TOP= $(PREFIX) 15 | 16 | INSTALL_LMOD= $(INSTALL_TOP)/share/lua/5.1 17 | INSTALL_CMOD= $(INSTALL_TOP)/lib/lua/5.1 18 | 19 | all: 20 | @echo Building nothing - no binaries 21 | 22 | clean: 23 | @echo Cleaning nothing - no binaries 24 | 25 | $(DISTOPS): 26 | $(MAKE) $(MFLAGS) -C dist $@ 27 | 28 | # Config to make sure that Lua uses the contained Lua code 29 | LUA_PATH_SETUP=LUA_PATH="?/init.lua;../lua/?.lua;../lua/?/init.lua;$(LUA_PATH);" 30 | LUA_SETUP=LUA_OLD_INIT="$(LUA_INIT)" LUA_INIT="@hook_require.lua" $(LUA_PATH_SETUP) 31 | 32 | check-regression: 33 | cd tests && $(LUA_SETUP) $(LUA_BIN) regressionTest.lua 34 | check-unit: 35 | cd tests && $(LUA_SETUP) $(LUNIT_BIN) --interpreter $(LUA_BIN) lunit-*.lua 36 | 37 | check: check-regression check-unit 38 | 39 | distcheck-tar: dist-tar 40 | $(MKDIR) tmp 41 | tar -C tmp -xf dist/$(VERSION).tar 42 | cd tmp/$(VERSION) && make check 43 | rm -rf tmp 44 | 45 | distcheck-zip: dist-zip 46 | $(MKDIR) tmp 47 | unzip -q -d tmp dist/$(VERSION).zip 48 | cd tmp/$(VERSION) && make check 49 | rm -rf tmp 50 | 51 | distcheck: distcheck-zip distcheck-tar 52 | 53 | 54 | install: 55 | $(MKDIR) $(DESTDIR)$(INSTALL_LMOD) 56 | cp -p -r lua/* $(DESTDIR)$(INSTALL_LMOD) 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## LuaJSON 2 | JSON Parser/Constructor for Lua 3 | 4 | ### Author: 5 | Thomas Harning Jr. 6 | 7 | ### Source code: 8 | http://repo.or.cz/luajson 9 | 10 | ### Bug reports: 11 | http://github.com/harningt/luajson 12 | harningt@gmail.com 13 | 14 | ### Requirements 15 | Lua 5.1, 5.2, 5.3, LuaJIT 2.0, or LuaJIT 2.1 16 | LPeg (Tested with 0.7, 0.8, 0.9, 0.10, 0.12rc2, 1.0.1) 17 | For regressionTest: 18 | lfs (Tested with 1.6.3) 19 | ### For lunit-tests: 20 | lunitx >= 0.8 21 | 22 | ### NOTE: 23 | LPeg 0.11 may not work - it crashed during my initial tests, 24 | it is not in the test matrix. 25 | 26 | ### Lua versions tested recently: 27 | * Lua 5.1.5 28 | * Lua 5.2.4 29 | * Lua 5.3.4 30 | * LuaJIT-2.0.4 31 | * LuaJIT-2.1.0-beta2 32 | 33 | ### License 34 | All-but tests: MIT-style, See LICENSE for details 35 | tests/*: Public Domain / MIT - whichever is least restrictive 36 | 37 | ### Module/Function overview: 38 | ### json.encode (callable module referencing json.encode.encode) 39 | ___encode ( value : ANY-valid )___ 40 | 41 | Takes in a JSON-encodable value and returns the JSON-encoded text 42 | Valid input types: 43 | * table 44 | * array-like table (spec below) 45 | * string 46 | * number 47 | * boolean 48 | * 'null' - represented by json.util.null 49 | 50 | Table keys (string,number,boolean) are encoded as strings, others are erroneus 51 | Table values are any valid input-type 52 | Array-like tables are converted into JSON arrays... 53 | Position 1 maps to JSON Array position 0 54 | 55 | ### json.decode (callable module referencing json.decode.decode) 56 | ___decode (data : string, strict : optional boolean)___ 57 | 58 | Takes in a string of JSON data and converts it into a Lua object 59 | If 'strict' is set, then the strict JSON rule-set is used 60 | 61 | ### json.util 62 | #### Useful utilities 63 | ___null___ 64 | 65 | Reference value to represent 'null' in a well-defined way to 66 | allow for null values to be inserted into an array/table 67 | 68 | undefined 69 | 70 | Reference value to represent 'undefined' in a well-defined 71 | way to allow for undefined values to be inserted into an 72 | array/table. 73 | 74 | IsArray (t : ANY) 75 | 76 | Checks if the passed in object is a plain-old-array based on 77 | whether or not is has the LuaJSON array metatable attached 78 | or the custom __is_luajson_array metadata key stored. 79 | 80 | InitArray(t: table) 81 | 82 | Sets the 'array' marker metatable to guarantee the table is 83 | represented as a LuaJSON array type. 84 | 85 | isCall (t : ANY) 86 | 87 | Checks if the passed in object is a LuaJSON call object. 88 | 89 | buildCall(name : string, ... parameters) 90 | 91 | Builds a call object with the given name and set of parameters. 92 | The name is stored in the 'name' field and the parameters in 93 | the 'parameters' field as an array. 94 | 95 | #### Additional Utilities 96 | clone (t : table) 97 | 98 | Shallow-clones a table by iterating using pairs and assigning. 99 | 100 | ___printValue (tab : ANY, name : string) 101 | 102 | recursively prints out all object values - if duplicates found, reference printed 103 | 104 | ___merge (t : table, ... : tables) 105 | 106 | Shallow-merges a sequence of tables onto table t by iterating over each using 107 | pairs and assigning. 108 | 109 | #### Internal Utilities - Not to Use 110 | decodeCall 111 | doOptionMerge 112 | 113 | ### Attribution 114 | parsing test suite from JSON_checker project of http://www.json.org/ 115 | No listed license for these files in their package. 116 | -------------------------------------------------------------------------------- /ReleaseNotes.txt: -------------------------------------------------------------------------------- 1 | docs/ReleaseNotes-1.3.4.txt -------------------------------------------------------------------------------- /dist/Makefile: -------------------------------------------------------------------------------- 1 | VERSION= luajson-$(shell git describe --abbrev=4 HEAD 2>/dev/null) 2 | 3 | DIST_TAR= $(VERSION).tar 4 | DIST_BZ2= $(DIST_TAR).bz2 5 | DIST_GZ= $(DIST_TAR).gz 6 | DIST_ZIP= $(VERSION).zip 7 | 8 | .PHONY: all clean dist distclean dist-bzip2 dist-gzip dist-tar dist-zip 9 | .DELETE_ON_ERROR: 10 | 11 | all: dist-all 12 | 13 | clean: dist-clean 14 | @echo Cleaning nothing - no binaries 15 | 16 | all dist dist-all: dist-tar dist-bzip2 dist-gzip dist-zip 17 | 18 | distclean: 19 | rm -f luajson-*.bz2 20 | rm -f luajson-*.gz 21 | rm -f luajson-*.tar 22 | rm -f luajson-*.zip 23 | 24 | dist-tar: $(DIST_TAR) 25 | 26 | dist-bzip2: $(DIST_BZ2) 27 | 28 | dist-gzip: $(DIST_GZ) 29 | 30 | dist-zip: $(DIST_ZIP) 31 | 32 | $(DIST_TAR): 33 | cd .. && git archive --format=tar --prefix=$(VERSION)/ HEAD . -o $(CURDIR)/$@ 34 | 35 | $(DIST_BZ2): $(DIST_TAR) 36 | bzip2 -9 -c -f $(DIST_TAR) > $@ 37 | 38 | $(DIST_GZ): $(DIST_TAR) 39 | gzip -9 -c -f $(DIST_TAR) > $@ 40 | 41 | $(DIST_ZIP): 42 | cd .. && git archive --format=zip --prefix=$(VERSION)/ HEAD: -o $(CURDIR)/$@ 43 | -------------------------------------------------------------------------------- /docs/LuaJSON.txt: -------------------------------------------------------------------------------- 1 | LuaJSON(3) 2 | ========== 3 | :Author: Thomas Harning 4 | :Email: harningt@gmail.com 5 | :Date: 2009/04/29 6 | 7 | NAME 8 | ---- 9 | luajson - JSON encoder/decoder for Lua 10 | 11 | SYNOPSIS 12 | -------- 13 | local json = require("json") 14 | 15 | json.decode("json-string" [, parameters]) 16 | 17 | json.decode.getDecoder(parameters) 18 | 19 | json.encode(lua_value [, parameters]) 20 | 21 | json.encode.getEncoder(parameters) 22 | 23 | DESCRIPTION 24 | ----------- 25 | json.decode("json-string" [, parameters]):: 26 | Obtains a JSON decoder using `getDecoder` with the parameters specified, 27 | then performs the decoding operation. 28 | 29 | json.encode(lua_value [, parameters]):: 30 | Obtains a JSON encoder using `getEncoder` with the parameters specified, 31 | then performs the encoding operation. 32 | 33 | json.decode.getDecoder(parameters):: 34 | Obtains a JSON decoder configured with the given parameters or defaults. 35 | 36 | json.encode.getEncoder(parameters):: 37 | Obtains a JSON encoder configured with the given parameters or defaults. 38 | 39 | json.encode.strict:: 40 | A default parameter specification containing 'strict' rules for encoding 41 | 42 | json.decode.strict:: 43 | A default parameter specification containing 'strict' rules for decoding 44 | 45 | === COMMON PARAMETERS 46 | 47 | initialObject : boolean:: 48 | Specifies if the outermost element be an array or object 49 | 50 | allowUndefined : boolean:: 51 | Specifies if 'undefined' is an allowed value 52 | 53 | null : any:: 54 | Placeholder object for null values 55 | 56 | undefined : any:: 57 | Placeholder for undefined values 58 | 59 | number.nan : boolean:: 60 | Specifies if NaN is an allowed value 61 | 62 | number.inf : boolean:: 63 | Specifies if +/-Infinity is an allowed value 64 | 65 | === ENCODER-SPECIFIC PARAMETERS 66 | 67 | preProcess : `function(object)`:: 68 | Called for every value to be encoded, optionally altering. 69 | If returns `nil` then no value change occurs. 70 | 71 | output : function:: 72 | Function that returns an encoder specification (TBD), if null 73 | default used that returns a string. 74 | 75 | array.isArray : `function(object)`:: 76 | If `true`/`false` returned, then the value is authoritatively 77 | an array or not 78 | 79 | strings.xEncode : boolean:: 80 | Specifies if binary values are to be encoded with \xNN rather than \uNNNN 81 | 82 | strings.encodeSet : string:: 83 | http://www.lua.org/manual/5.1/manual.html#5.4.1[gmatch-style] set of 84 | characters that need to be escaped (to be contained in `[]`) 85 | 86 | strings.encodeSetAppend : string:: 87 | Set of characters that need to be escaped (to be contained in `[]`). 88 | Appended to the current encodeSet. 89 | 90 | ==== Default Configuration 91 | [source,lua] 92 | ---- 93 | array.isArray == json-util's isArray implementation 94 | allowUndefined = true 95 | number.nan = true 96 | number.inf = true 97 | strings.xEncode = false 98 | strings.encodeSet = '\\"/%z\1-\031' 99 | ---- 100 | 101 | ==== Strict Configuration 102 | [source,lua] 103 | ---- 104 | initialObject = true 105 | allowUndefined = false 106 | number.nan = false 107 | number.inf = false 108 | ---- 109 | 110 | === DECODER-SPECIFIC PARAMETERS 111 | 112 | unicodeWhitespace : boolean:: 113 | Specifies if unicode whitespace characters are counted 114 | 115 | array.allowEmptyElement / calls.allowEmptyElement / object.allowEmptyElement : boolean:: 116 | Specifies if missing values are allowed, replacing them with the value of 'undefined' or 'null' if undefined not allowed. 117 | Example cases: 118 | [1,, 3] ==> { 1, json.util.undefined, 3 } 119 | { x: } ==> { x = json.util.undefined } 120 | call(1,, 3) ==> call(1, json.util.undefined, 3) 121 | 122 | array.trailingComma / calls.trailingComma / object.trailingComma : boolean:: 123 | Specifies if extraneous trailing commas are ignored in declaration 124 | 125 | calls.defs : map:: 126 | Defines set of specifically permitted function definitions. 127 | If boolean value, determines if allowed or not, decoded as a call object. 128 | Function return-value is the decoded result. 129 | Function definition: `function(name, [arguments])` : output-value 130 | 131 | calls.allowUndefined : boolean:: 132 | Specifies if undefined call definitions are decoded as call objects. 133 | 134 | number.frac : boolean:: 135 | Specifies if numbers can have a decimal component (ex: `.01`) 136 | 137 | number.exp : boolean:: 138 | Specifies if exponents are allowed (ex: `1e2`) 139 | 140 | number.hex : boolean:: 141 | Specifies if hexadecimal numbers are allowed (ex: `0xDEADBEEF`) 142 | 143 | object.number : boolean:: 144 | Specifies if numbers can be object keys 145 | 146 | object.identifier : boolean:: 147 | Specifies if unquoted 'identifiers' can be object keys (matching `[A-Za-z_][A-Za-z0-9_]*`) 148 | 149 | strings.badChars : string:: 150 | Set of characters that should not be present in a string 151 | 152 | strings.additionalEscapes : LPeg expression:: 153 | LPeg expression to handle output (ex: `lpeg.C(1)` would take `\k` and spit out `k`) 154 | 155 | strings.escapeCheck : non-consuming LPeg expression:: 156 | LPeg expression to check if a given character is allowed to be an escape value 157 | 158 | strings.decodeUnicode:: 159 | `function (XX, YY)` handling \uXXYY situation to output decoded unicode sequence 160 | 161 | strings.strict_quotes : boolean:: 162 | Specifies if the `'` character is considered a quoting specifier 163 | 164 | ==== Default configuration 165 | 166 | [source,lua] 167 | ---- 168 | unicodeWhitespace = true 169 | initialObject = false 170 | allowUndefined = true 171 | array.trailingComma = true 172 | number.frac = true 173 | number.exp = true 174 | number.hex = false 175 | object.number = true 176 | object.identifier = true 177 | object.trailingComma = true 178 | strings.badChars = '' -- No characters considered bad in a string 179 | strings.additionalEscapes = false, -- disallow untranslated escapes 180 | strings.escapeCheck = #lpeg.S('bfnrtv/\\"xu\'z'), 181 | strings.decodeUnicode = utf8DecodeUnicode, 182 | strings.strict_quotes = false 183 | ---- 184 | 185 | ==== Strict configuration 186 | 187 | [source,lua] 188 | ---- 189 | initialObject = true 190 | allowUndefined = false 191 | array.trailingComma = false 192 | object.identifier = false 193 | object.trailingComma = false 194 | strings.badChars = '\b\f\n\r\t\v' 195 | strings.additionalEscapes = false -- no additional escapes 196 | strings.escapeCheck = #lpeg.S('bfnrtv/\\"u') --only these are allowed to be escaped 197 | strings.strict_quotes = true 198 | ---- 199 | 200 | AUTHOR 201 | ------ 202 | Written by Thomas Harning Jr., 203 | 204 | REFERENCES 205 | ---------- 206 | http://www.inf.puc-rio.br/~roberto/lpeg[LPeg] 207 | 208 | http://json.org[JSON] 209 | 210 | COPYING 211 | ------- 212 | Copyright (C) 2008-2009 Thomas Harning Jr. Free use of this software is granted 213 | under the terms of the MIT license. 214 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-0.10.txt: -------------------------------------------------------------------------------- 1 | luajson v0.10 Release Notes 2 | =========================== 3 | 4 | User Visible Changes 5 | -------------------- 6 | This release changes the system quite a bit from previous versions in 7 | that there is more configurability. There still is quite a bit of strong 8 | defaulting to make it accept more data formats and encode such that the 9 | most strict decoder *should* decode it. 10 | 11 | It is now possible to obtain wrapped-up encoders and decoders with options 12 | "compiled" in. With decoders this is particularly bound-up (LPEG patterns), 13 | encoders take advantage of this with less closure-count. 14 | 15 | Extended features of newer LPEG versions can also be used as they are 16 | available, either via function detection or version # parsing. 17 | 18 | New decoder features: 19 | 20 | * Single-quoted strings 21 | * Function-call decoding 22 | * Generic string management 23 | * String post-processing option 24 | * UTF-16 -> UTF-8 code decoding for "\uXXXX" from Emil Renner Berthing by default (not req) 25 | * Handle extended UTF-8 spacing characters and BOF for inter-element spacing 26 | 27 | New encoder features: 28 | 29 | * Function-call encoding 30 | * String pre-processing option 31 | * Fully modular encoding system 32 | * Encoding with similar option system as decoding 33 | 34 | 35 | Plans for next release 36 | ---------------------- 37 | 38 | 0.11 39 | 40 | * UTF-8 encoding/decoding validation (currently decoding only supported) 41 | * Optional \xXX encoding 42 | * JSON encoding with fully 'strict' options 43 | * Generic output mechanism (ropes, IO, etc) 44 | 45 | 1.0 beta 46 | 47 | * Full API documentation 48 | * Stable encoder/decoder option-apis. Final 1.0 release will maintain 49 | that all options used in code will be future-proof, at least until 2.0 50 | 51 | Updates since 0.9.1 52 | =================== 53 | 54 | Emil Renner Berthing (1): 55 | decoder: 56 | Added proper utf8 decoding of strings 57 | Thomas Harning Jr (38): 58 | -ungrouped- 59 | Merge branch '0.9-rockspec' 60 | base: 61 | Setup 0.9.1 rockspec with md5 of luaforge release 62 | added luarocks module to check out luajson from git (requires git patch) 63 | Preparing for next release pre-emptively 64 | decode-util: 65 | during a table merge, if a nil is located, skip it 66 | decoder/test: 67 | Added preliminary function-call handling 68 | Require name is string and func is function 69 | Support both patterns and strings as function name specifiers 70 | function calls receive as first argument the name used 71 | added support for multiple arguments to functions 72 | Moved function-call decoder to separate module 73 | added string post-processing utility 74 | decoder/tests: 75 | adds support for single-quoted strings w/ expected escape-handling 76 | support unicode whitespaces as whitespace around values 77 | decoder: 78 | Fully modularized strings/number/calls data-types 79 | Refactored boolean/null/undefined out into the 'others' module 80 | Provide workaround for missing lpeg.type in lpeg 0.6, 0.7, ... 81 | More stabilization of string decoder configuration as well as hooking unicode-handling in 82 | optimized 'number' option-defaulting 83 | unified configuration system into a fast-cached Tokend which only permits card-readng ops (writing in seprate referece...) 84 | Fixed return assertion to prevent 'Invalid JSON data' from being returned as second result 85 | Added simple LPEG version parser "x.y.z.." => x.y 86 | Added support for LPEG 0.9 accumulator replacement (fold) which may be faster 87 | string.char => string_char for optimization in utf8 decoder 88 | encoder/decoder: 89 | added support for 'undefined' value 90 | encoder/test: 91 | exposed and test string preprocessing option 92 | encoder: 93 | add string preprocessor handler (for encoding->utf8 mgmt) 94 | Break apart the encoder into distinct modules + add call encoding 95 | split out number encoder into own file to mirror decoder 96 | added applicable configuration options from the decoder to the number encoder 97 | enhanced configuration support to match the decoder's optionset where appropriate 98 | added 'initialObject' check to assert that the root object is in fact an object/array 99 | mirrored decoder's getDecoder => getEncoder setup 100 | performed full modularization of the encoder + added function-encode tests 101 | tests: 102 | Removed package.path alteration since 'make check' handles that as well as luarocks 103 | applied configuration optimizations take advantage of the encoder optimizations and simplifications from the decoders 104 | added UTF-16 => UTF-8 encoder test 105 | extends UTF tests with boundary tests and test # for break detect 106 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-0.9.1.txt: -------------------------------------------------------------------------------- 1 | luajson v0.9.1 Release Notes 2 | ============================ 3 | 4 | User Visible Changes 5 | -------------------- 6 | LuaRocks package created. 7 | 8 | Plans for next release 9 | ---------------------- 10 | * Make the encoder customizable in a manner similar to the decoder 11 | * Try to create an integration point for unicode handling 12 | * Add in custom 'function' handling for decoder (ex: b64(stringvalue)) 13 | 14 | Updates since 0.9 15 | ================= 16 | 17 | Thomas Harning Jr (1): 18 | base: 19 | Preparation to make luajson work as a luarocks rock 20 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-0.9.txt: -------------------------------------------------------------------------------- 1 | luajson v0.9 Release Notes 2 | ========================== 3 | 4 | User Visible Changes 5 | -------------------- 6 | There are more tests added in using the lunit framework. These are particularly useful for 7 | testing and debugging small components in the luajson decoder/encoder sections. 8 | 9 | Decoders are now customizable in a modular manner. For example: You can construct a decoder 10 | that allows for NaN and Inf values, but be strict on everything else. The support framework 11 | should permit for more enhancements and custom elements. 12 | 13 | Plans for next release 14 | ---------------------- 15 | * Make the encoder customizable in a manner similar to the decoder 16 | * Try to create an integration point for unicode handling 17 | * Add in custom 'function' handling for decoder (ex: b64(stringvalue)) 18 | * Create luarocks 19 | 20 | Updates since 0.6 21 | ================= 22 | 23 | Thomas Harning Jr (15): 24 | base: 25 | Added changelog (with details for 0.6) 26 | Noted lunit 0.4 requirement 27 | Make distcheck properly run the check inside the dist 28 | decoder/encoder: 29 | Added support for primitive Array metatable marker to mark arrays as such 30 | decoder/test: 31 | Added hex decode capability 32 | decoder: 33 | Cleaned up unused values in the base decoder 34 | Refactored decoding mechanism to permit building even more customized decoders 35 | Fixed decoder generation to return cached versions 36 | fixed decoder to remove misnamed parameters (causing failed tests) 37 | Allow negative inf 38 | test: 39 | Reconfigured the tests to behave more correctly and allow for a simpler addition mechanism. 40 | Enhanced the regression tests to handle roundtripping in a sane manner 41 | Updated regression tests for roundtripping to have a better output format and handle outer whitespace and moved tests 42 | Added lunit-based tests for fine-grained tests 43 | Made lunit tests use 'setup' to simplify decoder hooking 44 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.0.1.txt: -------------------------------------------------------------------------------- 1 | luajson v1.0.1 Release Notes 2 | ============================ 3 | 4 | User Visible Changes 5 | -------------------- 6 | Duplicate references are allowed if it does not cause circular references. 7 | 8 | This allows something like the following to be valid, where it was not before: 9 | 10 | a = {1, 2, 3} 11 | json.encode({a, a}) 12 | 13 | Decoding this will not be handled in any special way, it will be encoded as: 14 | 15 | [ [1,2,3], [1,2,3] ] 16 | 17 | Plans for next release 18 | ---------------------- 19 | The 1.1 release will contain support for arbitrary configurations in the same 20 | manner as the built-in configurations. It will also contain support for 21 | a simple decoder where `null` and `undefined` values are mapped to nil, 22 | rather than the more complicated round-trip capable constants. 23 | 24 | Updates since 1.0 25 | ================= 26 | 27 | Thomas Harning Jr (3): 28 | base: 29 | updates utility to help construct lua rocks to provide stable output 30 | encoder: 31 | adds support for non-circular duplicate object references 32 | tests: 33 | adds encoding test for circular/non-circular duplicate values 34 | 35 | Contributions 36 | ============= 37 | 38 | Thanks to Marcus Irven for reporting 39 | the issue with duplicate objects that aren't circular. 40 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.0.2.txt: -------------------------------------------------------------------------------- 1 | luajson v1.0.2 Release Notes 2 | ============================ 3 | 4 | User Visible Changes 5 | -------------------- 6 | In Windows, Lua's tostring and tonumber handling occur differently for 7 | the boundary cases of NaN, Infinity, and -Infinity values. This broke 8 | Windows encoding/decoding of JSON for which these values are present. 9 | 10 | Example input/outputs: 11 | 12 | Windows: 13 | 14 | * tonumber("Infinity") == nil 15 | * tostring(1/0) == "1.#INF" 16 | * tostring(0/0) == "-1.#IND" 17 | 18 | Linux: 19 | 20 | * tonumber("Infinity") == inf 21 | * tostring(1/0) == "inf" 22 | * tostring(0/0) == "nan" 23 | 24 | The code that decoded was changed to report the output manually on decode 25 | rather than decode 'generically' using 'tonumber'. 26 | 27 | The code that encoded was changed to use numerical equivalence checking 28 | before returning what 'tostring' would have, which should catch nearly 29 | all cases. 30 | 31 | Checks were updated to do: 32 | 33 | * nan => value ~= value 34 | * inf => value == math.huge 35 | * -inf => value == -math.huge 36 | 37 | Plans for next release 38 | ---------------------- 39 | Bugfixes only in the 1.0.x series. 40 | 41 | Updates since 1.0.1 42 | =================== 43 | 44 | Thomas Harning Jr (2): 45 | decoder/encoder: 46 | better handles number encoding/decoding for cases where tonumber/tostring don't work as expected 47 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.0.3.txt: -------------------------------------------------------------------------------- 1 | luajson v1.0.3 Release Notes 2 | ============================ 3 | 4 | User Visible Changes 5 | -------------------- 6 | LPeg 0.10 now supported. The only problem was the mechanism for 7 | version detection. I now use object detection which should better 8 | work for future version updates. 9 | 10 | Plans for next release 11 | ---------------------- 12 | See 1.2.x series for future plans. 13 | 14 | Updates since 1.0.2 15 | =================== 16 | 17 | Thomas Harning Jr (2): 18 | decode/object: 19 | compatibility fix for LPeg 0.10 - function-checking vs number parsing 20 | docs: 21 | adds reference for where bug reporting should go 22 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.0.txt: -------------------------------------------------------------------------------- 1 | luajson v1.0 Release Notes 2 | ========================== 3 | 4 | User Visible Changes 5 | -------------------- 6 | This release marks the 1.0 release that has been in the works for quite some time. 7 | One of the major release items is detailed documentation and a reasonably stable/ 8 | clean API. Some features have been eliminated since they were not effective or 9 | used. 10 | 11 | Features removed: 12 | * Lax number handling... before this release, numbers with ugly definition were 13 | permitted, such as leading zeroes, multiple negative signs, ... 14 | * String post-processing... this feature was unused and appears to serve no 15 | useful purpose. Removing it made unifying encoding/decoding options simpler 16 | * Adds global pre-processing to handle arbitrary conversion of values during 17 | encoding. 18 | 19 | Changes: 20 | * String escape codes updated to match what the JSON spec uses for encoding 21 | 22 | Additions: 23 | * Add optional \x00 character encoding to handle encoding single bytes 24 | * Custom output stream functionality to create optimal output mechanisms 25 | that can stream JSON output. 26 | 27 | Plans for next release 28 | ---------------------- 29 | Currently there is nothing in the works for the next release. The project 30 | is very open to outside suggestions and patches for enhanced functionality... 31 | so please contribute. 32 | 33 | Updates since 0.10 34 | ================== 35 | 36 | Thomas Harning Jr (46): 37 | -ungrouped- 38 | Rockspec 0.10 39 | moved lua sources into 'lua' to assist luarocks management 40 | base: 41 | makefiles learn high bzip2 and gzip compression 42 | adds new rockspec for github 43 | updated github rockspec to reference all of the files 44 | Makefile learns split-apart checking 45 | rockspec 0.10-2 update -- previous one had improper luaforge link 46 | decoder/strings: 47 | removed unused 'postProcess' option 48 | removed unused 'null' decoder 49 | remove catchall escape handler from default 50 | applied minor cleanups 51 | added error analysis for strings + generic bad-character utility 52 | decoder/tests: 53 | remove depth limiting support code, implementation, and tests 54 | implemented non-defined call captures and updated tests to handle cases 55 | removed lax number handling (leading zeroes, multiple negatives, and spaces between - and digits) 56 | fixes ambiguous/invalid hex numbers with decimal/scientific format 57 | decoder: 58 | reorders replacements based on spec, adds 'x' escape 59 | allow whitespace before strict object/array 60 | generalizes decoding using grammar entries 61 | docs: 62 | preliminary documentation of options 63 | created markdown documentation to be translated into manpage-style documentation 64 | moves documentation to asciidoc 65 | encoder/decoder/tests: 66 | moves json.decode.util.merge => json.util.merge 67 | call-generation/handling moved to json.util and unified 68 | removes discrimination between calls with 1 and * arguments 69 | encoder/decoder: 70 | applied license headers to files missing them 71 | encoder/tests: 72 | move preprocessing to root to permit global pre-processor (ex: take special strings and base64-encode to call) 73 | encoder: 74 | add vertical tabulation character to the encoded set 75 | adds \xXX encoding and customizable encoding set 76 | adds custom output-encoder support while keeping default case optimal using templates 77 | adds output-stream writer for encoding 78 | consulted JSON specification and updated string definitions to match 79 | note encoding > 7F with escapes is hazardous 80 | properly capture expected range-of-characters to map 81 | updates strict encoding to use correct calls check and ordering 82 | correct language of disabled 'undefined' value 83 | cleans up code and errors 84 | removes extraneous 'defs' from the default options 85 | tests: 86 | adds \u and \x escape encoding/decoding test cases 87 | test strict form of string decoding tests 88 | updates strict string test to use strict encoder 89 | regression-test uses test strict encoder in strict tests 90 | adds strict encoder regression tests 91 | report error cause during regressionTest encoding failures 92 | added pre-value whitespace tests 93 | updated tests to match new stricter default syntax 94 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.1.1.txt: -------------------------------------------------------------------------------- 1 | 2 | luajson v1.1.1 Release Notes 3 | ============================ 4 | 5 | User Visible Changes 6 | -------------------- 7 | In Windows, Lua's tostring and tonumber handling occur differently for 8 | the boundary cases of NaN, Infinity, and -Infinity values. This broke 9 | Windows encoding/decoding of JSON for which these values are present. 10 | 11 | Example input/outputs: 12 | 13 | Windows: 14 | 15 | * tonumber("Infinity") == nil 16 | * tostring(1/0) == "1.#INF" 17 | * tostring(0/0) == "-1.#IND" 18 | 19 | Linux: 20 | 21 | * tonumber("Infinity") == inf 22 | * tostring(1/0) == "inf" 23 | * tostring(0/0) == "nan" 24 | 25 | The code that decoded was changed to report the output manually on decode 26 | rather than decode 'generically' using 'tonumber'. 27 | 28 | The code that encoded was changed to use numerical equivalence checking 29 | before returning what 'tostring' would have, which should catch nearly 30 | all cases. 31 | 32 | Checks were updated to do: 33 | 34 | * nan => value ~= value 35 | * inf => value == math.huge 36 | * -inf => value == -math.huge 37 | 38 | Plans for next release 39 | ---------------------- 40 | The next version should hopefully have more error reporting capabilities, 41 | such as returning where an error occurred and possibly details. 42 | How this will be done is a research-item. 43 | 44 | Updates since 1.1 45 | ================= 46 | 47 | Thomas Harning Jr (3): 48 | -ungrouped- 49 | Merge branch '1.0.x' 50 | decoder/encoder: 51 | better handles number encoding/decoding for cases where tonumber/tostring don't work as expected 52 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.1.2.txt: -------------------------------------------------------------------------------- 1 | luajson v1.1.2 Release Notes 2 | ============================ 3 | 4 | User Visible Changes 5 | -------------------- 6 | LPeg 0.10 now supported. The only problem was the mechanism for 7 | version detection. I now use object detection which should better 8 | work for future version updates. 9 | 10 | 11 | Plans for next release 12 | ---------------------- 13 | See 1.2.x series for future plans. 14 | 15 | Updates since 1.1.1 16 | =================== 17 | 18 | Thomas Harning Jr (4): 19 | decode/object: 20 | compatibility fix for LPeg 0.10 - function-checking vs number parsing 21 | docs: 22 | adds reference for where bug reporting should go 23 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.1.txt: -------------------------------------------------------------------------------- 1 | luajson v1.1 Release Notes 2 | ========================== 3 | 4 | User Visible Changes 5 | -------------------- 6 | A new default configuration "simple" is available that does not use 7 | `json.util.null` and `json.util.undefined` to represent their associated 8 | JSON values. 9 | 10 | There is also a change in the decoding of arrays with nulls to avoid 11 | special casing it. 12 | 13 | Plans for next release 14 | ---------------------- 15 | No plans yet for what a future release might contain. 16 | 17 | Updates since 1.0.1 18 | =================== 19 | 20 | Thomas Harning Jr (9): 21 | -ungrouped- 22 | Merge branch '1.0.x' 23 | decoder: 24 | learns how to handle arbitrary sets of defaults (now + simple) 25 | adds support for nil return values 26 | fixes bug where function call returns nothing => error 27 | adds support for 'simple' decoding to map null and undefined => nil 28 | removes special casing of null values in an array 29 | adds support for caching new arbitrary configs 30 | tests: 31 | tests added for simple/default decoders stand-alone, in arrays, in objects 32 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.2.1.txt: -------------------------------------------------------------------------------- 1 | luajson v1.2.1 Release Notes 2 | ============================ 3 | 4 | User Visible Changes 5 | -------------------- 6 | LPeg 0.10 now supported. The only problem was the mechanism for 7 | version detection. I now use object detection which should better 8 | work for future version updates. 9 | 10 | Plans for next release 11 | ---------------------- 12 | No change in the plans for the next release. 13 | For the next feature release, I plan on better fleshing out the enhanced 14 | error handling and possibly adding in the more customizable output 15 | system. For previews of the features that might make it into the 16 | next release, please see the "next" branch. Note that this branch 17 | is volatile and features may vanish rather than be reverted for cleanliness. 18 | 19 | Enhanced error handling introduced in LPeg 0.10 might help this task. 20 | 21 | 22 | Updates since 1.2 23 | ================= 24 | 25 | Thomas Harning Jr (6): 26 | decode/object: 27 | compatibility fix for LPeg 0.10 - function-checking vs number parsing 28 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.2.2.txt: -------------------------------------------------------------------------------- 1 | luajson v1.2.2 Release Notes 2 | ============================ 3 | 4 | User Visible Changes 5 | -------------------- 6 | Addressed issue #14, increasing the decoding max stack depth to ~190 using 7 | nested parsing. This should help deal with deeply-nested objects until 8 | a better solution is achieved. 9 | Addressed issue #15, the 'preProcess' function receives an additional 10 | parameter 'isObjectKey' to help it determine what sort of processing 11 | should be done. 12 | Adds a 'string' processor function that can be used to specially handle 13 | string values and custom escape values, such as for forcing ASCII by 14 | encoding all UTF-8 values using escape codes. 15 | 16 | Plans for next release 17 | ---------------------- 18 | Future enhancements in the 1.2 series include linearizing the parsing 19 | layer to make deep nesting less costly in terms of stack space. 20 | For the next series, better error handling is a goal. 21 | 22 | Updates since 1.2.1 23 | =================== 24 | 25 | Thomas Harning Jr (16): 26 | base: 27 | splits out Makefile to multiple files to manage distinct goals and adds install target 28 | decoder: 29 | updates decoders for nested data types to use Cmt when available to handle deep nesting by directly matching inside 30 | encoder/strings: 31 | adds simple processor+tests - with example use case of encoding multi-byte utf8 values 32 | encoder/tests: 33 | communicates the status that a value is an object key to the preProcess function if present 34 | tests: 35 | adds decoder test for maximum depth of parser 36 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.2.txt: -------------------------------------------------------------------------------- 1 | luajson v1.2 Release Notes 2 | ========================== 3 | 4 | Tested LPeg versions: 5 | 6 | * 0.9 - pass 7 | * 0.8 - pass 8 | * 0.7 - pass 9 | * 0.6 - fail : cannot handle 'nil' returns -> tranforms into booleans 10 | 11 | Note that 0.6 passes strict mode tests and behaviors. It just breaks 12 | down when decoding the plain return value `null` or `undefined` that 13 | would result in a `nil` return value. 14 | 15 | User Visible Changes 16 | -------------------- 17 | 18 | setObjectKey 19 | ~~~~~~~~~~~~ 20 | Thanks to Alexander Gladysh, we now have the "setObjectKey" 21 | hook to permit more customizable behavior during object construction. 22 | 23 | There is a new 'object' option that you can set to handle custom 24 | key-value setting. If you set in the configuration table 25 | .object.setObjectKey to a function with the signature 26 | `(tab, key, value)`, you will override the `rawset` style behavior. 27 | 28 | Example: 29 | json.decode("{'1':true, x:true}") 30 | --> { ["1"] = true, x = true } 31 | function newSetObjectKey(tab, key, value) 32 | local numkey = tonumber(key) 33 | key = numkey or key 34 | rawset(tab, key, value) 35 | end 36 | json.decode("{'1':true, x:true}", { object = { setObjectKey = newSetObjectKey } }) 37 | --> { [1] = true, x = true } 38 | 39 | require builtins update 40 | ~~~~~~~~~~~~~~~~~~~~~~~ 41 | Also changed is the major change to `require` as many of the default 42 | packages as possible to better help custom embedding scenarios where 43 | 'default' tables such as `io` and `os` may require on-demand loading 44 | via `require`. 45 | 46 | Plans for next release 47 | ---------------------- 48 | For the next feature release, I plan on better fleshing out the enhanced 49 | error handling and possibly adding in the more customizable output 50 | system. For previews of the features that might make it into the 51 | next release, please see the "next" branch. Note that this branch 52 | is volatile and features may vanish rather than be reverted for cleanliness. 53 | 54 | Updates since 1.1.1 55 | =================== 56 | 57 | Thomas Harning Jr (4): 58 | all: 59 | pulls in all but base/package modules via require (io,os,string,table,math) 60 | decoder: 61 | implements setObjectKey for LPeg < 0.9 62 | Moves 'setObjectKey' from root of `options` to `options.object` 63 | docs: 64 | adds reference for where bug reporting should go 65 | Alexander Gladysh (1): 66 | decoder: 67 | configurable object key filter 68 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.3.1.txt: -------------------------------------------------------------------------------- 1 | luajson v1.3.1 Release Notes 2 | ============================ 3 | 4 | User Visible Changes 5 | -------------------- 6 | This release does not introduce many changes. 7 | François Perrad kindly produced patches to make it work better with Lua 5.2 8 | and handle strict modes. 9 | It also introduces additional documentation about what varieties of Lua 10 | have been tested. 11 | 12 | Plans for next release 13 | ---------------------- 14 | A future release will have a stronger focus on decoder performance. It may 15 | mean a reduction in flexibility by removing options and breaking compatibility. 16 | 17 | 18 | Updates since 1.3 19 | ================= 20 | 21 | François Perrad (7): 22 | -ungrouped- 23 | fix with Lua 5.2 strict 24 | remove the condition around `_ENV = nil` 25 | remove conditional (is_52) code 26 | fix tests 27 | fix test with Lua 5.2.0 28 | lfs : 29 | fix with Lua 5.2 strict 30 | Thomas Harning Jr (7): 31 | -ungrouped- 32 | Merge pull request #21 from fperrad/compat51_52 33 | Merge pull request #22 from fperrad/patch-1 34 | Merge pull request #23 from fperrad/patch-2 35 | all: 36 | adds module metadata as suggested by fperrad 37 | decoder: 38 | fixes 5.2-strict mode state object 39 | docs: 40 | notes tested Lua versions 41 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.3.2.txt: -------------------------------------------------------------------------------- 1 | luajson v1.3.2 Release Notes 2 | ============================ 3 | 4 | User Visible Changes 5 | -------------------- 6 | One of the main changes in this release is compatibility with LPeg 0.12 and 7 | more sane _ENV manipulation to avoid problems in strict environments. 8 | The documentation has also been updated to reflect the behavior of the 9 | often-used 'merge' utility to avoid surprises in behavior. 10 | 11 | Plans for next release 12 | ---------------------- 13 | A future release will have a stronger focus on decoder performance. It may 14 | mean a reduction in flexibility by removing options and breaking compatibility. 15 | 16 | Updates since 1.3.1 17 | =================== 18 | 19 | Thomas Harning Jr (6): 20 | all: 21 | sets _ENV to nil using local _ENV = nil to avoid global writing 22 | decoder: 23 | fixes compatibility with lpeg 0.12 by closing off 0-length captures in loops 24 | docs: 25 | documents testing with the Penlight 'strict' module 26 | adds merge behavior documentation 27 | updates LPeg testing information 28 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.3.3.txt: -------------------------------------------------------------------------------- 1 | luajson v1.3.3 Release Notes 2 | ============================ 3 | 4 | User Visible Changes 5 | -------------------- 6 | The main changes for this release are: 7 | 8 | * Allow objects with metatables to declare themselves as an array by 9 | setting __is_luajson_array as true in the metatable. 10 | * Pass all unit tests in Lua 5.2 (lunit tests had an issue) 11 | 12 | Plans for next release 13 | ---------------------- 14 | As declared in v1.3.2, a future release will have a stronger focus on decoder 15 | performance. It may mean a reduction in flexibility by removing options and 16 | breaking compatibility... so this would likely be either v1.4 or v2.0 17 | 18 | 19 | Updates since 1.3.2 20 | =================== 21 | 22 | Thomas Harning Jr (16): 23 | all: 24 | adds module metadata as suggested by fperrad 25 | base: 26 | fixes link for luarocks download 27 | updates rock creation tool to include tag version 28 | encoder: 29 | if __is_luajson_array is set on a metatable, consider the value as an array 30 | tests: 31 | fixes lunit test to work with Lua 5.2 environment management 32 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.3.4.txt: -------------------------------------------------------------------------------- 1 | luajson v1.3.4 Release Notes 2 | ============================ 3 | 4 | User Visible Changes 5 | -------------------- 6 | 7 | Maintenance for older versions (ex: 1.0.x, 1.1.x, 1.2.x) will be on-demand. No 8 | longer will changes be introduced there and trickled down. 9 | 10 | The main changes for this release are: 11 | 12 | * Lua 5.3 and LuaJIT 2.1-beta support added. 13 | * LPEG 1.0.1 support verified. 14 | * Documentation enhancements: 15 | * README converted to MarkDown. 16 | * Address utility method documentation to address exposed 17 | public-use methods. 18 | * Address utility method documentation to call out unsupported 19 | internal methods. 20 | * Address GitHub issue #38: 21 | * A new allowEmptyElement option is available that will make 22 | array and map building more forgiving for missing entries. 23 | 24 | Plans for next release 25 | ---------------------- 26 | At this point there are no particular strong plans for next release. 27 | 28 | Enhancing error output and performance are goals, but not terribly high on my 29 | priority list. 30 | 31 | 32 | Updates since 1.3.3 33 | =================== 34 | 35 | Thomas Harning Jr (65): 36 | base: 37 | drops obsolute SCM rockspecs 38 | updates lunit tests to pass through interpreter 39 | adds travis-ci build file 40 | fixes travis 41 | travis fixes - drop unsupported Lua5.2 lpeg versions and add missing make build 42 | fixes regression test lua path 43 | travis fixes - drop unsupported Lua5.2 lpeg versions and add missing make build 44 | applies required branch list to travis 45 | rewrite language as python for broader support of the .travis.yml 46 | updates Travis-CI build structure to use sample 47 | lua is always symlinked as the correct version 48 | expands to newer versions of Lua/luajit 49 | switch to lunitx due to expanded Lua 5.2, Lua 5.3 support 50 | build: 51 | reverses build order to assign greater importance to newer items 52 | updates to LPEG 0.12.2 due to Lua5.3 compat patch 53 | adds LPeg 1.0.0 and trims a build from 0.12.2 tree 54 | ci: 55 | updates travis.yml to allow feature branches 56 | update used version of LuaRocks 57 | replaces LPEG 1.0.0 testing with 1.0.1 testing 58 | codecov: 59 | apply report generation automatically 60 | compat: 61 | fixes unpack calls to lookup table.unpack before the 5.2-deprecated/removed unpack 62 | decode+docs+tests: 63 | adds support for (array/object/calls).allowEmptyElement to address GH #38 64 | decode.calls+tests: 65 | handle trailingCommas in own setting vs borrowing 'array' configuration 66 | decode.strings+tests: 67 | fixes additionalEscapes to override builtin escapes and side-step escapeCheck needing to be altered 68 | decoder: 69 | drops unused 'expected' utility method 70 | docs: 71 | updates README to indicate updated testing results and drops mention of travis-ci pieces due to removal 72 | cleans up reference for non-present functionality GH #37 and adds details for json.util 73 | updates README to reflect new test LPEG platform and Lua 5.3.4 release 74 | encode: 75 | simplify encoder map building to current use case to enhance coverage 76 | license: 77 | updates copyright year 78 | luacov: 79 | update path to point to right location 80 | not using luacov-coveralls 81 | test: 82 | update tests to not rely removed math.pow 83 | update utf-8 handler to be more strict about integer division 84 | tests: 85 | updates to Lua 5.2/5.3 requirements 86 | updates more strongly to Lua 5.2/5.3 requirements by dropping _M usage 87 | adds positive test case with nothrow to enhance test coverage 88 | adds multiple registered calls and nested calls to enhance test coverage 89 | enhances coverage for number parsing options 90 | enhances coverage for strings.additionalEscapes decoding option 91 | enhances coverage for NaN handling 92 | enhances coverage for undefined and unregistered method encoding 93 | travis: 94 | synchronizes with moteus/lua-travis-example to fix LuaJIT builds 95 | use hererocks for local install management 96 | adds in coveralls coverage reporting 97 | run coveralls in verbose mode 98 | try global coveralls support 99 | manually do codecov generation after transforming to relative paths 100 | attempt downgrading luacov to 0.9.1 to fix line stats 101 | Ewan Breakey (1): 102 | -ungrouped- 103 | Update README to use MarkDown 104 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.3.txt: -------------------------------------------------------------------------------- 1 | luajson v1.3 Release Notes 2 | ========================== 3 | 4 | User Visible Changes 5 | -------------------- 6 | A global 'nothrow' option was added to change the behavior of parsing 7 | to not throw, but return `nil, error`. 8 | Error messages were also enhanced for many cases to include better details as 9 | to why the parsing may have failed. 10 | Some examples follow: 11 | 12 | For `[1i]` 13 | unexpected character @ character: 3 0:3 [i] line: 14 | [1i 15 | For `{x:1` 16 | Unclosed elements present 17 | 18 | The parser was also unrolled so that LPeg was placed more into a lexer role 19 | rather than full-blown parser. This solves the problem of LPeg running into 20 | problems in my parser due to unclosed choices at a small performance cost. 21 | 22 | Plans for next release 23 | ---------------------- 24 | A future release will have a stronger focus on decoder performance. It may 25 | mean a reduction in flexibility by removing options and breaking compatibility. 26 | 27 | 28 | Updates since 1.2.2 29 | =================== 30 | 31 | Thomas Harning Jr (9): 32 | all: 33 | 5.2 compatibility 34 | update option processing system to do more pre-configuration to better prepare for future changes 35 | base: 36 | rockspec scm-4 added due to file layout change 37 | fixes luajson-scm-4.rockspec 38 | decoder: 39 | adds global 'nothrow' option to protect call automatically 40 | error detection enhancements from next branch 41 | overhaul update to be iterative to avoid call-stack buildup w/ limits and horrible performance hit 42 | tests: 43 | adds null array roundtrip issue per gh-4 44 | Francois Perrad (2): 45 | -ungrouped- 46 | fix loadstring when Lua 5.2 is strict 47 | fix test suite with Lua 5.2 48 | -------------------------------------------------------------------------------- /lua/json.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local decode = require("json.decode") 6 | local encode = require("json.encode") 7 | local util = require("json.util") 8 | 9 | local _G = _G 10 | 11 | local _ENV = nil 12 | 13 | local json = { 14 | _VERSION = "1.3.5", 15 | _DESCRIPTION = "LuaJSON : customizable JSON decoder/encoder", 16 | _COPYRIGHT = "Copyright (c) 2007-2017 Thomas Harning Jr. ", 17 | decode = decode, 18 | encode = encode, 19 | util = util 20 | } 21 | 22 | _G.json = json 23 | 24 | return json 25 | -------------------------------------------------------------------------------- /lua/json/decode.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local lpeg = require("lpeg") 6 | 7 | local error = error 8 | local pcall = pcall 9 | 10 | local jsonutil = require("json.util") 11 | local merge = jsonutil.merge 12 | local util = require("json.decode.util") 13 | 14 | local decode_state = require("json.decode.state") 15 | 16 | local setmetatable, getmetatable = setmetatable, getmetatable 17 | local assert = assert 18 | local ipairs, pairs = ipairs, pairs 19 | local string_char = require("string").char 20 | 21 | local type = type 22 | 23 | local require = require 24 | 25 | local _ENV = nil 26 | 27 | local modulesToLoad = { 28 | "composite", 29 | "strings", 30 | "number", 31 | "others" 32 | } 33 | local loadedModules = { 34 | } 35 | 36 | local json_decode = {} 37 | 38 | json_decode.default = { 39 | unicodeWhitespace = true, 40 | initialObject = false, 41 | nothrow = false 42 | } 43 | 44 | local modes_defined = { "default", "strict", "simple" } 45 | 46 | json_decode.simple = {} 47 | 48 | json_decode.strict = { 49 | unicodeWhitespace = true, 50 | initialObject = true, 51 | nothrow = false 52 | } 53 | 54 | for _,name in ipairs(modulesToLoad) do 55 | local mod = require("json.decode." .. name) 56 | if mod.mergeOptions then 57 | for _, mode in pairs(modes_defined) do 58 | mod.mergeOptions(json_decode[mode], mode) 59 | end 60 | end 61 | loadedModules[#loadedModules + 1] = mod 62 | end 63 | 64 | -- Shift over default into defaultOptions to permit build optimization 65 | local defaultOptions = json_decode.default 66 | json_decode.default = nil 67 | 68 | local function generateDecoder(lexer, options) 69 | -- Marker to permit detection of final end 70 | local marker = {} 71 | local parser = lpeg.Ct((options.ignored * lexer)^0 * lpeg.Cc(marker)) * options.ignored * (lpeg.P(-1) + util.unexpected()) 72 | local decoder = function(data) 73 | local state = decode_state.create(options) 74 | local parsed = parser:match(data) 75 | assert(parsed, "Invalid JSON data") 76 | local i = 0 77 | while true do 78 | i = i + 1 79 | local item = parsed[i] 80 | if item == marker then break end 81 | if type(item) == 'function' and item ~= jsonutil.undefined and item ~= jsonutil.null then 82 | item(state) 83 | else 84 | state:set_value(item) 85 | end 86 | end 87 | if options.initialObject then 88 | assert(type(state.previous) == 'table', "Initial value not an object or array") 89 | end 90 | -- Make sure stack is empty 91 | assert(state.i == 0, "Unclosed elements present") 92 | return state.previous 93 | end 94 | if options.nothrow then 95 | return function(data) 96 | local status, rv = pcall(decoder, data) 97 | if status then 98 | return rv 99 | else 100 | return nil, rv 101 | end 102 | end 103 | end 104 | return decoder 105 | end 106 | 107 | local function buildDecoder(mode) 108 | mode = mode and merge({}, defaultOptions, mode) or defaultOptions 109 | for _, mod in ipairs(loadedModules) do 110 | if mod.mergeOptions then 111 | mod.mergeOptions(mode) 112 | end 113 | end 114 | local ignored = mode.unicodeWhitespace and util.unicode_ignored or util.ascii_ignored 115 | -- Store 'ignored' in the global options table 116 | mode.ignored = ignored 117 | 118 | --local grammar = { 119 | -- [1] = mode.initialObject and (ignored * (object_type + array_type)) or value_type 120 | --} 121 | local lexer 122 | for _, mod in ipairs(loadedModules) do 123 | local new_lexer = mod.generateLexer(mode) 124 | lexer = lexer and lexer + new_lexer or new_lexer 125 | end 126 | return generateDecoder(lexer, mode) 127 | end 128 | 129 | -- Since 'default' is nil, we cannot take map it 130 | local defaultDecoder = buildDecoder(json_decode.default) 131 | local prebuilt_decoders = {} 132 | for _, mode in pairs(modes_defined) do 133 | if json_decode[mode] ~= nil then 134 | prebuilt_decoders[json_decode[mode]] = buildDecoder(json_decode[mode]) 135 | end 136 | end 137 | 138 | --[[ 139 | Options: 140 | number => number decode options 141 | string => string decode options 142 | array => array decode options 143 | object => object decode options 144 | initialObject => whether or not to require the initial object to be a table/array 145 | allowUndefined => whether or not to allow undefined values 146 | ]] 147 | local function getDecoder(mode) 148 | mode = mode == true and json_decode.strict or mode or json_decode.default 149 | local decoder = mode == nil and defaultDecoder or prebuilt_decoders[mode] 150 | if decoder then 151 | return decoder 152 | end 153 | return buildDecoder(mode) 154 | end 155 | 156 | local function decode(data, mode) 157 | local decoder = getDecoder(mode) 158 | return decoder(data) 159 | end 160 | 161 | local mt = {} 162 | mt.__call = function(self, ...) 163 | return decode(...) 164 | end 165 | 166 | json_decode.getDecoder = getDecoder 167 | json_decode.decode = decode 168 | json_decode.util = util 169 | setmetatable(json_decode, mt) 170 | 171 | return json_decode 172 | -------------------------------------------------------------------------------- /lua/json/decode/composite.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local pairs = pairs 6 | local type = type 7 | 8 | local lpeg = require("lpeg") 9 | 10 | local util = require("json.decode.util") 11 | local jsonutil = require("json.util") 12 | 13 | local rawset = rawset 14 | 15 | local assert = assert 16 | local tostring = tostring 17 | 18 | local error = error 19 | local getmetatable = getmetatable 20 | 21 | local _ENV = nil 22 | 23 | local defaultOptions = { 24 | array = { 25 | allowEmptyElement = false, 26 | ignoreLength = false, 27 | trailingComma = true 28 | }, 29 | object = { 30 | allowEmptyElement = false, 31 | trailingComma = true, 32 | number = true, 33 | identifier = true, 34 | setObjectKey = rawset 35 | }, 36 | calls = { 37 | allowEmptyElement = false, 38 | defs = nil, 39 | -- By default, do not allow undefined calls to be de-serialized as call objects 40 | allowUndefined = false, 41 | trailingComma = true 42 | } 43 | } 44 | 45 | local modeOptions = { 46 | default = nil, 47 | strict = { 48 | array = { 49 | trailingComma = false 50 | }, 51 | object = { 52 | trailingComma = false, 53 | number = false, 54 | identifier = false 55 | } 56 | } 57 | } 58 | 59 | local function BEGIN_ARRAY(state) 60 | state:push() 61 | state:new_array() 62 | end 63 | local function END_ARRAY(state) 64 | state:end_array() 65 | state:pop() 66 | end 67 | 68 | local function BEGIN_OBJECT(state) 69 | state:push() 70 | state:new_object() 71 | end 72 | local function END_OBJECT(state) 73 | state:end_object() 74 | state:pop() 75 | end 76 | 77 | local function END_CALL(state) 78 | state:end_call() 79 | state:pop() 80 | end 81 | 82 | local function SET_KEY(state) 83 | state:set_key() 84 | end 85 | 86 | local function NEXT_VALUE(state) 87 | state:put_value() 88 | end 89 | 90 | local function mergeOptions(options, mode) 91 | jsonutil.doOptionMerge(options, true, 'array', defaultOptions, mode and modeOptions[mode]) 92 | jsonutil.doOptionMerge(options, true, 'object', defaultOptions, mode and modeOptions[mode]) 93 | jsonutil.doOptionMerge(options, true, 'calls', defaultOptions, mode and modeOptions[mode]) 94 | end 95 | 96 | 97 | local isPattern 98 | if lpeg.type then 99 | function isPattern(value) 100 | return lpeg.type(value) == 'pattern' 101 | end 102 | else 103 | local metaAdd = getmetatable(lpeg.P("")).__add 104 | function isPattern(value) 105 | return getmetatable(value).__add == metaAdd 106 | end 107 | end 108 | 109 | 110 | local function generateSingleCallLexer(name, func) 111 | if type(name) ~= 'string' and not isPattern(name) then 112 | error("Invalid functionCalls name: " .. tostring(name) .. " not a string or LPEG pattern") 113 | end 114 | -- Allow boolean or function to match up w/ encoding permissions 115 | if type(func) ~= 'boolean' and type(func) ~= 'function' then 116 | error("Invalid functionCalls item: " .. name .. " not a function") 117 | end 118 | local function buildCallCapture(name) 119 | return function(state) 120 | if func == false then 121 | error("Function call on '" .. name .. "' not permitted") 122 | end 123 | state:push() 124 | state:new_call(name, func) 125 | end 126 | end 127 | local nameCallCapture 128 | if type(name) == 'string' then 129 | nameCallCapture = lpeg.P(name .. "(") * lpeg.Cc(name) / buildCallCapture 130 | else 131 | -- Name matcher expected to produce a capture 132 | nameCallCapture = name * "(" / buildCallCapture 133 | end 134 | -- Call func over nameCallCapture and value to permit function receiving name 135 | return nameCallCapture 136 | end 137 | 138 | local function generateNamedCallLexers(options) 139 | if not options.calls or not options.calls.defs then 140 | return 141 | end 142 | local callCapture 143 | for name, func in pairs(options.calls.defs) do 144 | local newCapture = generateSingleCallLexer(name, func) 145 | if not callCapture then 146 | callCapture = newCapture 147 | else 148 | callCapture = callCapture + newCapture 149 | end 150 | end 151 | return callCapture 152 | end 153 | 154 | local function generateCallLexer(options) 155 | local lexer 156 | local namedCapture = generateNamedCallLexers(options) 157 | if options.calls and options.calls.allowUndefined then 158 | lexer = generateSingleCallLexer(lpeg.C(util.identifier), true) 159 | end 160 | if namedCapture then 161 | lexer = lexer and lexer + namedCapture or namedCapture 162 | end 163 | if lexer then 164 | lexer = lexer + lpeg.P(")") * lpeg.Cc(END_CALL) 165 | end 166 | return lexer 167 | end 168 | 169 | local function generateLexer(options) 170 | local ignored = options.ignored 171 | local array_options, object_options = options.array, options.object 172 | local lexer = 173 | lpeg.P("[") * lpeg.Cc(BEGIN_ARRAY) 174 | + lpeg.P("]") * lpeg.Cc(END_ARRAY) 175 | + lpeg.P("{") * lpeg.Cc(BEGIN_OBJECT) 176 | + lpeg.P("}") * lpeg.Cc(END_OBJECT) 177 | + lpeg.P(":") * lpeg.Cc(SET_KEY) 178 | + lpeg.P(",") * lpeg.Cc(NEXT_VALUE) 179 | if object_options.identifier then 180 | -- Add identifier match w/ validation check that it is in key 181 | lexer = lexer + lpeg.C(util.identifier) * ignored * lpeg.P(":") * lpeg.Cc(SET_KEY) 182 | end 183 | local callLexers = generateCallLexer(options) 184 | if callLexers then 185 | lexer = lexer + callLexers 186 | end 187 | return lexer 188 | end 189 | 190 | local composite = { 191 | mergeOptions = mergeOptions, 192 | generateLexer = generateLexer 193 | } 194 | 195 | return composite 196 | -------------------------------------------------------------------------------- /lua/json/decode/number.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local lpeg = require("lpeg") 6 | local tonumber = tonumber 7 | local jsonutil = require("json.util") 8 | local merge = jsonutil.merge 9 | local util = require("json.decode.util") 10 | 11 | local _ENV = nil 12 | 13 | local digit = lpeg.R("09") 14 | local digits = digit^1 15 | 16 | -- Illegal octal declaration 17 | local illegal_octal_detect = #(lpeg.P('0') * digits) * util.denied("Octal numbers") 18 | 19 | local int = (lpeg.P('-') + 0) * (lpeg.R("19") * digits + illegal_octal_detect + digit) 20 | 21 | local frac = lpeg.P('.') * digits 22 | 23 | local exp = lpeg.S("Ee") * (lpeg.S("-+") + 0) * digits 24 | 25 | local nan = lpeg.S("Nn") * lpeg.S("Aa") * lpeg.S("Nn") 26 | local inf = lpeg.S("Ii") * lpeg.P("nfinity") 27 | local ninf = lpeg.P('-') * lpeg.S("Ii") * lpeg.P("nfinity") 28 | local hex = (lpeg.P("0x") + lpeg.P("0X")) * lpeg.R("09","AF","af")^1 29 | 30 | local defaultOptions = { 31 | nan = true, 32 | inf = true, 33 | frac = true, 34 | exp = true, 35 | hex = false 36 | } 37 | 38 | local modeOptions = {} 39 | 40 | modeOptions.strict = { 41 | nan = false, 42 | inf = false 43 | } 44 | 45 | local nan_value = 0/0 46 | local inf_value = 1/0 47 | local ninf_value = -1/0 48 | 49 | --[[ 50 | Options: configuration options for number rules 51 | nan: match NaN 52 | inf: match Infinity 53 | frac: match fraction portion (.0) 54 | exp: match exponent portion (e1) 55 | DEFAULT: nan, inf, frac, exp 56 | ]] 57 | local function mergeOptions(options, mode) 58 | jsonutil.doOptionMerge(options, false, 'number', defaultOptions, mode and modeOptions[mode]) 59 | end 60 | 61 | local function generateLexer(options) 62 | options = options.number 63 | local ret = int 64 | if options.frac then 65 | ret = ret * (frac + 0) 66 | else 67 | ret = ret * (#frac * util.denied("Fractions", "number.frac") + 0) 68 | end 69 | if options.exp then 70 | ret = ret * (exp + 0) 71 | else 72 | ret = ret * (#exp * util.denied("Exponents", "number.exp") + 0) 73 | end 74 | if options.hex then 75 | ret = hex + ret 76 | else 77 | ret = #hex * util.denied("Hexadecimal", "number.hex") + ret 78 | end 79 | -- Capture number now 80 | ret = ret / tonumber 81 | if options.nan then 82 | ret = ret + nan / function() return nan_value end 83 | else 84 | ret = ret + #nan * util.denied("NaN", "number.nan") 85 | end 86 | if options.inf then 87 | ret = ret + ninf / function() return ninf_value end + inf / function() return inf_value end 88 | else 89 | ret = ret + (#ninf + #inf) * util.denied("+/-Inf", "number.inf") 90 | end 91 | return ret 92 | end 93 | 94 | local number = { 95 | int = int, 96 | mergeOptions = mergeOptions, 97 | generateLexer = generateLexer 98 | } 99 | 100 | return number 101 | -------------------------------------------------------------------------------- /lua/json/decode/others.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local lpeg = require("lpeg") 6 | local jsonutil = require("json.util") 7 | local merge = jsonutil.merge 8 | local util = require("json.decode.util") 9 | 10 | -- Container module for other JavaScript types (bool, null, undefined) 11 | 12 | local _ENV = nil 13 | 14 | -- For null and undefined, use the util.null value to preserve null-ness 15 | local booleanCapture = 16 | lpeg.P("true") * lpeg.Cc(true) 17 | + lpeg.P("false") * lpeg.Cc(false) 18 | 19 | local nullCapture = lpeg.P("null") 20 | local undefinedCapture = lpeg.P("undefined") 21 | 22 | local defaultOptions = { 23 | allowUndefined = true, 24 | null = jsonutil.null, 25 | undefined = jsonutil.undefined 26 | } 27 | 28 | local modeOptions = {} 29 | 30 | modeOptions.simple = { 31 | null = false, -- Mapped to nil 32 | undefined = false -- Mapped to nil 33 | } 34 | modeOptions.strict = { 35 | allowUndefined = false 36 | } 37 | 38 | local function mergeOptions(options, mode) 39 | jsonutil.doOptionMerge(options, false, 'others', defaultOptions, mode and modeOptions[mode]) 40 | end 41 | 42 | local function generateLexer(options) 43 | -- The 'or nil' clause allows false to map to a nil value since 'nil' cannot be merged 44 | options = options.others 45 | local valueCapture = ( 46 | booleanCapture 47 | + nullCapture * lpeg.Cc(options.null or nil) 48 | ) 49 | if options.allowUndefined then 50 | valueCapture = valueCapture + undefinedCapture * lpeg.Cc(options.undefined or nil) 51 | else 52 | valueCapture = valueCapture + #undefinedCapture * util.denied("undefined", "others.allowUndefined") 53 | end 54 | return valueCapture 55 | end 56 | 57 | local others = { 58 | mergeOptions = mergeOptions, 59 | generateLexer = generateLexer 60 | } 61 | 62 | return others 63 | -------------------------------------------------------------------------------- /lua/json/decode/state.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | 6 | local setmetatable = setmetatable 7 | local jsonutil = require("json.util") 8 | local assert = assert 9 | local type = type 10 | local next = next 11 | local unpack = require("table").unpack or unpack 12 | 13 | local _ENV = nil 14 | 15 | local state_ops = {} 16 | local state_mt = { 17 | __index = state_ops 18 | } 19 | 20 | function state_ops.pop(self) 21 | self.previous_set = true 22 | self.previous = self.active 23 | local i = self.i 24 | -- Load in this array into the active item 25 | self.active = self.stack[i] 26 | self.active_state = self.state_stack[i] 27 | self.active_key = self.key_stack[i] 28 | self.stack[i] = nil 29 | self.state_stack[i] = nil 30 | self.key_stack[i] = nil 31 | 32 | self.i = i - 1 33 | end 34 | 35 | function state_ops.push(self) 36 | local i = self.i + 1 37 | self.i = i 38 | 39 | self.stack[i] = self.active 40 | self.state_stack[i] = self.active_state 41 | self.key_stack[i] = self.active_key 42 | end 43 | 44 | function state_ops.put_object_value(self, trailing) 45 | local object_options = self.options.object 46 | if trailing and object_options.trailingComma then 47 | if not self.active_key then 48 | return 49 | end 50 | end 51 | assert(self.active_key, "Missing key value") 52 | object_options.setObjectKey(self.active, self.active_key, self:grab_value(object_options.allowEmptyElement)) 53 | self.active_key = nil 54 | end 55 | 56 | function state_ops.put_array_value(self, trailing) 57 | local array_options = self.options.array 58 | -- Safety check 59 | if trailing and not self.previous_set and array_options.trailingComma then 60 | return 61 | end 62 | local new_index = self.active_state + 1 63 | self.active_state = new_index 64 | self.active[new_index] = self:grab_value(array_options.allowEmptyElement) 65 | end 66 | 67 | function state_ops.put_call_value(self, trailing) 68 | local call_options = self.options.calls 69 | -- Safety check 70 | if trailing and not self.previous_set and call_options.trailingComma then 71 | return 72 | end 73 | local new_index = self.active_state + 1 74 | self.active_state = new_index 75 | self.active[new_index] = self:grab_value(call_options.allowEmptyElement) 76 | end 77 | 78 | function state_ops.put_value(self, trailing) 79 | if self.active_state == 'object' then 80 | self:put_object_value(trailing) 81 | elseif self.active.func then 82 | self:put_call_value(trailing) 83 | else 84 | self:put_array_value(trailing) 85 | end 86 | end 87 | 88 | function state_ops.new_array(self) 89 | local new_array = {} 90 | if jsonutil.InitArray then 91 | new_array = jsonutil.InitArray(new_array) or new_array 92 | end 93 | self.active = new_array 94 | self.active_state = 0 95 | self.active_key = nil 96 | self:unset_value() 97 | end 98 | 99 | function state_ops.end_array(self) 100 | if self.previous_set or self.active_state ~= 0 then 101 | -- Not an empty array 102 | self:put_value(true) 103 | end 104 | if self.active_state ~= #self.active and not self.options.array.ignoreLength then 105 | -- Store the length in 106 | self.active.n = self.active_state 107 | end 108 | end 109 | 110 | function state_ops.new_object(self) 111 | local new_object = {} 112 | self.active = new_object 113 | self.active_state = 'object' 114 | self.active_key = nil 115 | self:unset_value() 116 | end 117 | 118 | function state_ops.end_object(self) 119 | if self.active_key or self.previous_set or next(self.active) then 120 | -- Not an empty object 121 | self:put_value(true) 122 | end 123 | end 124 | 125 | function state_ops.new_call(self, name, func) 126 | -- TODO setup properly 127 | local new_call = {} 128 | new_call.name = name 129 | new_call.func = func 130 | self.active = new_call 131 | self.active_state = 0 132 | self.active_key = nil 133 | self:unset_value() 134 | end 135 | 136 | function state_ops.end_call(self) 137 | if self.previous_set or self.active_state ~= 0 then 138 | -- Not an empty array 139 | self:put_value(true) 140 | end 141 | if self.active_state ~= #self.active then 142 | -- Store the length in 143 | self.active.n = self.active_state 144 | end 145 | local func = self.active.func 146 | if func == true then 147 | func = jsonutil.buildCall 148 | end 149 | self.active = func(self.active.name, unpack(self.active, 1, self.active.n or #self.active)) 150 | end 151 | 152 | 153 | function state_ops.unset_value(self) 154 | self.previous_set = false 155 | self.previous = nil 156 | end 157 | 158 | function state_ops.grab_value(self, allowEmptyValue) 159 | if not self.previous_set and allowEmptyValue then 160 | -- Calculate an appropriate empty-value 161 | return self.emptyValue 162 | end 163 | assert(self.previous_set, "Previous value not set") 164 | self.previous_set = false 165 | return self.previous 166 | end 167 | 168 | function state_ops.set_value(self, value) 169 | assert(not self.previous_set, "Value set when one already in slot") 170 | self.previous_set = true 171 | self.previous = value 172 | end 173 | 174 | function state_ops.set_key(self) 175 | assert(self.active_state == 'object', "Cannot set key on array") 176 | local value = self:grab_value() 177 | local value_type = type(value) 178 | if self.options.object.number then 179 | assert(value_type == 'string' or value_type == 'number', "As configured, a key must be a number or string") 180 | else 181 | assert(value_type == 'string', "As configured, a key must be a string") 182 | end 183 | self.active_key = value 184 | end 185 | 186 | 187 | local function create(options) 188 | local emptyValue 189 | -- Calculate an empty value up front 190 | if options.others.allowUndefined then 191 | emptyValue = options.others.undefined or nil 192 | else 193 | emptyValue = options.others.null or nil 194 | end 195 | local ret = { 196 | options = options, 197 | stack = {}, 198 | state_stack = {}, 199 | key_stack = {}, 200 | i = 0, 201 | active = nil, 202 | active_key = nil, 203 | previous = nil, 204 | active_state = nil, 205 | emptyValue = emptyValue 206 | } 207 | return setmetatable(ret, state_mt) 208 | end 209 | 210 | local state = { 211 | create = create 212 | } 213 | 214 | return state 215 | -------------------------------------------------------------------------------- /lua/json/decode/strings.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local lpeg = require("lpeg") 6 | local jsonutil = require("json.util") 7 | local util = require("json.decode.util") 8 | local merge = jsonutil.merge 9 | 10 | local tonumber = tonumber 11 | local string_char = require("string").char 12 | local floor = require("math").floor 13 | local table_concat = require("table").concat 14 | 15 | local error = error 16 | 17 | local _ENV = nil 18 | 19 | local function get_error(item) 20 | local fmt_string = item .. " in string [%q] @ %i:%i" 21 | return lpeg.P(function(data, index) 22 | local line, line_index, bad_char, last_line = util.get_invalid_character_info(data, index) 23 | local err = fmt_string:format(bad_char, line, line_index) 24 | error(err) 25 | end) * 1 26 | end 27 | 28 | local bad_unicode = get_error("Illegal unicode escape") 29 | local bad_hex = get_error("Illegal hex escape") 30 | local bad_character = get_error("Illegal character") 31 | local bad_escape = get_error("Illegal escape") 32 | 33 | local knownReplacements = { 34 | ["'"] = "'", 35 | ['"'] = '"', 36 | ['\\'] = '\\', 37 | ['/'] = '/', 38 | b = '\b', 39 | f = '\f', 40 | n = '\n', 41 | r = '\r', 42 | t = '\t', 43 | v = '\v', 44 | z = '\z' 45 | } 46 | 47 | -- according to the table at http://da.wikipedia.org/wiki/UTF-8 48 | local function utf8DecodeUnicode(code1, code2) 49 | code1, code2 = tonumber(code1, 16), tonumber(code2, 16) 50 | if code1 == 0 and code2 < 0x80 then 51 | return string_char(code2) 52 | end 53 | if code1 < 0x08 then 54 | return string_char( 55 | 0xC0 + code1 * 4 + floor(code2 / 64), 56 | 0x80 + code2 % 64) 57 | end 58 | return string_char( 59 | 0xE0 + floor(code1 / 16), 60 | 0x80 + (code1 % 16) * 4 + floor(code2 / 64), 61 | 0x80 + code2 % 64) 62 | end 63 | 64 | local function decodeX(code) 65 | code = tonumber(code, 16) 66 | return string_char(code) 67 | end 68 | 69 | local doSimpleSub = lpeg.C(lpeg.S("'\"\\/bfnrtvz")) / knownReplacements 70 | local doUniSub = lpeg.P('u') * (lpeg.C(util.hexpair) * lpeg.C(util.hexpair) + bad_unicode) 71 | local doXSub = lpeg.P('x') * (lpeg.C(util.hexpair) + bad_hex) 72 | 73 | local defaultOptions = { 74 | badChars = '', 75 | additionalEscapes = false, -- disallow untranslated escapes 76 | escapeCheck = #lpeg.S('bfnrtv/\\"xu\'z'), -- no check on valid characters 77 | decodeUnicode = utf8DecodeUnicode, 78 | strict_quotes = false 79 | } 80 | 81 | local modeOptions = {} 82 | 83 | modeOptions.strict = { 84 | badChars = '\b\f\n\r\t\v', 85 | additionalEscapes = false, -- no additional escapes 86 | escapeCheck = #lpeg.S('bfnrtv/\\"u'), --only these chars are allowed to be escaped 87 | strict_quotes = true 88 | } 89 | 90 | local function mergeOptions(options, mode) 91 | jsonutil.doOptionMerge(options, false, 'strings', defaultOptions, mode and modeOptions[mode]) 92 | end 93 | 94 | local function buildCaptureString(quote, badChars, escapeMatch) 95 | local captureChar = (1 - lpeg.S("\\" .. badChars .. quote)) + (lpeg.P("\\") / "" * escapeMatch) 96 | -- During error, force end 97 | local captureString = captureChar^0 + (-#lpeg.P(quote) * bad_character + -1) 98 | return lpeg.P(quote) * lpeg.Cs(captureString) * lpeg.P(quote) 99 | end 100 | 101 | local function generateLexer(options) 102 | options = options.strings 103 | local quotes = { '"' } 104 | if not options.strict_quotes then 105 | quotes[#quotes + 1] = "'" 106 | end 107 | local escapeMatch = doSimpleSub 108 | escapeMatch = escapeMatch + doXSub / decodeX 109 | escapeMatch = escapeMatch + doUniSub / options.decodeUnicode 110 | if options.escapeCheck then 111 | escapeMatch = options.escapeCheck * escapeMatch + bad_escape 112 | end 113 | if options.additionalEscapes then 114 | escapeMatch = options.additionalEscapes + escapeMatch 115 | end 116 | local captureString 117 | for i = 1, #quotes do 118 | local cap = buildCaptureString(quotes[i], options.badChars, escapeMatch) 119 | if captureString == nil then 120 | captureString = cap 121 | else 122 | captureString = captureString + cap 123 | end 124 | end 125 | return captureString 126 | end 127 | 128 | local strings = { 129 | mergeOptions = mergeOptions, 130 | generateLexer = generateLexer 131 | } 132 | 133 | return strings 134 | -------------------------------------------------------------------------------- /lua/json/decode/util.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local lpeg = require("lpeg") 6 | local select = select 7 | local pairs, ipairs = pairs, ipairs 8 | local tonumber = tonumber 9 | local string_char = require("string").char 10 | local rawset = rawset 11 | local jsonutil = require("json.util") 12 | 13 | local error = error 14 | local setmetatable = setmetatable 15 | 16 | local table_concat = require("table").concat 17 | 18 | local merge = require("json.util").merge 19 | 20 | local type = type 21 | 22 | local _ENV = nil 23 | 24 | local function get_invalid_character_info(input, index) 25 | local parsed = input:sub(1, index) 26 | local bad_character = input:sub(index, index) 27 | local _, line_number = parsed:gsub('\n',{}) 28 | local last_line = parsed:match("\n([^\n]+.)$") or parsed 29 | return line_number, #last_line, bad_character, last_line 30 | end 31 | 32 | local function build_report(msg) 33 | local fmt = msg:gsub("%%", "%%%%") .. " @ character: %i %i:%i [%s] line:\n%s" 34 | return lpeg.P(function(data, pos) 35 | local line, line_index, bad_char, last_line = get_invalid_character_info(data, pos) 36 | local text = fmt:format(pos, line, line_index, bad_char, last_line) 37 | error(text) 38 | end) * 1 39 | end 40 | local function unexpected() 41 | local msg = "unexpected character" 42 | return build_report(msg) 43 | end 44 | local function denied(item, option) 45 | local msg 46 | if option then 47 | msg = ("'%s' denied by option set '%s'"):format(item, option) 48 | else 49 | msg = ("'%s' denied"):format(item) 50 | end 51 | return build_report(msg) 52 | end 53 | 54 | -- 09, 0A, 0B, 0C, 0D, 20 55 | local ascii_space = lpeg.S("\t\n\v\f\r ") 56 | local unicode_space 57 | do 58 | local chr = string_char 59 | local u_space = ascii_space 60 | -- \u0085 \u00A0 61 | u_space = u_space + lpeg.P(chr(0xC2)) * lpeg.S(chr(0x85) .. chr(0xA0)) 62 | -- \u1680 \u180E 63 | u_space = u_space + lpeg.P(chr(0xE1)) * (lpeg.P(chr(0x9A, 0x80)) + chr(0xA0, 0x8E)) 64 | -- \u2000 - \u200A, also 200B 65 | local spacing_end = "" 66 | for i = 0x80,0x8b do 67 | spacing_end = spacing_end .. chr(i) 68 | end 69 | -- \u2028 \u2029 \u202F 70 | spacing_end = spacing_end .. chr(0xA8) .. chr(0xA9) .. chr(0xAF) 71 | u_space = u_space + lpeg.P(chr(0xE2, 0x80)) * lpeg.S(spacing_end) 72 | -- \u205F 73 | u_space = u_space + lpeg.P(chr(0xE2, 0x81, 0x9F)) 74 | -- \u3000 75 | u_space = u_space + lpeg.P(chr(0xE3, 0x80, 0x80)) 76 | -- BOM \uFEFF 77 | u_space = u_space + lpeg.P(chr(0xEF, 0xBB, 0xBF)) 78 | unicode_space = u_space 79 | end 80 | 81 | local identifier = lpeg.R("AZ","az","__") * lpeg.R("AZ","az", "__", "09") ^0 82 | 83 | local hex = lpeg.R("09","AF","af") 84 | local hexpair = hex * hex 85 | 86 | local comments = { 87 | cpp = lpeg.P("//") * (1 - lpeg.P("\n"))^0 * lpeg.P("\n"), 88 | c = lpeg.P("/*") * (1 - lpeg.P("*/"))^0 * lpeg.P("*/") 89 | } 90 | 91 | local comment = comments.cpp + comments.c 92 | 93 | local ascii_ignored = (ascii_space + comment)^0 94 | 95 | local unicode_ignored = (unicode_space + comment)^0 96 | 97 | -- Parse the lpeg version skipping patch-values 98 | -- LPEG <= 0.7 have no version value... so 0.7 is value 99 | -- LPEG >= 1.1 uses a string for the version instead of function 100 | local DecimalLpegVersion = lpeg.version 101 | and tonumber( 102 | (type(lpeg.version) == "string" and lpeg.version or lpeg.version()):match("(%d+%.%d+)") 103 | ) 104 | or 0.7 105 | 106 | local function setObjectKeyForceNumber(t, key, value) 107 | key = tonumber(key) or key 108 | return rawset(t, key, value) 109 | end 110 | 111 | local util = { 112 | unexpected = unexpected, 113 | denied = denied, 114 | ascii_space = ascii_space, 115 | unicode_space = unicode_space, 116 | identifier = identifier, 117 | hex = hex, 118 | hexpair = hexpair, 119 | comments = comments, 120 | comment = comment, 121 | ascii_ignored = ascii_ignored, 122 | unicode_ignored = unicode_ignored, 123 | DecimalLpegVersion = DecimalLpegVersion, 124 | get_invalid_character_info = get_invalid_character_info, 125 | setObjectKeyForceNumber = setObjectKeyForceNumber 126 | } 127 | 128 | return util 129 | -------------------------------------------------------------------------------- /lua/json/encode.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local type = type 6 | local assert, error = assert, error 7 | local getmetatable, setmetatable = getmetatable, setmetatable 8 | 9 | local ipairs, pairs = ipairs, pairs 10 | local require = require 11 | 12 | local output = require("json.encode.output") 13 | 14 | local util = require("json.util") 15 | local util_merge, isCall = util.merge, util.isCall 16 | 17 | local _ENV = nil 18 | 19 | --[[ 20 | List of encoding modules to load. 21 | Loaded in sequence such that earlier encoders get priority when 22 | duplicate type-handlers exist. 23 | ]] 24 | local modulesToLoad = { 25 | "strings", 26 | "number", 27 | "calls", 28 | "others", 29 | "array", 30 | "object" 31 | } 32 | -- Modules that have been loaded 33 | local loadedModules = {} 34 | 35 | local json_encode = {} 36 | 37 | -- Configuration bases for client apps 38 | local modes_defined = { "default", "strict" } 39 | 40 | json_encode.default = {} 41 | json_encode.strict = { 42 | initialObject = true -- Require an object at the root 43 | } 44 | 45 | -- For each module, load it and its defaults 46 | for _,name in ipairs(modulesToLoad) do 47 | local mod = require("json.encode." .. name) 48 | if mod.mergeOptions then 49 | for _, mode in pairs(modes_defined) do 50 | mod.mergeOptions(json_encode[mode], mode) 51 | end 52 | end 53 | loadedModules[name] = mod 54 | end 55 | 56 | -- NOTE: Nested not found, so assume unsupported until use case arises 57 | local function flattenOutput(out, value) 58 | assert(type(value) ~= 'table') 59 | out = out or {} 60 | out[#out + 1] = value 61 | return out 62 | end 63 | 64 | -- Prepares the encoding map from the already provided modules and new config 65 | local function prepareEncodeMap(options) 66 | local map = {} 67 | for _, name in ipairs(modulesToLoad) do 68 | local encodermap = loadedModules[name].getEncoder(options[name]) 69 | for valueType, encoderSet in pairs(encodermap) do 70 | map[valueType] = flattenOutput(map[valueType], encoderSet) 71 | end 72 | end 73 | return map 74 | end 75 | 76 | --[[ 77 | Encode a value with a given encoding map and state 78 | ]] 79 | local function encodeWithMap(value, map, state, isObjectKey) 80 | local t = type(value) 81 | local encoderList = assert(map[t], "Failed to encode value, unhandled type: " .. t) 82 | for _, encoder in ipairs(encoderList) do 83 | local ret = encoder(value, state, isObjectKey) 84 | if false ~= ret then 85 | return ret 86 | end 87 | end 88 | error("Failed to encode value, encoders for " .. t .. " deny encoding") 89 | end 90 | 91 | 92 | local function getBaseEncoder(options) 93 | local encoderMap = prepareEncodeMap(options) 94 | if options.preProcess then 95 | local preProcess = options.preProcess 96 | return function(value, state, isObjectKey) 97 | local ret = preProcess(value, isObjectKey or false) 98 | if nil ~= ret then 99 | value = ret 100 | end 101 | return encodeWithMap(value, encoderMap, state) 102 | end 103 | end 104 | return function(value, state, isObjectKey) 105 | return encodeWithMap(value, encoderMap, state) 106 | end 107 | end 108 | --[[ 109 | Retreive an initial encoder instance based on provided options 110 | the initial encoder is responsible for initializing state 111 | State has at least these values configured: encode, check_unique, already_encoded 112 | ]] 113 | function json_encode.getEncoder(options) 114 | options = options and util_merge({}, json_encode.default, options) or json_encode.default 115 | local encode = getBaseEncoder(options) 116 | 117 | local function initialEncode(value) 118 | if options.initialObject then 119 | local errorMessage = "Invalid arguments: expects a JSON Object or Array at the root" 120 | assert(type(value) == 'table' and not isCall(value, options), errorMessage) 121 | end 122 | 123 | local alreadyEncoded = {} 124 | local function check_unique(value) 125 | assert(not alreadyEncoded[value], "Recursive encoding of value") 126 | alreadyEncoded[value] = true 127 | end 128 | 129 | local outputEncoder = options.output and options.output() or output.getDefault() 130 | local state = { 131 | encode = encode, 132 | check_unique = check_unique, 133 | already_encoded = alreadyEncoded, -- To unmark encoding when moving up stack 134 | outputEncoder = outputEncoder 135 | } 136 | local ret = encode(value, state) 137 | if nil ~= ret then 138 | return outputEncoder.simple and outputEncoder.simple(ret) or ret 139 | end 140 | end 141 | return initialEncode 142 | end 143 | 144 | -- CONSTRUCT STATE WITH FOLLOWING (at least) 145 | --[[ 146 | encoder 147 | check_unique -- used by inner encoders to make sure value is unique 148 | already_encoded -- used to unmark a value as unique 149 | ]] 150 | function json_encode.encode(data, options) 151 | return json_encode.getEncoder(options)(data) 152 | end 153 | 154 | local mt = {} 155 | mt.__call = function(self, ...) 156 | return json_encode.encode(...) 157 | end 158 | 159 | setmetatable(json_encode, mt) 160 | 161 | return json_encode 162 | -------------------------------------------------------------------------------- /lua/json/encode/array.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local jsonutil = require("json.util") 6 | 7 | local type = type 8 | local pairs = pairs 9 | local assert = assert 10 | 11 | local table = require("table") 12 | local math = require("math") 13 | local table_concat = table.concat 14 | local math_floor, math_modf = math.floor, math.modf 15 | 16 | local jsonutil = require("json.util") 17 | local util_IsArray = jsonutil.IsArray 18 | 19 | local _ENV = nil 20 | 21 | local defaultOptions = { 22 | isArray = util_IsArray 23 | } 24 | 25 | local modeOptions = {} 26 | 27 | local function mergeOptions(options, mode) 28 | jsonutil.doOptionMerge(options, false, 'array', defaultOptions, mode and modeOptions[mode]) 29 | end 30 | 31 | --[[ 32 | Utility function to determine whether a table is an array or not. 33 | Criteria for it being an array: 34 | * ExternalIsArray returns true (or false directly reports not-array) 35 | * If the table has an 'n' value that is an integer >= 1 then it 36 | is an array... may result in false positives (should check some values 37 | before it) 38 | * It is a contiguous list of values with zero string-based keys 39 | ]] 40 | local function isArray(val, options) 41 | local externalIsArray = options and options.isArray 42 | 43 | if externalIsArray then 44 | local ret = externalIsArray(val) 45 | if ret == true or ret == false then 46 | return ret 47 | end 48 | end 49 | -- Use the 'n' element if it's a number 50 | if type(val.n) == 'number' and math_floor(val.n) == val.n and val.n >= 1 then 51 | return true 52 | end 53 | local len = #val 54 | for k,v in pairs(val) do 55 | if type(k) ~= 'number' then 56 | return false 57 | end 58 | local _, decim = math_modf(k) 59 | if not (decim == 0 and 1<=k) then 60 | return false 61 | end 62 | if k > len then -- Use Lua's length as absolute determiner 63 | return false 64 | end 65 | end 66 | 67 | return true 68 | end 69 | 70 | --[[ 71 | Cleanup function to unmark a value as in the encoding process and return 72 | trailing results 73 | ]] 74 | local function unmarkAfterEncode(tab, state, ...) 75 | state.already_encoded[tab] = nil 76 | return ... 77 | end 78 | local function getEncoder(options) 79 | options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions 80 | local function encodeArray(tab, state) 81 | if not isArray(tab, options) then 82 | return false 83 | end 84 | -- Make sure this value hasn't been encoded yet 85 | state.check_unique(tab) 86 | local encode = state.encode 87 | local compositeEncoder = state.outputEncoder.composite 88 | local valueEncoder = [[ 89 | for i = 1, (composite.n or #composite) do 90 | local val = composite[i] 91 | PUTINNER(i ~= 1) 92 | val = encode(val, state) 93 | val = val or '' 94 | if val then 95 | PUTVALUE(val) 96 | end 97 | end 98 | ]] 99 | return unmarkAfterEncode(tab, state, compositeEncoder(valueEncoder, '[', ']', ',', tab, encode, state)) 100 | end 101 | return { table = encodeArray } 102 | end 103 | 104 | local array = { 105 | mergeOptions = mergeOptions, 106 | isArray = isArray, 107 | getEncoder = getEncoder 108 | } 109 | 110 | return array 111 | -------------------------------------------------------------------------------- /lua/json/encode/calls.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local table = require("table") 6 | local table_concat = table.concat 7 | 8 | local select = select 9 | local getmetatable, setmetatable = getmetatable, setmetatable 10 | local assert = assert 11 | 12 | local jsonutil = require("json.util") 13 | 14 | local isCall, decodeCall = jsonutil.isCall, jsonutil.decodeCall 15 | 16 | local _ENV = nil 17 | 18 | local defaultOptions = { 19 | } 20 | 21 | -- No real default-option handling needed... 22 | local modeOptions = {} 23 | 24 | local function mergeOptions(options, mode) 25 | jsonutil.doOptionMerge(options, false, 'calls', defaultOptions, mode and modeOptions[mode]) 26 | end 27 | 28 | 29 | --[[ 30 | Encodes 'value' as a function call 31 | Must have parameters in the 'callData' field of the metatable 32 | name == name of the function call 33 | parameters == array of parameters to encode 34 | ]] 35 | local function getEncoder(options) 36 | options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions 37 | local function encodeCall(value, state) 38 | if not isCall(value) then 39 | return false 40 | end 41 | local encode = state.encode 42 | local name, params = decodeCall(value) 43 | local compositeEncoder = state.outputEncoder.composite 44 | local valueEncoder = [[ 45 | for i = 1, (composite.n or #composite) do 46 | local val = composite[i] 47 | PUTINNER(i ~= 1) 48 | val = encode(val, state) 49 | val = val or '' 50 | if val then 51 | PUTVALUE(val) 52 | end 53 | end 54 | ]] 55 | return compositeEncoder(valueEncoder, name .. '(', ')', ',', params, encode, state) 56 | end 57 | return { 58 | table = encodeCall, 59 | ['function'] = encodeCall 60 | } 61 | end 62 | 63 | local calls = { 64 | mergeOptions = mergeOptions, 65 | getEncoder = getEncoder 66 | } 67 | 68 | return calls 69 | -------------------------------------------------------------------------------- /lua/json/encode/number.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local tostring = tostring 6 | local assert = assert 7 | local jsonutil = require("json.util") 8 | local huge = require("math").huge 9 | 10 | local _ENV = nil 11 | 12 | local defaultOptions = { 13 | nan = true, 14 | inf = true 15 | } 16 | 17 | local modeOptions = {} 18 | modeOptions.strict = { 19 | nan = false, 20 | inf = false 21 | } 22 | 23 | local function mergeOptions(options, mode) 24 | jsonutil.doOptionMerge(options, false, 'number', defaultOptions, mode and modeOptions[mode]) 25 | end 26 | 27 | 28 | local function encodeNumber(number, options) 29 | if number ~= number then 30 | assert(options.nan, "Invalid number: NaN not enabled") 31 | return "NaN" 32 | end 33 | if number == huge then 34 | assert(options.inf, "Invalid number: Infinity not enabled") 35 | return "Infinity" 36 | end 37 | if number == -huge then 38 | assert(options.inf, "Invalid number: Infinity not enabled") 39 | return "-Infinity" 40 | end 41 | return tostring(number) 42 | end 43 | 44 | local function getEncoder(options) 45 | options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions 46 | return { 47 | number = function(number, state) 48 | return encodeNumber(number, options) 49 | end 50 | } 51 | end 52 | 53 | local number = { 54 | mergeOptions = mergeOptions, 55 | getEncoder = getEncoder 56 | } 57 | 58 | return number 59 | -------------------------------------------------------------------------------- /lua/json/encode/object.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local pairs = pairs 6 | local assert = assert 7 | 8 | local type = type 9 | local tostring = tostring 10 | 11 | local table_concat = require("table").concat 12 | local jsonutil = require("json.util") 13 | 14 | local _ENV = nil 15 | 16 | local defaultOptions = { 17 | } 18 | 19 | local modeOptions = {} 20 | 21 | local function mergeOptions(options, mode) 22 | jsonutil.doOptionMerge(options, false, 'object', defaultOptions, mode and modeOptions[mode]) 23 | end 24 | 25 | --[[ 26 | Cleanup function to unmark a value as in the encoding process and return 27 | trailing results 28 | ]] 29 | local function unmarkAfterEncode(tab, state, ...) 30 | state.already_encoded[tab] = nil 31 | return ... 32 | end 33 | --[[ 34 | Encode a table as a JSON Object ( keys = strings, values = anything else ) 35 | ]] 36 | local function encodeTable(tab, options, state) 37 | -- Make sure this value hasn't been encoded yet 38 | state.check_unique(tab) 39 | local encode = state.encode 40 | local compositeEncoder = state.outputEncoder.composite 41 | local valueEncoder = [[ 42 | local first = true 43 | for k, v in pairs(composite) do 44 | local ti = type(k) 45 | assert(ti == 'string' or ti == 'number' or ti == 'boolean', "Invalid object index type: " .. ti) 46 | local name = encode(tostring(k), state, true) 47 | if first then 48 | first = false 49 | else 50 | name = ',' .. name 51 | end 52 | PUTVALUE(name .. ':') 53 | local val = encode(v, state) 54 | val = val or '' 55 | if val then 56 | PUTVALUE(val) 57 | end 58 | end 59 | ]] 60 | return unmarkAfterEncode(tab, state, compositeEncoder(valueEncoder, '{', '}', nil, tab, encode, state)) 61 | end 62 | 63 | local function getEncoder(options) 64 | options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions 65 | return { 66 | table = function(tab, state) 67 | return encodeTable(tab, options, state) 68 | end 69 | } 70 | end 71 | 72 | local object = { 73 | mergeOptions = mergeOptions, 74 | getEncoder = getEncoder 75 | } 76 | 77 | return object 78 | -------------------------------------------------------------------------------- /lua/json/encode/others.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local tostring = tostring 6 | 7 | local assert = assert 8 | local jsonutil = require("json.util") 9 | local type = type 10 | 11 | local _ENV = nil 12 | 13 | -- Shortcut that works 14 | local encodeBoolean = tostring 15 | 16 | local defaultOptions = { 17 | allowUndefined = true, 18 | null = jsonutil.null, 19 | undefined = jsonutil.undefined 20 | } 21 | 22 | local modeOptions = {} 23 | 24 | modeOptions.strict = { 25 | allowUndefined = false 26 | } 27 | 28 | local function mergeOptions(options, mode) 29 | jsonutil.doOptionMerge(options, false, 'others', defaultOptions, mode and modeOptions[mode]) 30 | end 31 | local function getEncoder(options) 32 | options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions 33 | local function encodeOthers(value, state) 34 | if value == options.null then 35 | return 'null' 36 | elseif value == options.undefined then 37 | assert(options.allowUndefined, "Invalid value: Unsupported 'Undefined' parameter") 38 | return 'undefined' 39 | else 40 | return false 41 | end 42 | end 43 | local function encodeBoolean(value, state) 44 | return value and 'true' or 'false' 45 | end 46 | local nullType = type(options.null) 47 | local undefinedType = options.undefined and type(options.undefined) 48 | -- Make sure that all of the types handled here are handled 49 | local ret = { 50 | boolean = encodeBoolean, 51 | ['nil'] = function() return 'null' end, 52 | [nullType] = encodeOthers 53 | } 54 | if undefinedType then 55 | ret[undefinedType] = encodeOthers 56 | end 57 | return ret 58 | end 59 | 60 | local others = { 61 | encodeBoolean = encodeBoolean, 62 | mergeOptions = mergeOptions, 63 | getEncoder = getEncoder 64 | } 65 | 66 | return others 67 | -------------------------------------------------------------------------------- /lua/json/encode/output.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local type = type 6 | local assert, error = assert, error 7 | local table_concat = require("table").concat 8 | local loadstring = loadstring or load 9 | 10 | local io = require("io") 11 | 12 | local setmetatable = setmetatable 13 | 14 | local output_utility = require("json.encode.output_utility") 15 | 16 | local _ENV = nil 17 | 18 | local tableCompositeCache = setmetatable({}, {__mode = 'v'}) 19 | 20 | local TABLE_VALUE_WRITER = [[ 21 | ret[#ret + 1] = %VALUE% 22 | ]] 23 | 24 | local TABLE_INNER_WRITER = "" 25 | 26 | --[[ 27 | nextValues can output a max of two values to throw into the data stream 28 | expected to be called until nil is first return value 29 | value separator should either be attached to v1 or in innerValue 30 | ]] 31 | local function defaultTableCompositeWriter(nextValues, beginValue, closeValue, innerValue, composite, encode, state) 32 | if type(nextValues) == 'string' then 33 | local fun = output_utility.prepareEncoder(defaultTableCompositeWriter, nextValues, innerValue, TABLE_VALUE_WRITER, TABLE_INNER_WRITER) 34 | local ret = {} 35 | fun(composite, ret, encode, state) 36 | return beginValue .. table_concat(ret, innerValue) .. closeValue 37 | end 38 | end 39 | 40 | -- no 'simple' as default action is just to return the value 41 | local function getDefault() 42 | return { composite = defaultTableCompositeWriter } 43 | end 44 | 45 | -- BEGIN IO-WRITER OUTPUT 46 | local IO_INNER_WRITER = [[ 47 | if %WRITE_INNER% then 48 | state.__outputFile:write(%INNER_VALUE%) 49 | end 50 | ]] 51 | local IO_VALUE_WRITER = [[ 52 | state.__outputFile:write(%VALUE%) 53 | ]] 54 | 55 | local function buildIoWriter(output) 56 | if not output then -- Default to stdout 57 | output = io.output() 58 | end 59 | local function ioWriter(nextValues, beginValue, closeValue, innerValue, composite, encode, state) 60 | -- HOOK OUTPUT STATE 61 | state.__outputFile = output 62 | if type(nextValues) == 'string' then 63 | local fun = output_utility.prepareEncoder(ioWriter, nextValues, innerValue, IO_VALUE_WRITER, IO_INNER_WRITER) 64 | local ret = {} 65 | output:write(beginValue) 66 | fun(composite, ret, encode, state) 67 | output:write(closeValue) 68 | return nil 69 | end 70 | end 71 | 72 | local function ioSimpleWriter(encoded) 73 | if encoded then 74 | output:write(encoded) 75 | end 76 | return nil 77 | end 78 | return { composite = ioWriter, simple = ioSimpleWriter } 79 | end 80 | local function getIoWriter(output) 81 | return function() 82 | return buildIoWriter(output) 83 | end 84 | end 85 | 86 | local output = { 87 | getDefault = getDefault, 88 | getIoWriter = getIoWriter 89 | } 90 | 91 | return output 92 | -------------------------------------------------------------------------------- /lua/json/encode/output_utility.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local setmetatable = setmetatable 6 | local assert, loadstring = assert, loadstring or load 7 | 8 | local _ENV = nil 9 | 10 | -- Key == weak, if main key goes away, then cache cleared 11 | local outputCache = setmetatable({}, {__mode = 'k'}) 12 | -- TODO: inner tables weak? 13 | 14 | local function buildFunction(nextValues, innerValue, valueWriter, innerWriter) 15 | local putInner = "" 16 | if innerValue and innerWriter then 17 | -- Prepare the lua-string representation of the separator to put in between values 18 | local formattedInnerValue = ("%q"):format(innerValue) 19 | -- Fill in the condition %WRITE_INNER% and the %INNER_VALUE% to actually write 20 | putInner = innerWriter:gsub("%%WRITE_INNER%%", "%%1"):gsub("%%INNER_VALUE%%", formattedInnerValue) 21 | end 22 | -- Template-in the value writer (if present) and its conditional argument 23 | local functionCode = nextValues:gsub("PUTINNER(%b())", putInner) 24 | -- %VALUE% is to be filled in by the value-to-write 25 | valueWriter = valueWriter:gsub("%%VALUE%%", "%%1") 26 | -- Template-in the value writer with its argument 27 | functionCode = functionCode:gsub("PUTVALUE(%b())", valueWriter) 28 | functionCode = [[ 29 | return function(composite, ret, encode, state) 30 | ]] .. functionCode .. [[ 31 | end 32 | ]] 33 | return assert(loadstring(functionCode))() 34 | end 35 | 36 | local function prepareEncoder(cacheKey, nextValues, innerValue, valueWriter, innerWriter) 37 | local cache = outputCache[cacheKey] 38 | if not cache then 39 | cache = {} 40 | outputCache[cacheKey] = cache 41 | end 42 | local fun = cache[nextValues] 43 | if not fun then 44 | fun = buildFunction(nextValues, innerValue, valueWriter, innerWriter) 45 | cache[nextValues] = fun 46 | end 47 | return fun 48 | end 49 | 50 | local output_utility = { 51 | prepareEncoder = prepareEncoder 52 | } 53 | 54 | return output_utility 55 | -------------------------------------------------------------------------------- /lua/json/encode/strings.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local string_char = require("string").char 6 | local pairs = pairs 7 | 8 | local jsonutil = require("json.util") 9 | local util_merge = jsonutil.merge 10 | 11 | local _ENV = nil 12 | 13 | local normalEncodingMap = { 14 | ['"'] = '\\"', 15 | ['\\'] = '\\\\', 16 | ['/'] = '\\/', 17 | ['\b'] = '\\b', 18 | ['\f'] = '\\f', 19 | ['\n'] = '\\n', 20 | ['\r'] = '\\r', 21 | ['\t'] = '\\t', 22 | ['\v'] = '\\v' -- not in official spec, on report, removing 23 | } 24 | 25 | local xEncodingMap = {} 26 | for char, encoded in pairs(normalEncodingMap) do 27 | xEncodingMap[char] = encoded 28 | end 29 | 30 | -- Pre-encode the control characters to speed up encoding... 31 | -- NOTE: UTF-8 may not work out right w/ JavaScript 32 | -- JavaScript uses 2 bytes after a \u... yet UTF-8 is a 33 | -- byte-stream encoding, not pairs of bytes (it does encode 34 | -- some letters > 1 byte, but base case is 1) 35 | for i = 0, 255 do 36 | local c = string_char(i) 37 | if c:match('[%z\1-\031\128-\255]') and not normalEncodingMap[c] then 38 | -- WARN: UTF8 specializes values >= 0x80 as parts of sequences... 39 | -- without \x encoding, do not allow encoding > 7F 40 | normalEncodingMap[c] = ('\\u%.4X'):format(i) 41 | xEncodingMap[c] = ('\\x%.2X'):format(i) 42 | end 43 | end 44 | 45 | local defaultOptions = { 46 | xEncode = false, -- Encode single-bytes as \xXX 47 | processor = nil, -- Simple processor for the string prior to quoting 48 | -- / is not required to be quoted but it helps with certain decoding 49 | -- Required encoded characters, " \, and 00-1F (0 - 31) 50 | encodeSet = '\\"/%z\1-\031', 51 | encodeSetAppend = nil -- Chars to append to the default set 52 | } 53 | 54 | local modeOptions = {} 55 | 56 | local function mergeOptions(options, mode) 57 | jsonutil.doOptionMerge(options, false, 'strings', defaultOptions, mode and modeOptions[mode]) 58 | end 59 | 60 | local function getEncoder(options) 61 | options = options and util_merge({}, defaultOptions, options) or defaultOptions 62 | local encodeSet = options.encodeSet 63 | if options.encodeSetAppend then 64 | encodeSet = encodeSet .. options.encodeSetAppend 65 | end 66 | local encodingMap = options.xEncode and xEncodingMap or normalEncodingMap 67 | local encodeString 68 | if options.processor then 69 | local processor = options.processor 70 | encodeString = function(s, state) 71 | return '"' .. processor(s:gsub('[' .. encodeSet .. ']', encodingMap)) .. '"' 72 | end 73 | else 74 | encodeString = function(s, state) 75 | return '"' .. s:gsub('[' .. encodeSet .. ']', encodingMap) .. '"' 76 | end 77 | end 78 | return { 79 | string = encodeString 80 | } 81 | end 82 | 83 | local strings = { 84 | mergeOptions = mergeOptions, 85 | getEncoder = getEncoder 86 | } 87 | 88 | return strings 89 | -------------------------------------------------------------------------------- /lua/json/util.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Licensed according to the included 'LICENSE' document 3 | Author: Thomas Harning Jr 4 | ]] 5 | local type = type 6 | local print = print 7 | local tostring = tostring 8 | local pairs = pairs 9 | local getmetatable, setmetatable = getmetatable, setmetatable 10 | local select = select 11 | 12 | local _ENV = nil 13 | 14 | local function foreach(tab, func) 15 | for k, v in pairs(tab) do 16 | func(k,v) 17 | end 18 | end 19 | local function printValue(tab, name) 20 | local parsed = {} 21 | local function doPrint(key, value, space) 22 | space = space or '' 23 | if type(value) == 'table' then 24 | if parsed[value] then 25 | print(space .. key .. '= <' .. parsed[value] .. '>') 26 | else 27 | parsed[value] = key 28 | print(space .. key .. '= {') 29 | space = space .. ' ' 30 | foreach(value, function(key, value) doPrint(key, value, space) end) 31 | end 32 | else 33 | if type(value) == 'string' then 34 | value = '[[' .. tostring(value) .. ']]' 35 | end 36 | print(space .. key .. '=' .. tostring(value)) 37 | end 38 | end 39 | doPrint(name, tab) 40 | end 41 | 42 | local function clone(t) 43 | local ret = {} 44 | for k,v in pairs(t) do 45 | ret[k] = v 46 | end 47 | return ret 48 | end 49 | 50 | local function inner_merge(t, remaining, from, ...) 51 | if remaining == 0 then 52 | return t 53 | end 54 | if from then 55 | for k,v in pairs(from) do 56 | t[k] = v 57 | end 58 | end 59 | return inner_merge(t, remaining - 1, ...) 60 | end 61 | 62 | --[[* 63 | Shallow-merges tables in order onto the first table. 64 | 65 | @param t table to merge entries onto 66 | @param ... sequence of 0 or more tables to merge onto 't' 67 | 68 | @returns table 't' from input 69 | ]] 70 | local function merge(t, ...) 71 | return inner_merge(t, select('#', ...), ...) 72 | end 73 | 74 | -- Function to insert nulls into the JSON stream 75 | local function null() 76 | return null 77 | end 78 | 79 | -- Marker for 'undefined' values 80 | local function undefined() 81 | return undefined 82 | end 83 | 84 | local ArrayMT = {} 85 | 86 | --[[ 87 | Return's true if the metatable marks it as an array.. 88 | Or false if it has no array component at all 89 | Otherwise nil to get the normal detection component working 90 | ]] 91 | local function IsArray(value) 92 | if type(value) ~= 'table' then return false end 93 | local meta = getmetatable(value) 94 | local ret = meta == ArrayMT or (meta ~= nil and meta.__is_luajson_array) 95 | if not ret then 96 | if #value == 0 then return false end 97 | else 98 | return ret 99 | end 100 | end 101 | local function InitArray(array) 102 | setmetatable(array, ArrayMT) 103 | return array 104 | end 105 | 106 | local CallMT = {} 107 | 108 | local function isCall(value) 109 | return CallMT == getmetatable(value) 110 | end 111 | 112 | local function buildCall(name, ...) 113 | local callData = { 114 | name = name, 115 | parameters = {n = select('#', ...), ...} 116 | } 117 | return setmetatable(callData, CallMT) 118 | end 119 | 120 | local function decodeCall(callData) 121 | if not isCall(callData) then return nil end 122 | return callData.name, callData.parameters 123 | end 124 | 125 | local function doOptionMerge(options, nested, name, defaultOptions, modeOptions) 126 | if nested then 127 | modeOptions = modeOptions and modeOptions[name] 128 | defaultOptions = defaultOptions and defaultOptions[name] 129 | end 130 | options[name] = merge( 131 | {}, 132 | defaultOptions, 133 | modeOptions, 134 | options[name] 135 | ) 136 | end 137 | 138 | local json_util = { 139 | printValue = printValue, 140 | clone = clone, 141 | merge = merge, 142 | null = null, 143 | undefined = undefined, 144 | IsArray = IsArray, 145 | InitArray = InitArray, 146 | isCall = isCall, 147 | buildCall = buildCall, 148 | decodeCall = decodeCall, 149 | doOptionMerge = doOptionMerge 150 | } 151 | 152 | return json_util 153 | -------------------------------------------------------------------------------- /rockspecs/luajson-0.10-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "0.10-1" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/3637/luajson-0.10.tar.bz2", 5 | md5 = "0b6fa5e3a07daabe79241922b0bfda92" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lunit >= 0.4", 20 | "lpeg >= 0.8.1" 21 | } 22 | build = { 23 | type = "module", 24 | modules = { 25 | json = "src/json.lua", 26 | ["json.util"] = "src/json/util.lua", 27 | ["json.decode"] = "src/json/decode.lua", 28 | ["json.decode.array"] = "src/json/decode/array.lua", 29 | ["json.decode.calls"] = "src/json/decode/calls.lua", 30 | ["json.decode.number"] = "src/json/decode/number.lua", 31 | ["json.decode.object"] = "src/json/decode/object.lua", 32 | ["json.decode.others"] = "src/json/decode/others.lua", 33 | ["json.decode.strings"] = "src/json/decode/strings.lua", 34 | ["json.decode.util"] = "src/json/decode/util.lua", 35 | ["json.encode"] = "src/json/encode.lua", 36 | ["json.encode.array"] = "src/json/encode/array.lua", 37 | ["json.encode.calls"] = "src/json/encode/calls.lua", 38 | ["json.encode.number"] = "src/json/encode/number.lua", 39 | ["json.encode.object"] = "src/json/encode/object.lua", 40 | ["json.encode.others"] = "src/json/encode/others.lua", 41 | ["json.encode.strings"] = "src/json/encode/strings.lua" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rockspecs/luajson-0.10-2.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "0.10-2" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/3893/luajson-0.10.tar.bz2", 5 | md5 = "0b6fa5e3a07daabe79241922b0bfda92" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lunit >= 0.4", 20 | "lpeg >= 0.8.1" 21 | } 22 | build = { 23 | type = "module", 24 | modules = { 25 | json = "src/json.lua", 26 | ["json.util"] = "src/json/util.lua", 27 | ["json.decode"] = "src/json/decode.lua", 28 | ["json.decode.array"] = "src/json/decode/array.lua", 29 | ["json.decode.calls"] = "src/json/decode/calls.lua", 30 | ["json.decode.number"] = "src/json/decode/number.lua", 31 | ["json.decode.object"] = "src/json/decode/object.lua", 32 | ["json.decode.others"] = "src/json/decode/others.lua", 33 | ["json.decode.strings"] = "src/json/decode/strings.lua", 34 | ["json.decode.util"] = "src/json/decode/util.lua", 35 | ["json.encode"] = "src/json/encode.lua", 36 | ["json.encode.array"] = "src/json/encode/array.lua", 37 | ["json.encode.calls"] = "src/json/encode/calls.lua", 38 | ["json.encode.number"] = "src/json/encode/number.lua", 39 | ["json.encode.object"] = "src/json/encode/object.lua", 40 | ["json.encode.others"] = "src/json/encode/others.lua", 41 | ["json.encode.strings"] = "src/json/encode/strings.lua" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rockspecs/luajson-0.9.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "0.9.1-1" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/3637/luajson-0.9.1.tar.bz2", 5 | md5 = "49a6a3b7912a053a2b7ad6bbcf4d3564" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://gitrepo.or.cz/w/luajson.git", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lunit >= 0.4", 20 | "lpeg >= 0.8.1" 21 | } 22 | build = { 23 | type = "module", 24 | modules = { 25 | json = "src/json.lua", 26 | ["json.util"] = "src/json/util.lua", 27 | ["json.encode"] = "src/json/encode.lua", 28 | ["json.decode"] = "src/json/decode.lua", 29 | ["json.decode.array"] = "src/json/decode/array.lua", 30 | ["json.decode.number"] = "src/json/decode/number.lua", 31 | ["json.decode.object"] = "src/json/decode/object.lua", 32 | ["json.decode.string"] = "src/json/decode/strings.lua", 33 | ["json.decode.util"] = "src/json/decode/util.lua" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rockspecs/luajson-1.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "1.0-1" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/4105/luajson-1.0.tar.bz2", 5 | md5 = "0e3ee192f93d40c4947e828668828e91" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lunit >= 0.4", 20 | "lpeg >= 0.8.1" 21 | } 22 | build = { 23 | type = "module", 24 | modules = { 25 | ["json.decode.array"] = "lua/json/decode/array.lua", 26 | ["json.decode.calls"] = "lua/json/decode/calls.lua", 27 | ["json.decode.number"] = "lua/json/decode/number.lua", 28 | ["json.decode.object"] = "lua/json/decode/object.lua", 29 | ["json.decode.others"] = "lua/json/decode/others.lua", 30 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 31 | ["json.decode.util"] = "lua/json/decode/util.lua", 32 | ["json.decode"] = "lua/json/decode.lua", 33 | ["json.encode.array"] = "lua/json/encode/array.lua", 34 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 35 | ["json.encode.number"] = "lua/json/encode/number.lua", 36 | ["json.encode.object"] = "lua/json/encode/object.lua", 37 | ["json.encode.others"] = "lua/json/encode/others.lua", 38 | ["json.encode.output"] = "lua/json/encode/output.lua", 39 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 40 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 41 | ["json.encode"] = "lua/json/encode.lua", 42 | ["json.util"] = "lua/json/util.lua", 43 | ["json"] = "lua/json.lua" 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /rockspecs/luajson-1.0.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "1.0.1-1" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/4311/luajson-1.0.1.tar.bz2", 5 | md5 = "f6d52689e0bc4cbbfffff29308e78812" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lunit >= 0.4", 20 | "lpeg >= 0.8.1" 21 | } 22 | build = { 23 | type = "module", 24 | modules = { 25 | ["json"] = "lua/json.lua", 26 | ["json.decode"] = "lua/json/decode.lua", 27 | ["json.decode.array"] = "lua/json/decode/array.lua", 28 | ["json.decode.calls"] = "lua/json/decode/calls.lua", 29 | ["json.decode.number"] = "lua/json/decode/number.lua", 30 | ["json.decode.object"] = "lua/json/decode/object.lua", 31 | ["json.decode.others"] = "lua/json/decode/others.lua", 32 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 33 | ["json.decode.util"] = "lua/json/decode/util.lua", 34 | ["json.encode"] = "lua/json/encode.lua", 35 | ["json.encode.array"] = "lua/json/encode/array.lua", 36 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 37 | ["json.encode.number"] = "lua/json/encode/number.lua", 38 | ["json.encode.object"] = "lua/json/encode/object.lua", 39 | ["json.encode.others"] = "lua/json/encode/others.lua", 40 | ["json.encode.output"] = "lua/json/encode/output.lua", 41 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 42 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 43 | ["json.util"] = "lua/json/util.lua" 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /rockspecs/luajson-1.0.3-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "1.0.3-1" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/4678/luajson-1.0.3.tar.bz2", 5 | md5 = "15591a426672f32a639187a425464789" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lunit >= 0.4", 20 | "lpeg >= 0.8.1" 21 | } 22 | build = { 23 | type = "module", 24 | modules = { 25 | ["json"] = "lua/json.lua", 26 | ["json.decode"] = "lua/json/decode.lua", 27 | ["json.decode.array"] = "lua/json/decode/array.lua", 28 | ["json.decode.calls"] = "lua/json/decode/calls.lua", 29 | ["json.decode.number"] = "lua/json/decode/number.lua", 30 | ["json.decode.object"] = "lua/json/decode/object.lua", 31 | ["json.decode.others"] = "lua/json/decode/others.lua", 32 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 33 | ["json.decode.util"] = "lua/json/decode/util.lua", 34 | ["json.encode"] = "lua/json/encode.lua", 35 | ["json.encode.array"] = "lua/json/encode/array.lua", 36 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 37 | ["json.encode.number"] = "lua/json/encode/number.lua", 38 | ["json.encode.object"] = "lua/json/encode/object.lua", 39 | ["json.encode.others"] = "lua/json/encode/others.lua", 40 | ["json.encode.output"] = "lua/json/encode/output.lua", 41 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 42 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 43 | ["json.util"] = "lua/json/util.lua" 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /rockspecs/luajson-1.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "1.1-1" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/4312/luajson-1.1.tar.bz2", 5 | md5 = "9c4d267b7383b54576ed307e451aefa4" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lunit >= 0.4", 20 | "lpeg >= 0.8.1" 21 | } 22 | build = { 23 | type = "module", 24 | modules = { 25 | ["json"] = "lua/json.lua", 26 | ["json.decode"] = "lua/json/decode.lua", 27 | ["json.decode.array"] = "lua/json/decode/array.lua", 28 | ["json.decode.calls"] = "lua/json/decode/calls.lua", 29 | ["json.decode.number"] = "lua/json/decode/number.lua", 30 | ["json.decode.object"] = "lua/json/decode/object.lua", 31 | ["json.decode.others"] = "lua/json/decode/others.lua", 32 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 33 | ["json.decode.util"] = "lua/json/decode/util.lua", 34 | ["json.encode"] = "lua/json/encode.lua", 35 | ["json.encode.array"] = "lua/json/encode/array.lua", 36 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 37 | ["json.encode.number"] = "lua/json/encode/number.lua", 38 | ["json.encode.object"] = "lua/json/encode/object.lua", 39 | ["json.encode.others"] = "lua/json/encode/others.lua", 40 | ["json.encode.output"] = "lua/json/encode/output.lua", 41 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 42 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 43 | ["json.util"] = "lua/json/util.lua" 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /rockspecs/luajson-1.1.2-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "1.1.2-1" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/4677/luajson-1.1.2.tar.bz2", 5 | md5 = "432925dc0baea4329016e9bc5315e987" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lunit >= 0.4", 20 | "lpeg >= 0.8.1" 21 | } 22 | build = { 23 | type = "module", 24 | modules = { 25 | ["json"] = "lua/json.lua", 26 | ["json.decode"] = "lua/json/decode.lua", 27 | ["json.decode.array"] = "lua/json/decode/array.lua", 28 | ["json.decode.calls"] = "lua/json/decode/calls.lua", 29 | ["json.decode.number"] = "lua/json/decode/number.lua", 30 | ["json.decode.object"] = "lua/json/decode/object.lua", 31 | ["json.decode.others"] = "lua/json/decode/others.lua", 32 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 33 | ["json.decode.util"] = "lua/json/decode/util.lua", 34 | ["json.encode"] = "lua/json/encode.lua", 35 | ["json.encode.array"] = "lua/json/encode/array.lua", 36 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 37 | ["json.encode.number"] = "lua/json/encode/number.lua", 38 | ["json.encode.object"] = "lua/json/encode/object.lua", 39 | ["json.encode.others"] = "lua/json/encode/others.lua", 40 | ["json.encode.output"] = "lua/json/encode/output.lua", 41 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 42 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 43 | ["json.util"] = "lua/json/util.lua" 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /rockspecs/luajson-1.2-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "1.2-1" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/4672/luajson-1.2.tar.bz2", 5 | md5 = "a80aa3c2d96d73afbf63f7146857e116" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lunit >= 0.4", 20 | "lpeg >= 0.8.1" 21 | } 22 | build = { 23 | type = "module", 24 | modules = { 25 | ["json"] = "lua/json.lua", 26 | ["json.decode"] = "lua/json/decode.lua", 27 | ["json.decode.array"] = "lua/json/decode/array.lua", 28 | ["json.decode.calls"] = "lua/json/decode/calls.lua", 29 | ["json.decode.number"] = "lua/json/decode/number.lua", 30 | ["json.decode.object"] = "lua/json/decode/object.lua", 31 | ["json.decode.others"] = "lua/json/decode/others.lua", 32 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 33 | ["json.decode.util"] = "lua/json/decode/util.lua", 34 | ["json.encode"] = "lua/json/encode.lua", 35 | ["json.encode.array"] = "lua/json/encode/array.lua", 36 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 37 | ["json.encode.number"] = "lua/json/encode/number.lua", 38 | ["json.encode.object"] = "lua/json/encode/object.lua", 39 | ["json.encode.others"] = "lua/json/encode/others.lua", 40 | ["json.encode.output"] = "lua/json/encode/output.lua", 41 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 42 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 43 | ["json.util"] = "lua/json/util.lua" 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /rockspecs/luajson-1.2.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "1.2.1-1" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/4676/luajson-1.2.1.tar.bz2", 5 | md5 = "9efc22baf18b757d7a592376c2113bfd" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lunit >= 0.4", 20 | "lpeg >= 0.8.1" 21 | } 22 | build = { 23 | type = "module", 24 | modules = { 25 | ["json"] = "lua/json.lua", 26 | ["json.decode"] = "lua/json/decode.lua", 27 | ["json.decode.array"] = "lua/json/decode/array.lua", 28 | ["json.decode.calls"] = "lua/json/decode/calls.lua", 29 | ["json.decode.number"] = "lua/json/decode/number.lua", 30 | ["json.decode.object"] = "lua/json/decode/object.lua", 31 | ["json.decode.others"] = "lua/json/decode/others.lua", 32 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 33 | ["json.decode.util"] = "lua/json/decode/util.lua", 34 | ["json.encode"] = "lua/json/encode.lua", 35 | ["json.encode.array"] = "lua/json/encode/array.lua", 36 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 37 | ["json.encode.number"] = "lua/json/encode/number.lua", 38 | ["json.encode.object"] = "lua/json/encode/object.lua", 39 | ["json.encode.others"] = "lua/json/encode/others.lua", 40 | ["json.encode.output"] = "lua/json/encode/output.lua", 41 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 42 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 43 | ["json.util"] = "lua/json/util.lua" 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /rockspecs/luajson-1.2.2-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "1.2.2-1" 3 | source = { 4 | url = "http://luaforge.net/frs/download.php/4816/luajson-1.2.2.tar.bz2", 5 | md5 = "6b2768c4a2b102bf36308c722de4060a" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lunit >= 0.4", 20 | "lpeg >= 0.8.1" 21 | } 22 | build = { 23 | type = "module", 24 | modules = { 25 | ["json"] = "lua/json.lua", 26 | ["json.decode"] = "lua/json/decode.lua", 27 | ["json.decode.array"] = "lua/json/decode/array.lua", 28 | ["json.decode.calls"] = "lua/json/decode/calls.lua", 29 | ["json.decode.number"] = "lua/json/decode/number.lua", 30 | ["json.decode.object"] = "lua/json/decode/object.lua", 31 | ["json.decode.others"] = "lua/json/decode/others.lua", 32 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 33 | ["json.decode.util"] = "lua/json/decode/util.lua", 34 | ["json.encode"] = "lua/json/encode.lua", 35 | ["json.encode.array"] = "lua/json/encode/array.lua", 36 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 37 | ["json.encode.number"] = "lua/json/encode/number.lua", 38 | ["json.encode.object"] = "lua/json/encode/object.lua", 39 | ["json.encode.others"] = "lua/json/encode/others.lua", 40 | ["json.encode.output"] = "lua/json/encode/output.lua", 41 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 42 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 43 | ["json.util"] = "lua/json/util.lua" 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /rockspecs/luajson-1.3-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "1.3-1" 3 | source = { 4 | url = "http://cloud.github.com/downloads/harningt/luajson/luajson-1.3.tar.gz", 5 | md5 = "18a59640119143ea69b3744b467e1a89" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lunit >= 0.4", 20 | "lpeg >= 0.8.1" 21 | } 22 | build = { 23 | type = "module", 24 | modules = { 25 | ["json"] = "lua/json.lua", 26 | ["json.decode"] = "lua/json/decode.lua", 27 | ["json.decode.composite"] = "lua/json/decode/composite.lua", 28 | ["json.decode.number"] = "lua/json/decode/number.lua", 29 | ["json.decode.others"] = "lua/json/decode/others.lua", 30 | ["json.decode.state"] = "lua/json/decode/state.lua", 31 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 32 | ["json.decode.util"] = "lua/json/decode/util.lua", 33 | ["json.encode"] = "lua/json/encode.lua", 34 | ["json.encode.array"] = "lua/json/encode/array.lua", 35 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 36 | ["json.encode.number"] = "lua/json/encode/number.lua", 37 | ["json.encode.object"] = "lua/json/encode/object.lua", 38 | ["json.encode.others"] = "lua/json/encode/others.lua", 39 | ["json.encode.output"] = "lua/json/encode/output.lua", 40 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 41 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 42 | ["json.util"] = "lua/json/util.lua" 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /rockspecs/luajson-1.3.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "1.3.1-1" 3 | source = { 4 | url = "http://cloud.github.com/downloads/harningt/luajson/luajson-1.3.1.tar.gz", 5 | md5 = "1ef4751ea4a6e90f76a599d0b3f928f5" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lunit >= 0.4", 20 | "lpeg >= 0.8.1" 21 | } 22 | build = { 23 | type = "module", 24 | modules = { 25 | ["json"] = "lua/json.lua", 26 | ["json.decode"] = "lua/json/decode.lua", 27 | ["json.decode.composite"] = "lua/json/decode/composite.lua", 28 | ["json.decode.number"] = "lua/json/decode/number.lua", 29 | ["json.decode.others"] = "lua/json/decode/others.lua", 30 | ["json.decode.state"] = "lua/json/decode/state.lua", 31 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 32 | ["json.decode.util"] = "lua/json/decode/util.lua", 33 | ["json.encode"] = "lua/json/encode.lua", 34 | ["json.encode.array"] = "lua/json/encode/array.lua", 35 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 36 | ["json.encode.number"] = "lua/json/encode/number.lua", 37 | ["json.encode.object"] = "lua/json/encode/object.lua", 38 | ["json.encode.others"] = "lua/json/encode/others.lua", 39 | ["json.encode.output"] = "lua/json/encode/output.lua", 40 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 41 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 42 | ["json.util"] = "lua/json/util.lua" 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /rockspecs/luajson-1.3.2-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "1.3.2-1" 3 | source = { 4 | url = "git://github.com/harningt/luajson.git", 5 | tag = "1.3.2" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lunit >= 0.4", 20 | "lpeg >= 0.8.1" 21 | } 22 | build = { 23 | type = "module", 24 | modules = { 25 | ["json"] = "lua/json.lua", 26 | ["json.decode"] = "lua/json/decode.lua", 27 | ["json.decode.composite"] = "lua/json/decode/composite.lua", 28 | ["json.decode.number"] = "lua/json/decode/number.lua", 29 | ["json.decode.others"] = "lua/json/decode/others.lua", 30 | ["json.decode.state"] = "lua/json/decode/state.lua", 31 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 32 | ["json.decode.util"] = "lua/json/decode/util.lua", 33 | ["json.encode"] = "lua/json/encode.lua", 34 | ["json.encode.array"] = "lua/json/encode/array.lua", 35 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 36 | ["json.encode.number"] = "lua/json/encode/number.lua", 37 | ["json.encode.object"] = "lua/json/encode/object.lua", 38 | ["json.encode.others"] = "lua/json/encode/others.lua", 39 | ["json.encode.output"] = "lua/json/encode/output.lua", 40 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 41 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 42 | ["json.util"] = "lua/json/util.lua" 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /rockspecs/luajson-1.3.2-2.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "1.3.2-2" 3 | source = { 4 | url = "git://github.com/harningt/luajson.git", 5 | tag = "1.3.2" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lpeg >= 0.8.1" 20 | } 21 | build = { 22 | type = "module", 23 | modules = { 24 | ["json"] = "lua/json.lua", 25 | ["json.decode"] = "lua/json/decode.lua", 26 | ["json.decode.composite"] = "lua/json/decode/composite.lua", 27 | ["json.decode.number"] = "lua/json/decode/number.lua", 28 | ["json.decode.others"] = "lua/json/decode/others.lua", 29 | ["json.decode.state"] = "lua/json/decode/state.lua", 30 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 31 | ["json.decode.util"] = "lua/json/decode/util.lua", 32 | ["json.encode"] = "lua/json/encode.lua", 33 | ["json.encode.array"] = "lua/json/encode/array.lua", 34 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 35 | ["json.encode.number"] = "lua/json/encode/number.lua", 36 | ["json.encode.object"] = "lua/json/encode/object.lua", 37 | ["json.encode.others"] = "lua/json/encode/others.lua", 38 | ["json.encode.output"] = "lua/json/encode/output.lua", 39 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 40 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 41 | ["json.util"] = "lua/json/util.lua" 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /rockspecs/luajson-1.3.3-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "1.3.3-1" 3 | source = { 4 | url = "git://github.com/harningt/luajson.git", 5 | tag = "1.3.3" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lpeg >= 0.8.1" 20 | } 21 | build = { 22 | type = "module", 23 | modules = { 24 | ["json"] = "lua/json.lua", 25 | ["json.decode"] = "lua/json/decode.lua", 26 | ["json.decode.composite"] = "lua/json/decode/composite.lua", 27 | ["json.decode.number"] = "lua/json/decode/number.lua", 28 | ["json.decode.others"] = "lua/json/decode/others.lua", 29 | ["json.decode.state"] = "lua/json/decode/state.lua", 30 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 31 | ["json.decode.util"] = "lua/json/decode/util.lua", 32 | ["json.encode"] = "lua/json/encode.lua", 33 | ["json.encode.array"] = "lua/json/encode/array.lua", 34 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 35 | ["json.encode.number"] = "lua/json/encode/number.lua", 36 | ["json.encode.object"] = "lua/json/encode/object.lua", 37 | ["json.encode.others"] = "lua/json/encode/others.lua", 38 | ["json.encode.output"] = "lua/json/encode/output.lua", 39 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 40 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 41 | ["json.util"] = "lua/json/util.lua" 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /rockspecs/luajson-1.3.4-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "1.3.4-1" 3 | source = { 4 | url = "git://github.com/harningt/luajson.git", 5 | tag = "1.3.4" 6 | } 7 | description = { 8 | summary = "customizable JSON decoder/encoder", 9 | detailed = [[ 10 | LuaJSON is a customizable JSON decoder/encoder using 11 | LPEG for parsing. 12 | ]], 13 | homepage = "http://github.com/harningt/luajson", 14 | maintainer = "Thomas Harning ", 15 | license = "MIT/X11" 16 | } 17 | dependencies = { 18 | "lua >= 5.1", 19 | "lpeg >= 0.8.1" 20 | } 21 | build = { 22 | type = "module", 23 | modules = { 24 | ["json"] = "lua/json.lua", 25 | ["json.decode"] = "lua/json/decode.lua", 26 | ["json.decode.composite"] = "lua/json/decode/composite.lua", 27 | ["json.decode.number"] = "lua/json/decode/number.lua", 28 | ["json.decode.others"] = "lua/json/decode/others.lua", 29 | ["json.decode.state"] = "lua/json/decode/state.lua", 30 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 31 | ["json.decode.util"] = "lua/json/decode/util.lua", 32 | ["json.encode"] = "lua/json/encode.lua", 33 | ["json.encode.array"] = "lua/json/encode/array.lua", 34 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 35 | ["json.encode.number"] = "lua/json/encode/number.lua", 36 | ["json.encode.object"] = "lua/json/encode/object.lua", 37 | ["json.encode.others"] = "lua/json/encode/others.lua", 38 | ["json.encode.output"] = "lua/json/encode/output.lua", 39 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 40 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 41 | ["json.util"] = "lua/json/util.lua" 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /rockspecs/luajson-scm-4.rockspec: -------------------------------------------------------------------------------- 1 | package = "luajson" 2 | version = "scm-4" 3 | source = { 4 | url = "git://github.com/harningt/luajson.git" 5 | } 6 | description = { 7 | summary = "customizable JSON decoder/encoder", 8 | detailed = [[ 9 | LuaJSON is a customizable JSON decoder/encoder using 10 | LPEG for parsing. 11 | ]], 12 | homepage = "http://github.com/harningt/luajson", 13 | maintainer = "Thomas Harning ", 14 | license = "MIT/X11" 15 | } 16 | dependencies = { 17 | "lua >= 5.1", 18 | "lunit >= 0.4", 19 | "lpeg >= 0.8.1" 20 | } 21 | build = { 22 | type = "module", 23 | modules = { 24 | ["json"] = "lua/json.lua", 25 | ["json.decode"] = "lua/json/decode.lua", 26 | ["json.decode.composite"] = "lua/json/decode/composite.lua", 27 | ["json.decode.number"] = "lua/json/decode/number.lua", 28 | ["json.decode.others"] = "lua/json/decode/others.lua", 29 | ["json.decode.state"] = "lua/json/decode/state.lua", 30 | ["json.decode.strings"] = "lua/json/decode/strings.lua", 31 | ["json.decode.util"] = "lua/json/decode/util.lua", 32 | ["json.encode"] = "lua/json/encode.lua", 33 | ["json.encode.array"] = "lua/json/encode/array.lua", 34 | ["json.encode.calls"] = "lua/json/encode/calls.lua", 35 | ["json.encode.number"] = "lua/json/encode/number.lua", 36 | ["json.encode.object"] = "lua/json/encode/object.lua", 37 | ["json.encode.others"] = "lua/json/encode/others.lua", 38 | ["json.encode.output"] = "lua/json/encode/output.lua", 39 | ["json.encode.output_utility"] = "lua/json/encode/output_utility.lua", 40 | ["json.encode.strings"] = "lua/json/encode/strings.lua", 41 | ["json.util"] = "lua/json/util.lua" 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /tests/.luacov: -------------------------------------------------------------------------------- 1 | --- Global configuration file. Copy, customize and store in your 2 | -- project folder as '.luacov' for project specific configuration 3 | -- @class module 4 | -- @name luacov.defaults 5 | return { 6 | 7 | -- default filename to load for config options if not provided 8 | -- only has effect in 'luacov.defaults.lua' 9 | configfile = ".luacov", 10 | 11 | -- filename to store stats collected 12 | statsfile = "luacov.stats.out", 13 | 14 | -- filename to store report 15 | reportfile = "luacov.report.json", 16 | 17 | -- Run reporter on completion? (won't work for ticks) 18 | runreport = false, 19 | 20 | -- Delete stats file after reporting? 21 | deletestats = false, 22 | 23 | -- Patterns for files to include when reporting 24 | -- all will be included if nothing is listed 25 | -- (exclude overrules include, do not include 26 | -- the .lua extension) 27 | include = { 28 | "/json$", 29 | "/json/.+$", 30 | }, 31 | 32 | -- Patterns for files to exclude when reporting 33 | -- all will be included if nothing is listed 34 | -- (exclude overrules include, do not include 35 | -- the .lua extension) 36 | exclude = { 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /tests/data.txt: -------------------------------------------------------------------------------- 1 | {"a317":null,"a50":50,"a108":true,"a482":482,"a397":null,"a228":true,"a193":null,"a58":58,"a71":[],"a2":2,"a376":true,"a46":46,"a337":null,"a366":366,"a239":[],"a347":[],"a388":true,"a424":true,"a386":386,"a387":[],"a81":null,"a49":null,"a60":true,"a496":true,"a346":346,"a497":null,"a328":true,"a370":370,"a427":[],"a194":194,"a269":null,"a354":354,"a265":null,"a454":454,"a276":true,"a494":494,"a96":true,"a392":true,"a492":true,"a273":null,"a339":[],"a491":[],"a490":490,"a266":266,"a37":null,"a489":null,"a447":[],"a488":true,"a487":[],"a486":486,"a278":278,"a149":null,"a59":[],"a103":[],"a153":null,"a395":[],"a357":null,"a483":[],"a481":null,"a480":true,"a51":[],"a479":[],"a299":[],"a127":[],"a123":[],"a467":[],"a475":[],"a474":474,"a173":null,"a82":82,"a165":null,"a244":true,"a375":[],"a472":true,"a377":null,"a471":[],"a83":[],"a147":[],"a87":[],"a456":true,"a315":[],"a145":null,"a249":null,"a406":406,"a40":true,"a29":null,"a468":true,"a114":114,"a128":true,"a409":null,"a355":[],"a293":null,"a48":true,"a476":true,"a227":[],"a466":466,"a267":[],"a465":null,"a464":true,"a463":[],"a92":true,"a237":null,"a76":true,"a462":462,"a461":null,"a275":[],"a139":[],"a460":true,"a247":[],"a97":null,"a459":[],"a174":174,"a458":458,"a415":[],"a172":true,"a457":null,"a301":null,"a295":[],"a39":[],"a34":34,"a291":[],"a116":true,"a202":202,"a255":[],"a200":true,"a433":null,"a35":[],"a407":[],"a470":470,"a212":true,"a126":126,"a292":true,"a62":62,"a134":134,"a321":null,"a455":[],"a222":222,"a143":[],"a175":[],"a136":true,"a198":198,"a63":[],"a120":true,"a67":[],"a452":true,"a437":null,"a450":450,"a156":true,"a195":[],"a201":null,"a44":true,"a449":null,"a155":[],"a391":[],"a89":null,"a105":null,"a351":[],"a300":true,"a28":true,"a45":null,"a135":[],"a426":426,"a1":null,"a448":true,"a144":true,"a72":true,"a340":true,"a56":true,"a446":446,"a125":null,"a90":90,"a182":182,"a73":null,"a343":[],"a211":[],"a444":true,"a443":[],"a442":442,"a245":null,"a441":null,"a440":true,"a15":[],"a439":[],"a364":true,"a402":402,"a246":246,"a132":true,"a99":[],"a14":14,"a451":[],"a324":true,"a91":[],"a78":78,"a176":true,"a382":382,"a109":null,"a435":[],"a434":434,"a66":66,"a110":110,"a166":166,"a336":true,"a422":422,"a432":true,"a431":[],"a47":[],"a190":190,"a43":[],"a430":430,"a356":true,"a283":[],"a429":null,"a25":null,"a361":null,"a428":true,"a80":true,"a69":null,"a305":null,"a146":146,"a196":true,"a333":null,"a24":true,"a425":null,"a500":true,"a3":[],"a423":[],"a186":186,"a88":true,"a421":null,"a36":true,"a350":350,"a52":true,"a389":null,"a171":[],"a236":true,"a325":null,"a411":[],"a298":298,"a57":null,"a204":true,"a181":null,"a349":null,"a418":418,"a138":138,"a169":null,"a417":null,"a416":true,"a414":414,"a413":null,"a79":[],"a319":[],"a412":true,"a124":true,"a75":[],"a394":394,"a384":true,"a408":true,"a118":118,"a405":null,"a268":true,"a231":[],"a279":[],"a27":[],"a404":true,"a23":[],"a306":306,"a271":[],"a401":null,"a289":null,"a400":true,"a150":150,"a399":[],"a398":398,"a22":22,"a238":238,"a281":null,"a396":true,"a84":true,"a484":true,"a393":null,"a493":null,"a390":390,"a178":178,"a419":[],"a85":null,"a5":null,"a133":null,"a499":[],"a218":218,"a162":162,"a368":true,"a235":[],"a379":[],"a410":410,"a260":true,"a383":[],"a436":true,"a33":null,"a335":[],"a371":[],"a141":null,"a290":290,"a220":true,"a380":true,"a32":true,"a338":338,"a381":null,"a142":142,"a374":374,"a140":true,"a373":null,"a372":true,"a119":[],"a101":null,"a205":null,"a197":null,"a55":[],"a159":[],"a495":[],"a261":null,"a187":[],"a129":null,"a30":30,"a318":318,"a369":null,"a329":null,"a223":[],"a160":true,"a158":158,"a252":true,"a148":true,"a121":null,"a304":true,"a241":null,"a365":null,"a26":26,"a438":438,"a168":true,"a111":[],"a363":[],"a64":true,"a362":362,"a360":true,"a151":[],"a216":true,"a219":[],"a21":null,"a191":[],"a100":true,"a65":null,"a359":[],"a7":[],"a192":true,"a312":true,"a102":102,"a188":true,"a234":234,"a131":[],"a189":null,"a17":null,"a353":null,"a112":true,"a352":true,"a13":null,"a420":true,"a348":true,"a313":null,"a179":[],"a122":122,"a345":null,"a12":true,"a344":true,"a18":18,"a342":342,"a251":[],"a303":[],"a341":null,"a378":378,"a334":334,"a157":null,"a130":130,"a332":true,"a331":[],"a330":330,"a10":10,"a367":[],"a327":[],"a326":326,"a323":[],"a322":322,"a225":null,"a86":86,"a316":true,"a177":null,"a314":314,"a358":358,"a311":[],"a185":null,"a310":310,"a309":null,"a217":null,"a206":206,"a115":[],"a164":true,"a240":true,"a208":true,"a403":[],"a302":302,"a9":null,"a257":null,"a478":478,"a297":null,"a296":true,"a294":294,"a161":null,"a307":[],"a287":[],"a286":286,"a285":null,"a20":true,"a284":true,"a282":282,"a280":true,"a485":null,"a152":true,"a277":null,"a274":274,"a272":true,"a270":270,"a264":true,"a263":[],"a77":null,"a106":106,"a154":154,"a167":[],"a215":[],"a308":true,"a98":98,"a94":94,"a70":70,"a74":74,"a11":[],"a256":true,"a254":254,"a253":null,"a19":[],"a207":[],"a95":[],"a262":262,"a250":250,"a248":true,"a184":true,"a243":[],"a242":242,"a385":null,"a41":null,"a233":null,"a68":true,"a232":true,"a230":230,"a229":null,"a209":null,"a226":226,"a320":true,"a224":true,"a16":true,"a221":null,"a54":54,"a117":null,"a113":null,"a38":38,"a259":[],"a137":null,"a214":214,"a213":null,"a183":[],"a469":null,"a107":[],"a163":[],"a61":null,"a93":null,"a42":42,"a453":null,"a203":[],"a199":[],"a445":null,"a31":[],"a210":210,"a6":6,"a477":null,"a258":258,"a104":true,"a4":true,"a180":true,"a498":498,"a170":170,"a473":null,"a288":true,"a8":true,"a53":null} 2 | -------------------------------------------------------------------------------- /tests/dataTest.lua: -------------------------------------------------------------------------------- 1 | local io = require("io") 2 | require("json") 3 | local f = io.open("data.txt") 4 | local data = f:read('*a') 5 | f:close() 6 | local opt = (...) 7 | local strict = opt and opt:match('--strict') 8 | local decode = json.decode.decode 9 | for i = 1,1000 do 10 | decode(data, strict) 11 | end 12 | -------------------------------------------------------------------------------- /tests/dataTest.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/hook_require.lua: -------------------------------------------------------------------------------- 1 | local os = require("os") 2 | local old_require = require 3 | if os.getenv('LUA_OLD_INIT') then 4 | local loadstring = loadstring or load 5 | assert(loadstring(os.getenv('LUA_OLD_INIT')))() 6 | else 7 | require("luarocks.require") 8 | end 9 | local luarocks_require = require 10 | 11 | function require(module, ...) 12 | if module == "json" or module:match("^json%.") then 13 | return old_require(module, ...) 14 | end 15 | return luarocks_require(module, ...) 16 | end 17 | -------------------------------------------------------------------------------- /tests/lunit-calls.lua: -------------------------------------------------------------------------------- 1 | local lpeg = require("lpeg") 2 | local json = require("json") 3 | local lunit = require("lunit") 4 | local math = require("math") 5 | local testutil = require("testutil") 6 | 7 | local encode = json.encode 8 | -- DECODE NOT 'local' due to requirement for testutil to access it 9 | decode = json.decode.getDecoder(false) 10 | 11 | if not module then 12 | _ENV = lunit.module("lunit-calls", 'seeall') 13 | else 14 | module("lunit-calls", lunit.testcase, package.seeall) 15 | end 16 | 17 | function setup() 18 | -- Ensure that the decoder is reset 19 | _G["decode"] = json.decode.getDecoder(false) 20 | end 21 | 22 | local values = { 23 | 0, 24 | 1, 25 | 0.2, 26 | "Hello", 27 | true, 28 | {hi=true}, 29 | {1,2} 30 | } 31 | 32 | function test_identity() 33 | local function testFunction(capturedName, ...) 34 | assert_equal('call', capturedName) 35 | return (...) 36 | end 37 | local strict = { 38 | calls = { defs = { 39 | call = testFunction 40 | } } 41 | } 42 | local decode = json.decode.getDecoder(strict) 43 | for i, v in ipairs(values) do 44 | local str = "call(" .. encode(v) .. ")" 45 | local decoded = decode(str) 46 | if type(decoded) == 'table' then 47 | for k2, v2 in pairs(v) do 48 | assert_equal(v2, decoded[k2]) 49 | decoded[k2] = nil 50 | end 51 | assert_nil(next(decoded)) 52 | else 53 | assert_equal(v, decoded) 54 | end 55 | end 56 | end 57 | 58 | -- Test for a function that throws 59 | function test_function_failure() 60 | local function testFunction(...) 61 | error("CANNOT CONTINUE") 62 | end 63 | local strict = { 64 | calls = { defs = { 65 | call = testFunction 66 | } } 67 | } 68 | local decode = json.decode.getDecoder(strict) 69 | for i, v in ipairs(values) do 70 | local str = "call(" .. encode(v) .. ")" 71 | assert_error(function() 72 | decode(str) 73 | end) 74 | end 75 | end 76 | 77 | -- Test for a function that is not a function 78 | function test_not_a_function_fail() 79 | local notFunction = { 80 | 0/0, 1/0, -1/0, 0, 1, "Hello", {}, coroutine.create(function() end) 81 | } 82 | for _, v in ipairs(notFunction) do 83 | assert_error(function() 84 | local strict = { 85 | calls = { defs = { 86 | call = v 87 | }, allowUndefined = false } 88 | } 89 | json.decode.getDecoder(strict) 90 | end) 91 | end 92 | end 93 | 94 | function test_not_permitted_fail() 95 | local strict = { 96 | calls = { 97 | defs = { call = false } 98 | } 99 | } 100 | local decoder = json.decode.getDecoder(strict) 101 | assert_error(function() 102 | decoder("call(1)") 103 | end) 104 | end 105 | 106 | function test_permitted() 107 | local strict = { 108 | calls = { 109 | defs = { call = true, other = true } 110 | } 111 | } 112 | local decoder = json.decode.getDecoder(strict) 113 | assert(decoder("call(1)").name == 'call') 114 | assert(decoder("other(1)").name == 'other') 115 | end 116 | 117 | function test_permitted_trailing() 118 | local strict = { 119 | calls = { 120 | defs = { call = true, other = true } 121 | } 122 | } 123 | local decoder = json.decode.getDecoder(strict) 124 | assert(decoder("call(1,)").name == 'call') 125 | assert(decoder("other(1,)").name == 'other') 126 | end 127 | function test_permitted_no_trailing() 128 | local strict = { 129 | calls = { 130 | defs = { call = true, other = true }, 131 | trailingComma = false 132 | } 133 | } 134 | local decoder = json.decode.getDecoder(strict) 135 | assert_error(function() 136 | decoder("call(1,)") 137 | end) 138 | assert_error(function() 139 | decoder("other(1,)") 140 | end) 141 | end 142 | function test_permitted_nested() 143 | local strict = { 144 | calls = { 145 | defs = { call = true, other = true } 146 | } 147 | } 148 | local decoder = json.decode.getDecoder(strict) 149 | assert(decoder("call(call(1))").name == 'call') 150 | assert(decoder("other(call(1))").name == 'other') 151 | end 152 | 153 | function test_not_defined_fail() 154 | local decoder = json.decode.getDecoder({ 155 | calls = { 156 | allowUndefined = false 157 | } 158 | }) 159 | assert_error(function() 160 | decoder("call(1)") 161 | end) 162 | end 163 | 164 | function test_not_defined_succeed() 165 | local decoder = json.decode.getDecoder({ 166 | calls = { 167 | allowUndefined = true 168 | } 169 | }) 170 | assert(decoder("call(1)").name == 'call') 171 | end 172 | 173 | -- Test for a name that is not a string 174 | function test_name_not_string() 175 | local notString = { 176 | true, false, 0/0, 1/0, -1/0, 0, 1, {}, function() end, coroutine.create(function() end) 177 | } 178 | for _, v in ipairs(notString) do 179 | assert_error(function() 180 | local defs = { 181 | [v] = function() end 182 | } 183 | local strict = { 184 | calls = { defs = defs } 185 | } 186 | json.decode.getDecoder(strict) 187 | end) 188 | end 189 | end 190 | 191 | -- Test for a name that is a string or a pattern 192 | function test_name_matches_string_or_pattern() 193 | local matchedValues = { 194 | ["mystring"] = "mystring", 195 | [lpeg.C(lpeg.P("m") * (lpeg.P("y") + lpeg.P("Y")) * "string")] = "mystring", 196 | [lpeg.C(lpeg.P("m") * (lpeg.P("y") + lpeg.P("Y")) * "string")] = "mYstring" 197 | } 198 | for pattern, value in pairs(matchedValues) do 199 | local matched = false 200 | local function mustBeCalled(capturedName, ...) 201 | assert_equal(value, capturedName) 202 | matched = true 203 | end 204 | matched = false 205 | local strict = { 206 | calls = { defs = { 207 | [pattern] = mustBeCalled 208 | } } 209 | } 210 | json.decode.getDecoder(strict)(value .. "(true)") 211 | assert_true(matched, "Value <" .. value .. "> did not match the given pattern") 212 | end 213 | end 214 | -------------------------------------------------------------------------------- /tests/lunit-depth.lua: -------------------------------------------------------------------------------- 1 | local lunit = require("lunit") 2 | local json = require("json") 3 | 4 | if not module then 5 | _ENV = lunit.module("lunit-depth", 'seeall') 6 | else 7 | module("lunit-depth", lunit.testcase, package.seeall) 8 | end 9 | 10 | local SAFE_DEPTH = 23 11 | local SAFE_CALL_DEPTH = 31 12 | 13 | function test_object_current_max_depth() 14 | local root = {} 15 | for i = 1, SAFE_DEPTH do 16 | root = { x = root } 17 | end 18 | local encoded = json.encode(root) 19 | json.decode(encoded) 20 | end 21 | 22 | function test_array_current_max_depth() 23 | local root = {} 24 | for i = 1, SAFE_DEPTH do 25 | root = { root } 26 | end 27 | local encoded = json.encode(root) 28 | json.decode(encoded) 29 | end 30 | 31 | function test_function_current_max_depth() 32 | local root = json.util.buildCall("deep") 33 | for i = 1, SAFE_CALL_DEPTH do 34 | root = json.util.buildCall("deep", root) 35 | end 36 | local encoded = json.encode(root) 37 | json.decode(encoded, { calls = { allowUndefined = true }}) 38 | end 39 | 40 | if os.getenv("TEST_UNSAFE") then 41 | local UNSAFE_DEPTH = 194 42 | local UNSAFE_CALL_DEPTH = UNSAFE_DEPTH 43 | function test_object_unsafe_max_depth() 44 | local root = {} 45 | for i = 1, UNSAFE_DEPTH do 46 | root = { x = root } 47 | end 48 | local encoded = json.encode(root) 49 | json.decode(encoded) 50 | end 51 | 52 | function test_array_unsafe_max_depth() 53 | local root = {} 54 | for i = 1, UNSAFE_DEPTH do 55 | root = { root } 56 | end 57 | local encoded = json.encode(root) 58 | json.decode(encoded) 59 | end 60 | 61 | function test_function_unsafe_max_depth() 62 | local root = json.util.buildCall("deep") 63 | for i = 1, UNSAFE_CALL_DEPTH do 64 | root = json.util.buildCall("deep", root) 65 | end 66 | local encoded = json.encode(root) 67 | json.decode(encoded, { calls = { allowUndefined = true }}) 68 | end 69 | 70 | end 71 | -------------------------------------------------------------------------------- /tests/lunit-empties-decode.lua: -------------------------------------------------------------------------------- 1 | local json = require("json") 2 | local lunit = require("lunit") 3 | 4 | -- Test module for handling the decoding with 'empties' allowed 5 | if not module then 6 | _ENV = lunit.module("lunit-empties-decode", 'seeall') 7 | else 8 | module("lunit-empties-decode", lunit.testcase, package.seeall) 9 | end 10 | 11 | local options = { 12 | array = { 13 | allowEmptyElement = true 14 | }, 15 | calls = { 16 | allowEmptyElement = true, 17 | allowUndefined = true 18 | }, 19 | object = { 20 | allowEmptyElement = true, 21 | } 22 | } 23 | local options_notrailing = { 24 | array = { 25 | allowEmptyElement = true, 26 | trailingComma = false 27 | }, 28 | calls = { 29 | allowEmptyElement = true, 30 | allowUndefined = true, 31 | trailingComma = false 32 | }, 33 | object = { 34 | allowEmptyElement = true, 35 | trailingComma = false 36 | } 37 | } 38 | local options_simple_null = { 39 | array = { 40 | allowEmptyElement = true 41 | }, 42 | calls = { 43 | allowEmptyElement = true, 44 | allowUndefined = true 45 | }, 46 | object = { 47 | allowEmptyElement = true, 48 | }, 49 | others = { 50 | null = false, 51 | undefined = false 52 | } 53 | } 54 | 55 | function test_decode_array_with_only_null() 56 | local result = assert(json.decode('[null]', options_simple_null)) 57 | assert_nil(result[1]) 58 | assert_equal(1, result.n) 59 | local result = assert(json.decode('[null]', options)) 60 | assert_equal(json.util.null, result[1]) 61 | assert_equal(1, #result) 62 | end 63 | 64 | function test_decode_array_with_empties() 65 | local result = assert(json.decode('[,]', options_simple_null)) 66 | assert_nil(result[1]) 67 | assert_equal(1, result.n) 68 | local result = assert(json.decode('[,]', options)) 69 | assert_equal(json.util.undefined, result[1]) 70 | assert_equal(1, #result) 71 | 72 | local result = assert(json.decode('[,]', options_notrailing)) 73 | assert_equal(json.util.undefined, result[1]) 74 | assert_equal(json.util.undefined, result[2]) 75 | assert_equal(2, #result) 76 | end 77 | 78 | function test_decode_array_with_null() 79 | local result = assert(json.decode('[1, null, 3]', options_simple_null)) 80 | assert_equal(1, result[1]) 81 | assert_nil(result[2]) 82 | assert_equal(3, result[3]) 83 | assert_equal(3, result.n) 84 | local result = assert(json.decode('[1, null, 3]', options)) 85 | assert_equal(1, result[1]) 86 | assert_equal(json.util.null, result[2]) 87 | assert_equal(3, result[3]) 88 | end 89 | function test_decode_array_with_empty() 90 | local result = assert(json.decode('[1,, 3]', options_simple_null)) 91 | assert_equal(1, result[1]) 92 | assert_nil(result[2]) 93 | assert_equal(3, result[3]) 94 | assert_equal(3, result.n) 95 | local result = assert(json.decode('[1,, 3]', options)) 96 | assert_equal(1, result[1]) 97 | assert_equal(json.util.undefined, result[2]) 98 | assert_equal(3, result[3]) 99 | end 100 | 101 | function test_decode_small_array_with_trailing_null() 102 | local result = assert(json.decode('[1, null]', options_simple_null)) 103 | assert_equal(1, result[1]) 104 | assert_nil(result[2]) 105 | assert_equal(2, result.n) 106 | local result = assert(json.decode('[1, ]', options_simple_null)) 107 | assert_equal(1, result[1]) 108 | assert_equal(1, #result) 109 | local result = assert(json.decode('[1, ]', options)) 110 | assert_equal(1, result[1]) 111 | assert_equal(1, #result) 112 | local result = assert(json.decode('[1, ]', options_notrailing)) 113 | assert_equal(1, result[1]) 114 | assert_equal(json.util.undefined, result[2]) 115 | assert_equal(2, #result) 116 | end 117 | 118 | function test_decode_array_with_trailing_null() 119 | local result = assert(json.decode('[1, null, 3, null]', options_simple_null)) 120 | assert_equal(1, result[1]) 121 | assert_nil(result[2]) 122 | assert_equal(3, result[3]) 123 | assert_nil(result[4]) 124 | assert_equal(4, result.n) 125 | local result = assert(json.decode('[1, null, 3, null]', options)) 126 | assert_equal(1, result[1]) 127 | assert_equal(json.util.null, result[2]) 128 | assert_equal(3, result[3]) 129 | assert_equal(json.util.null, result[4]) 130 | assert_equal(4, #result) 131 | local result = assert(json.decode('[1, , 3, ]', options)) 132 | assert_equal(1, result[1]) 133 | assert_equal(json.util.undefined, result[2]) 134 | assert_equal(3, result[3]) 135 | assert_equal(3, #result) 136 | local result = assert(json.decode('[1, , 3, ]', options_notrailing)) 137 | assert_equal(1, result[1]) 138 | assert_equal(json.util.undefined, result[2]) 139 | assert_equal(3, result[3]) 140 | assert_equal(json.util.undefined, result[4]) 141 | assert_equal(4, #result) 142 | end 143 | 144 | function test_decode_object_with_null() 145 | local result = assert(json.decode('{x: null}', options_simple_null)) 146 | assert_nil(result.x) 147 | assert_nil(next(result)) 148 | 149 | local result = assert(json.decode('{x: null}', options)) 150 | assert_equal(json.util.null, result.x) 151 | 152 | local result = assert(json.decode('{x: }', options_simple_null)) 153 | assert_nil(result.x) 154 | assert_nil(next(result)) 155 | 156 | local result = assert(json.decode('{x: }', options)) 157 | assert_equal(json.util.undefined, result.x) 158 | 159 | -- Handle the trailing comma case 160 | local result = assert(json.decode('{x: ,}', options_simple_null)) 161 | assert_nil(result.x) 162 | assert_nil(next(result)) 163 | 164 | local result = assert(json.decode('{x: ,}', options)) 165 | assert_equal(json.util.undefined, result.x) 166 | 167 | -- NOTE: Trailing comma must be allowed explicitly in this case 168 | assert_error(function() 169 | json.decode('{x: ,}', options_notrailing) 170 | end) 171 | 172 | -- Standard setup doesn't allow empties 173 | assert_error(function() 174 | json.decode('{x: }') 175 | end) 176 | end 177 | function test_decode_bigger_object_with_null() 178 | local result = assert(json.decode('{y: 1, x: null}', options_simple_null)) 179 | assert_equal(1, result.y) 180 | assert_nil(result.x) 181 | 182 | local result = assert(json.decode('{y: 1, x: null}', options)) 183 | assert_equal(1, result.y) 184 | assert_equal(json.util.null, result.x) 185 | 186 | local result = assert(json.decode('{y: 1, x: }', options_simple_null)) 187 | assert_equal(1, result.y) 188 | assert_nil(result.x) 189 | local result = assert(json.decode('{x: , y: 1}', options_simple_null)) 190 | assert_equal(1, result.y) 191 | assert_nil(result.x) 192 | 193 | local result = assert(json.decode('{y: 1, x: }', options)) 194 | assert_equal(1, result.y) 195 | assert_equal(json.util.undefined, result.x) 196 | 197 | local result = assert(json.decode('{x: , y: 1}', options)) 198 | assert_equal(1, result.y) 199 | assert_equal(json.util.undefined, result.x) 200 | 201 | -- Handle the trailing comma case 202 | local result = assert(json.decode('{y: 1, x: , }', options_simple_null)) 203 | assert_equal(1, result.y) 204 | assert_nil(result.x) 205 | local result = assert(json.decode('{x: , y: 1, }', options_simple_null)) 206 | assert_equal(1, result.y) 207 | assert_nil(result.x) 208 | 209 | local result = assert(json.decode('{y: 1, x: ,}', options)) 210 | assert_equal(1, result.y) 211 | assert_equal(json.util.undefined, result.x) 212 | 213 | local result = assert(json.decode('{x: , y: 1, }', options)) 214 | assert_equal(1, result.y) 215 | assert_equal(json.util.undefined, result.x) 216 | 217 | -- NOTE: Trailing comma must be allowed explicitly in this case as there is no such thing as an "empty" key:value pair 218 | assert_error(function() 219 | json.decode('{y: 1, x: ,}', options_notrailing) 220 | end) 221 | assert_error(function() 222 | json.decode('{x: , y: 1, }', options_notrailing) 223 | end) 224 | end 225 | 226 | function test_decode_call_with_empties() 227 | local result = assert(json.decode('call(,)', options_simple_null)) 228 | result = result.parameters 229 | assert_nil(result[1]) 230 | assert_equal(1, result.n) 231 | local result = assert(json.decode('call(,)', options)) 232 | result = result.parameters 233 | assert_equal(json.util.undefined, result[1]) 234 | assert_equal(1, #result) 235 | 236 | local result = assert(json.decode('call(,)', options_notrailing)) 237 | result = result.parameters 238 | assert_equal(json.util.undefined, result[1]) 239 | assert_equal(json.util.undefined, result[2]) 240 | assert_equal(2, #result) 241 | end 242 | 243 | 244 | 245 | function test_call_with_empties_and_trailing() 246 | local result = assert(json.decode('call(1, null, 3, null)', options_simple_null)) 247 | result = result.parameters 248 | assert_equal(1, result[1]) 249 | assert_nil(result[2]) 250 | assert_equal(3, result[3]) 251 | assert_nil(result[4]) 252 | assert_equal(4, result.n) 253 | local result = assert(json.decode('call(1, null, 3, null)', options)) 254 | result = result.parameters 255 | assert_equal(1, result[1]) 256 | assert_equal(json.util.null, result[2]) 257 | assert_equal(3, result[3]) 258 | assert_equal(json.util.null, result[4]) 259 | assert_equal(4, #result) 260 | local result = assert(json.decode('call(1, , 3, )', options)) 261 | result = result.parameters 262 | assert_equal(1, result[1]) 263 | assert_equal(json.util.undefined, result[2]) 264 | assert_equal(3, result[3]) 265 | assert_equal(3, #result) 266 | local result = assert(json.decode('call(1, , 3, )', options_notrailing)) 267 | result = result.parameters 268 | assert_equal(1, result[1]) 269 | assert_equal(json.util.undefined, result[2]) 270 | assert_equal(3, result[3]) 271 | assert_equal(json.util.undefined, result[4]) 272 | assert_equal(4, #result) 273 | end 274 | -------------------------------------------------------------------------------- /tests/lunit-encoderfunc.lua: -------------------------------------------------------------------------------- 1 | local json = require("json") 2 | local lunit = require("lunit") 3 | local math = require("math") 4 | local testutil = require("testutil") 5 | local unpack = require("table").unpack or unpack 6 | 7 | local setmetatable = setmetatable 8 | 9 | if not module then 10 | _ENV = lunit.module("lunit-encoderfunc", 'seeall') 11 | else 12 | module("lunit-encoderfunc", lunit.testcase, package.seeall) 13 | end 14 | 15 | local function build_call(name, parameters) 16 | return json.util.buildCall(name, unpack(parameters, parameters.n)) 17 | end 18 | 19 | function test_param_counts() 20 | local encoder = json.encode.getEncoder() 21 | assert(encoder(build_call('noparam', {}))) 22 | assert(encoder(build_call('oneparam', {1}))) 23 | assert(encoder(build_call('multiparam', {1,2}))) 24 | end 25 | 26 | function test_output() 27 | local encoder = json.encode.getEncoder() 28 | assert_equal('b64("hello")', encoder(build_call('b64', {'hello'}))) 29 | assert_equal('add(1,2)', encoder(build_call('add', {1,2}))) 30 | assert_equal('dood([b64("hello"),add(1,2)])', 31 | encoder(build_call('dood', { { 32 | build_call('b64', {'hello'}), 33 | build_call('add', {1,2}) 34 | } }))) 35 | end 36 | -------------------------------------------------------------------------------- /tests/lunit-encoding.lua: -------------------------------------------------------------------------------- 1 | local json = require("json") 2 | local lunit = require("lunit") 3 | 4 | if not module then 5 | _ENV = lunit.module("lunit-encoding", 'seeall') 6 | else 7 | module("lunit-encoding", lunit.testcase, package.seeall) 8 | end 9 | 10 | function test_cloned_array_sibling() 11 | local obj = {} 12 | assert_pass(function() 13 | json.encode({obj, obj}) 14 | end) 15 | end 16 | 17 | function test_cloned_object_sibling() 18 | local obj = {} 19 | assert_pass(function() 20 | json.encode({x = obj, y = obj}) 21 | end) 22 | end 23 | 24 | function test_cloned_array_deep_sibling() 25 | local obj = {} 26 | assert_pass(function() 27 | json.encode({ 28 | {obj}, {obj} 29 | }) 30 | end) 31 | end 32 | 33 | function test_cloned_array_multilevel_sibling() 34 | local obj = {} 35 | assert_pass(function() 36 | json.encode({ 37 | {obj, {obj}} 38 | }) 39 | end) 40 | end 41 | 42 | function test_recursive_object() 43 | local obj = {} 44 | obj.x = obj 45 | assert_error(function() 46 | json.encode(obj) 47 | end) 48 | end 49 | 50 | function test_recursive_array() 51 | local obj = {} 52 | obj[1] = obj 53 | assert_error(function() 54 | json.encode(obj) 55 | end) 56 | end 57 | 58 | function test_custom_encode() 59 | local obj = { x = "y" } 60 | local sawX 61 | local function preProcessor(value, isObjectKey) 62 | if value == "x" then 63 | sawX = true 64 | assert_true(isObjectKey) 65 | else 66 | assert_false(isObjectKey) 67 | end 68 | return value 69 | end 70 | local encoder = json.encode.getEncoder({ 71 | preProcess = preProcessor 72 | }) 73 | assert_nil(sawX) 74 | encoder(obj) 75 | assert_true(sawX) 76 | end 77 | 78 | function test_custom_array() 79 | assert_equal("[]", json.encode(setmetatable({}, {__is_luajson_array = true}))) 80 | assert_equal("[]", json.encode(json.util.InitArray({}))) 81 | end 82 | 83 | function test_undefined() 84 | assert_equal("[undefined]", json.encode({ json.util.undefined })) 85 | end 86 | 87 | function test_unknown() 88 | assert_error("Expected attempting to encode an unregistered function to fail", function() 89 | json.encode({ function() end }) 90 | end) 91 | end 92 | -------------------------------------------------------------------------------- /tests/lunit-nothrow-decode.lua: -------------------------------------------------------------------------------- 1 | local json = require("json") 2 | local lunit = require("lunit") 3 | 4 | -- Test module for handling the simple decoding that behaves more like expected 5 | if not module then 6 | _ENV = lunit.module("lunit-nothrow-decode", 'seeall') 7 | else 8 | module("lunit-nothrow-decode", lunit.testcase, package.seeall) 9 | end 10 | 11 | function test_decode_nothrow_bad_data() 12 | assert_nil((json.decode('x', {nothrow = true}))) 13 | assert_nil((json.decode('{x:x}', {nothrow = true}))) 14 | assert_nil((json.decode('[x:x]', {nothrow = true}))) 15 | assert_nil((json.decode('[1.fg]', {nothrow = true}))) 16 | assert_nil((json.decode('["\\xzz"]', {nothrow = true}))) 17 | end 18 | 19 | function test_decode_nothrow_ok_data() 20 | assert_not_nil((json.decode('"x"', {nothrow = true}))) 21 | assert_not_nil((json.decode('{x:"x"}', {nothrow = true}))) 22 | assert_not_nil((json.decode('["x"]', {nothrow = true}))) 23 | assert_not_nil((json.decode('[1.0]', {nothrow = true}))) 24 | assert_not_nil((json.decode('["\\u00FF"]', {nothrow = true}))) 25 | end 26 | 27 | -------------------------------------------------------------------------------- /tests/lunit-numbers.lua: -------------------------------------------------------------------------------- 1 | local json = require("json") 2 | local lunit = require("lunit") 3 | local math = require("math") 4 | local testutil = require("testutil") 5 | local string = require("string") 6 | 7 | local encode = json.encode 8 | -- DECODE NOT 'local' due to requirement for testutil to access it 9 | decode = json.decode.getDecoder(false) 10 | 11 | local TEST_ENV 12 | if not module then 13 | _ENV = lunit.module("lunit-numbers", 'seeall') 14 | TEST_ENV = _ENV 15 | else 16 | module("lunit-numbers", lunit.testcase, package.seeall) 17 | TEST_ENV = _M 18 | end 19 | 20 | function setup() 21 | -- Ensure that the decoder is reset 22 | _G["decode"] = json.decode.getDecoder(false) 23 | end 24 | 25 | local function is_near(expect, received) 26 | local pctDiff 27 | if expect == received then 28 | pctDiff = 0 29 | else 30 | pctDiff = math.abs(1 - expect / received) 31 | end 32 | if pctDiff < 0.000001 then 33 | return true 34 | else 35 | return false, ("expected '%s' but was '%s' .. '%s'%% apart"):format(expect, received, pctDiff * 100) 36 | end 37 | end 38 | local function assert_near(expect, received) 39 | assert(is_near(expect, received)) 40 | end 41 | local function test_simple(num) 42 | assert_near(num, decode(tostring(num))) 43 | end 44 | local function test_simple_w_encode(num) 45 | assert_near(num, decode(encode(num))) 46 | end 47 | local function test_scientific(num) 48 | assert_near(num, decode(string.format('%e', num))) 49 | assert_near(num, decode(string.format('%E', num))) 50 | end 51 | local function test_scientific_denied(num) 52 | local decode = json.decode.getDecoder({ number = { exp = false } }) 53 | assert_error_match("Exponent-denied error did not match", "Exponents.*denied", function() 54 | decode(string.format('%e', num)) 55 | end) 56 | assert_error_match("Exponent-denied error did not match", "Exponents.*denied", function() 57 | decode(string.format('%E', num)) 58 | end) 59 | end 60 | local numbers = { 61 | 0, 1, -1, math.pi, -math.pi 62 | } 63 | math.randomseed(0xDEADBEEF) 64 | local pow = math.pow or load("return function(a, b) return a ^ b end")() 65 | -- Add sequence of numbers at low/high end of value-set 66 | for i = -300,300,60 do 67 | numbers[#numbers + 1] = math.random() * pow(10, i) 68 | numbers[#numbers + 1] = -math.random() * pow(10, i) 69 | end 70 | 71 | local function get_number_tester(f) 72 | return function () 73 | for _, v in ipairs(numbers) do 74 | f(v) 75 | end 76 | end 77 | end 78 | 79 | local function test_fraction(num) 80 | assert_near(num, decode(string.format("%f", num))) 81 | end 82 | local function test_fraction_denied(num) 83 | local decode = json.decode.getDecoder({ number = { frac = false } }) 84 | local formatted = string.format('%f', num) 85 | assert_error_match("Fraction-denied error did not match for " .. formatted, "Fractions.*denied", function() 86 | decode(formatted) 87 | end) 88 | end 89 | local function get_number_fraction_tester(f) 90 | return function () 91 | for _, v in ipairs(numbers) do 92 | -- Fractional portion must be present 93 | local formatted = string.format("%f", v) 94 | -- San check that the formatted value is near the desired value 95 | if nil ~= formatted:find("%.") and is_near(v, tonumber(formatted)) then 96 | f(v) 97 | end 98 | end 99 | end 100 | end 101 | 102 | test_simple_numbers = get_number_tester(test_simple) 103 | test_simple_numbers_w_encode = get_number_tester(test_simple_w_encode) 104 | test_simple_numbers_scientific = get_number_tester(test_scientific) 105 | test_simple_numbers_scientific_denied = get_number_tester(test_scientific_denied) 106 | test_simple_numbers_fraction_only = get_number_fraction_tester(test_fraction) 107 | test_simple_numbers_fraction_denied_only = get_number_fraction_tester(test_fraction_denied) 108 | 109 | function test_infinite_nostrict() 110 | assert_equal(math.huge, decode("Infinity")) 111 | assert_equal(math.huge, decode("infinity")) 112 | assert_equal(-math.huge, decode("-Infinity")) 113 | assert_equal(-math.huge, decode("-infinity")) 114 | end 115 | 116 | function test_nan_nostrict() 117 | local value = decode("nan") 118 | assert_true(value ~= value) 119 | local value = decode("NaN") 120 | assert_true(value ~= value) 121 | assert_equal("NaN", encode(decode("NaN"))) 122 | end 123 | 124 | function test_expression() 125 | assert_error(function() 126 | decode("1 + 2") 127 | end) 128 | end 129 | 130 | -- For strict tests, small concession must be made to allow non-array/objects as root 131 | local strict = json.util.merge({}, json.decode.strict, {initialObject = false}) 132 | local strictDecoder = json.decode.getDecoder(strict) 133 | 134 | local numberValue = {hex = true} 135 | 136 | local hex = {number = numberValue} 137 | local hexDecoder = json.decode.getDecoder(hex) 138 | 139 | function test_hex() 140 | if decode == hexDecoder then -- MUST SKIP FAIL UNTIL BETTER METHOD SETUP 141 | return 142 | end 143 | assert_error(function() 144 | decode("0x20") 145 | end) 146 | end 147 | 148 | local hexNumbers = { 149 | 0xDEADBEEF, 150 | 0xCAFEBABE, 151 | 0x00000000, 152 | 0xFFFFFFFF, 153 | 0xCE, 154 | 0x01 155 | } 156 | 157 | function test_hex_only() 158 | _G["decode"] = hexDecoder 159 | for _, v in ipairs(hexNumbers) do 160 | assert_equal(v, decode(("0x%x"):format(v))) 161 | assert_equal(v, decode(("0X%X"):format(v))) 162 | assert_equal(v, decode(("0x%X"):format(v))) 163 | assert_equal(v, decode(("0X%x"):format(v))) 164 | end 165 | end 166 | 167 | local decimal_hexes = { 168 | "0x0.1", 169 | "0x.1", 170 | "0x0e+1", 171 | "0x0E-1" 172 | } 173 | function test_no_decimal_hex_only() 174 | for _, str in ipairs(decimal_hexes) do 175 | assert_error(function() 176 | hexDecoder(str) 177 | end) 178 | end 179 | end 180 | 181 | function test_nearly_scientific_hex_only() 182 | assert_equal(0x00E1, hexDecoder("0x00e1")) 183 | end 184 | 185 | local function buildStrictDecoder(f) 186 | return testutil.buildPatchedDecoder(f, strictDecoder) 187 | end 188 | local function buildFailedStrictDecoder(f) 189 | return testutil.buildFailedPatchedDecoder(f, strictDecoder) 190 | end 191 | -- SETUP CHECKS FOR SEQUENCE OF DECODERS 192 | for k, v in pairs(TEST_ENV) do 193 | if k:match("^test_") and not k:match("_gen$") and not k:match("_only$") then 194 | if k:match("_nostrict") then 195 | TEST_ENV[k .. "_strict_gen"] = buildFailedStrictDecoder(v) 196 | else 197 | TEST_ENV[k .. "_strict_gen"] = buildStrictDecoder(v) 198 | end 199 | TEST_ENV[k .. "_hex_gen"] = testutil.buildPatchedDecoder(v, hexDecoder) 200 | end 201 | end 202 | -------------------------------------------------------------------------------- /tests/lunit-simple-decode.lua: -------------------------------------------------------------------------------- 1 | local json = require("json") 2 | local lunit = require("lunit") 3 | 4 | -- Test module for handling the simple decoding that behaves more like expected 5 | if not module then 6 | _ENV = lunit.module("lunit-simple-decode", 'seeall') 7 | else 8 | module("lunit-simple-decode", lunit.testcase, package.seeall) 9 | end 10 | 11 | function test_decode_simple_undefined() 12 | assert_nil(json.decode('undefined', json.decode.simple)) 13 | end 14 | function test_decode_default_undefined() 15 | assert_equal(json.util.undefined, json.decode('undefined')) 16 | end 17 | 18 | function test_decode_simple_null() 19 | assert_nil(json.decode('null', json.decode.simple)) 20 | end 21 | 22 | function test_decode_default_null() 23 | assert_equal(json.util.null, json.decode('null')) 24 | end 25 | 26 | function test_decode_array_simple_with_only_null() 27 | local result = assert(json.decode('[null]', json.decode.simple)) 28 | assert_nil(result[1]) 29 | assert_equal(1, result.n) 30 | end 31 | 32 | function test_decode_array_default_with_only_null() 33 | local result = assert(json.decode('[null]')) 34 | assert_equal(json.util.null, result[1]) 35 | assert_equal(1, #result) 36 | end 37 | 38 | function test_decode_array_simple_with_null() 39 | local result = assert(json.decode('[1, null, 3]', json.decode.simple)) 40 | assert_equal(1, result[1]) 41 | assert_nil(result[2]) 42 | assert_equal(3, result[3]) 43 | assert_equal(3, result.n) 44 | end 45 | 46 | function test_decode_array_default_with_null() 47 | local result = assert(json.decode('[1, null, 3]')) 48 | assert_equal(1, result[1]) 49 | assert_equal(json.util.null, result[2]) 50 | assert_equal(3, result[3]) 51 | assert_equal(3, #result) 52 | end 53 | 54 | function test_decode_small_array_simple_with_trailing_null() 55 | local result = assert(json.decode('[1, null]', json.decode.simple)) 56 | assert_equal(1, result[1]) 57 | assert_nil(result[2]) 58 | assert_equal(2, result.n) 59 | end 60 | 61 | function test_decode_small_array_default_with_trailing_null() 62 | local result = assert(json.decode('[1, null]')) 63 | assert_equal(1, result[1]) 64 | assert_equal(json.util.null, result[2]) 65 | assert_equal(2, #result) 66 | end 67 | 68 | 69 | function test_decode_small_array_simple_with_trailing_null() 70 | local result = assert(json.decode('[1, null]', json.decode.simple)) 71 | assert_equal(1, result[1]) 72 | assert_nil(result[2]) 73 | assert_equal(2, result.n) 74 | end 75 | 76 | function test_decode_small_array_default_with_trailing_null() 77 | local result = assert(json.decode('[1, null]')) 78 | assert_equal(1, result[1]) 79 | assert_equal(json.util.null, result[2]) 80 | assert_equal(2, #result) 81 | end 82 | 83 | 84 | function test_decode_array_simple_with_trailing_null() 85 | local result = assert(json.decode('[1, null, 3, null]', json.decode.simple)) 86 | assert_equal(1, result[1]) 87 | assert_nil(result[2]) 88 | assert_equal(3, result[3]) 89 | assert_nil(result[4]) 90 | assert_equal(4, result.n) 91 | end 92 | 93 | function test_decode_array_default_with_trailing_null() 94 | local result = assert(json.decode('[1, null, 3, null]')) 95 | assert_equal(1, result[1]) 96 | assert_equal(json.util.null, result[2]) 97 | assert_equal(3, result[3]) 98 | assert_equal(json.util.null, result[4]) 99 | assert_equal(4, #result) 100 | end 101 | 102 | 103 | function test_decode_object_simple_with_null() 104 | local result = assert(json.decode('{x: null}', json.decode.simple)) 105 | assert_nil(result.x) 106 | assert_nil(next(result)) 107 | end 108 | 109 | function test_decode_object_default_with_null() 110 | local result = assert(json.decode('{x: null}')) 111 | assert_equal(json.util.null, result.x) 112 | assert_not_nil(next(result)) 113 | end 114 | 115 | function test_decode_object_with_stringized_numeric_keys_default() 116 | local result = assert(json.decode('{"1": "one"}')) 117 | assert_equal("one", result["1"]) 118 | assert_equal(nil, result[1]) 119 | end 120 | 121 | function test_decode_object_with_stringized_numeric_keys_force_numeric() 122 | local result = assert( 123 | json.decode( 124 | '{"1": "one"}', 125 | { object = { setObjectKey = assert(json.decode.util.setObjectKeyForceNumber) } } 126 | ) 127 | ) 128 | assert_equal(nil, result["1"]) 129 | assert_equal("one", result[1]) 130 | end 131 | -------------------------------------------------------------------------------- /tests/lunit-strings.lua: -------------------------------------------------------------------------------- 1 | local json = require("json") 2 | local lunit = require("lunit") 3 | local testutil = require("testutil") 4 | local string= require("string") 5 | 6 | local encode = json.encode 7 | -- DECODE NOT 'local' due to requirement for testutil to access it 8 | decode = json.decode.getDecoder(false) 9 | 10 | local error = error 11 | 12 | if not module then 13 | _ENV = lunit.module("lunit-strings", 'seeall') 14 | else 15 | module("lunit-strings", lunit.testcase, package.seeall) 16 | end 17 | 18 | local function assert_table_equal(expect, t) 19 | if type(expect) ~= 'table' then 20 | return assert_equal(expect, t) 21 | end 22 | for k,v in pairs(expect) do 23 | if type(k) ~= 'string' and type(k) ~= 'number' and type(k) ~= 'boolean' then 24 | error("INVALID expected table key") 25 | end 26 | local found = t[k] 27 | if found == nil then 28 | fail(tostring(k) .. " not found but expected") 29 | end 30 | assert_table_equal(v, t[k]) 31 | end 32 | for k,v in pairs(t) do 33 | if nil == expect[k] then 34 | fail(tostring(k) .. " found but not expected") 35 | end 36 | end 37 | end 38 | 39 | function setup() 40 | -- Ensure that the decoder is reset 41 | _G["decode"] = json.decode.getDecoder(false) 42 | end 43 | 44 | function test_strict_quotes() 45 | local opts = { 46 | strings = { 47 | strict_quotes = true 48 | } 49 | } 50 | assert_error(function() 51 | local decoder = json.decode.getDecoder(opts) 52 | decoder("'hello'") 53 | end) 54 | opts.strings.strict_quotes = false 55 | assert_equal("hello", json.decode.getDecoder(opts)("'hello'")) 56 | -- Quote test 57 | assert_equal("he'\"llo'", json.decode.getDecoder(opts)("'he\\'\"llo\\''")) 58 | 59 | end 60 | 61 | local utf16_matches = { 62 | -- 1-byte 63 | { '"\\u0000"', string.char(0x00) }, 64 | { '"\\u007F"', string.char(0x7F) }, 65 | -- 2-byte 66 | { '"\\u0080"', string.char(0xC2, 0x80) }, 67 | { '"\\u00A2"', string.char(0xC2, 0xA2) }, 68 | { '"\\u07FF"', string.char(0xDF, 0xBF) }, 69 | -- 3-byte 70 | { '"\\u0800"', string.char(0xE0, 0xA0, 0x80) }, 71 | { '"\\u20AC"', string.char(0xE2, 0x82, 0xAC) }, 72 | { '"\\uFEFF"', string.char(0xEF, 0xBB, 0xBF) }, 73 | { '"\\uFFFF"', string.char(0xEF, 0xBF, 0xBF) }, 74 | -- 4-byte - currently not handled 75 | --{ '"\\uD800\\uDC00"', string.char(0xF0, 0x90, 0x80, 0x80) }, 76 | --{ '"\\uDBFF\\uDFFF"', string.char(0xF4, 0x8F, 0xBF, 0xBF) } 77 | 78 | } 79 | 80 | function test_utf16_decode() 81 | for i, v in ipairs(utf16_matches) do 82 | -- Test that the default \u decoder outputs UTF8 83 | local num = tostring(i) .. ' ' 84 | assert_equal(num .. v[2], num .. json.decode(v[1])) 85 | end 86 | end 87 | 88 | local BOM = string.char(0xEF, 0xBB, 0xBF) 89 | -- BOM skipping tests - here due to relation to UTF8/16 90 | local BOM_skip_tests = { 91 | { BOM .. '"x"', "x" }, 92 | { BOM .. '["\\uFFFF",true]', { string.char(0xEF, 0xBF, 0xBF), true } }, 93 | -- Other uses of unicode spaces 94 | } 95 | 96 | function test_bom_skip() 97 | for i,v in ipairs(BOM_skip_tests) do 98 | assert_table_equal(v[2], json.decode(v[1])) 99 | end 100 | end 101 | 102 | -- Unicode whitespace codepoints gleaned from unicode.org 103 | local WHITESPACES = { 104 | "\\u0009", -- \t 105 | "\\u000A", -- \n 106 | "\\u000B", -- \v 107 | "\\u000C", -- \f 108 | "\\u000D", -- \r 109 | "\\u0020", -- space 110 | "\\u0085", 111 | "\\u00A0", 112 | "\\u1680", 113 | "\\u180E", 114 | "\\u2000", 115 | "\\u2001", 116 | "\\u2002", 117 | "\\u2003", 118 | "\\u2004", 119 | "\\u2005", 120 | "\\u2006", 121 | "\\u2007", 122 | "\\u2008", 123 | "\\u2009", 124 | "\\u200A", 125 | "\\u200B", -- addition, zero-width space 126 | "\\u2028", 127 | "\\u2029", 128 | "\\u202F", 129 | "\\u205F", 130 | "\\u3000", 131 | "\\uFEFF" -- Zero-width non-breaking space (BOM) 132 | } 133 | 134 | local inject_ws_values = { 135 | "%WS%true", 136 | " %WS%'the%WS blob' %WS%", 137 | "%WS%{ key: %WS%\"valueMan\",%WS% key2:%WS%4.4}", 138 | "%WS%false%WS%" 139 | } 140 | function test_whitespace_ignore() 141 | for _, ws in ipairs(WHITESPACES) do 142 | ws = json.decode('"' .. ws .. '"') 143 | for _, v in ipairs(inject_ws_values) do 144 | v = v:gsub("%%WS%%", ws) 145 | assert_true(nil ~= json.decode(v)) 146 | end 147 | end 148 | end 149 | 150 | function test_u_encoding() 151 | local encoder = json.encode.getEncoder() 152 | local decoder = json.decode.getDecoder() 153 | for i = 0, 255 do 154 | local char = string.char(i) 155 | assert_equal(char, decoder(encoder(char))) 156 | end 157 | end 158 | 159 | function test_x_encoding() 160 | local encoder = json.encode.getEncoder({ strings = { xEncode = true } }) 161 | local decoder = json.decode.getDecoder() 162 | for i = 0, 255 do 163 | local char = string.char(i) 164 | assert_equal(char, decoder(encoder(char))) 165 | end 166 | end 167 | 168 | local multibyte_encoding_values = { 169 | -- 2-byte 170 | { '"\\u0080"', string.char(0xC2, 0x80) }, 171 | { '"\\u00A2"', string.char(0xC2, 0xA2) }, 172 | { '"\\u07FF"', string.char(0xDF, 0xBF) }, 173 | -- 3-byte 174 | { '"\\u0800"', string.char(0xE0, 0xA0, 0x80) }, 175 | { '"\\u20AC"', string.char(0xE2, 0x82, 0xAC) }, 176 | { '"\\uFEFF"', string.char(0xEF, 0xBB, 0xBF) }, 177 | { '"\\uFFFF"', string.char(0xEF, 0xBF, 0xBF) }, 178 | -- 4-byte (surrogate pairs) 179 | { '"\\uD800\\uDC00"', string.char(0xF0, 0x90, 0x80, 0x80) }, 180 | { '"\\uDBFF\\uDFFF"', string.char(0xF4, 0x8F, 0xBF, 0xBF) } 181 | } 182 | 183 | function test_custom_encoding() 184 | local function processor(s) 185 | return require("utf8_processor").process(s) 186 | end 187 | local encoder = json.encode.getEncoder({ 188 | strings = { 189 | processor = processor 190 | } 191 | }) 192 | for i, v in ipairs(multibyte_encoding_values) do 193 | local encoded = encoder(v[2]) 194 | assert_equal(v[1], encoded, "Failed to encode value using custom encoder") 195 | end 196 | end 197 | 198 | function test_strict_decoding() 199 | local encoder = json.encode.getEncoder(json.encode.strict) 200 | local decoder = json.decode.getDecoder(json.decode.strict) 201 | for i = 0, 255 do 202 | local char = string.char(i) 203 | -- Must wrap character in array due to decoder strict-ness 204 | assert_equal(char, decoder(encoder({char}))[1]) 205 | end 206 | end 207 | -------------------------------------------------------------------------------- /tests/lunit-tests.lua: -------------------------------------------------------------------------------- 1 | local json = require("json") 2 | local lunit = require("lunit") 3 | local testutil = require("testutil") 4 | local lpeg = require("lpeg") 5 | -- DECODE NOT 'local' due to requirement for testutil to access it 6 | decode = json.decode.getDecoder(false) 7 | 8 | local TEST_ENV 9 | if not module then 10 | _ENV = lunit.module("lunit-tests", 'seeall') 11 | TEST_ENV = _ENV 12 | else 13 | module("lunit-tests", lunit.testcase, package.seeall) 14 | TEST_ENV = _M 15 | end 16 | 17 | function setup() 18 | _G["decode"] = json.decode.getDecoder(false) 19 | end 20 | 21 | function test_array_empty() 22 | local ret = assert_table(decode("[]")) 23 | assert_equal(0, #ret) 24 | assert_nil(next(ret)) 25 | end 26 | 27 | function test_array_trailComma_nostrict() 28 | local ret = assert_table(decode("[true,]")) 29 | assert_equal(true, ret[1]) 30 | assert_nil(next(ret, 1)) 31 | assert_equal(1, #ret) 32 | end 33 | 34 | function test_array_innerComma() 35 | assert_error(function() 36 | decode("[true,,true]") 37 | end) 38 | end 39 | 40 | function test_preprocess() 41 | assert_equal('"Hello"', json.encode(1, {preProcess = function() return "Hello" end})) 42 | assert_equal('-1', json.encode(1, {preProcess = function(x) return -x end})) 43 | assert_equal('-Infinity', json.encode(1/0, {preProcess = function(x) return -x end})) 44 | end 45 | 46 | function test_additionalEscapes_only() 47 | -- Test that additionalEscapes is processed on its own - side-stepping normal processing 48 | assert_equal("Hello\\?", json.decode([["\S"]], { strings = { additionalEscapes = lpeg.C(lpeg.P("S")) / "Hello\\?" } })) 49 | -- Test that additionalEscapes overrides any builtin handling 50 | assert_equal("Hello\\?", json.decode([["\n"]], { strings = { additionalEscapes = lpeg.C(lpeg.P("n")) / "Hello\\?" } })) 51 | end 52 | 53 | local strictDecoder = json.decode.getDecoder(true) 54 | 55 | local function buildStrictDecoder(f) 56 | return testutil.buildPatchedDecoder(f, strictDecoder) 57 | end 58 | local function buildFailedStrictDecoder(f) 59 | return testutil.buildFailedPatchedDecoder(f, strictDecoder) 60 | end 61 | -- SETUP CHECKS FOR SEQUENCE OF DECODERS 62 | for k, v in pairs(TEST_ENV) do 63 | if k:match("^test_") and not k:match("_gen$") and not k:match("_only$") then 64 | if k:match("_nostrict") then 65 | TEST_ENV[k .. "_strict_gen"] = buildFailedStrictDecoder(v) 66 | else 67 | TEST_ENV[k .. "_strict_gen"] = buildStrictDecoder(v) 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /tests/regressionTest.lua: -------------------------------------------------------------------------------- 1 | -- Additional path that may be required 2 | local json = require("json") 3 | local io = require("io") 4 | local os = require("os") 5 | 6 | local lfs = require("lfs") 7 | 8 | local success = true 9 | 10 | local function getFileData(fileName) 11 | local f = io.open(fileName, 'rb') 12 | if not f then return end 13 | local data = f:read('*a') 14 | f:close() 15 | return data 16 | end 17 | 18 | local function putTempData(data) 19 | local name = os.tmpname() 20 | local f = assert(io.open(name, 'wb')) 21 | f:write(data) 22 | f:close() 23 | return name 24 | end 25 | 26 | -- Ensure that the encoder/decoder can round-trip valid JSON 27 | local function RoundTripTest(parseFunc, encodeFunc, jsonData, luaData, fullRoundTrip, failRoundTrip) 28 | local success, dataString = pcall(encodeFunc, luaData) 29 | if failRoundTrip then 30 | assert(not success, "Round trip encoding test result not as expected") 31 | return true 32 | else 33 | assert(success, "Couldn't encode the lua data..." .. tostring(dataString)) 34 | end 35 | local success, result = pcall(parseFunc, dataString) 36 | if not success then 37 | print("Could not parse the generated JSON of (", luaData) 38 | print("GENERATED: [[" .. dataString .. "]]") 39 | print("DATA STORED IN: ", putTempData(dataString)) 40 | return 41 | end 42 | if fullRoundTrip then 43 | -- Ensure that whitespace is trimmed off ends 44 | dataString = dataString:match("^[%s]*(.-)[%s]*$") 45 | jsonData = jsonData:match("^[%s]*(.-)[%s]*$") 46 | if dataString ~= jsonData then 47 | print("Encoded values do not match") 48 | print("ORIGINAL: << " .. jsonData .. " >>") 49 | print("RE-ENCOD: << " .. dataString .. " >>") 50 | return 51 | end 52 | end 53 | return true 54 | end 55 | 56 | local function testFile(fileName, parseFunc, encodeFunc, expectSuccess, fullRoundTrip, failRoundTrip) 57 | local data = getFileData(fileName) 58 | if not data then return end 59 | io.write(".") 60 | local succeed, result = pcall(parseFunc, data) 61 | if expectSuccess ~= succeed then 62 | print("Wrongly " .. (expectSuccess and "Failed" or "Succeeded") .. " on : " .. fileName .. "(" .. tostring(result) .. ")") 63 | success = false 64 | elseif succeed then 65 | if not RoundTripTest(parseFunc, encodeFunc, data, result, fullRoundTrip, failRoundTrip) then 66 | print("FAILED TO ROUND TRIP: " .. fileName) 67 | success = false 68 | end 69 | end 70 | end 71 | 72 | local function testDirectories(parseFunc, encodeFunc, directories, ...) 73 | if not directories then return end 74 | for _,directory in ipairs(directories) do 75 | if lfs.attributes(directory, 'mode') == 'directory' then 76 | for f in lfs.dir(directory) do 77 | testFile(directory .. "/" .. f, parseFunc, encodeFunc, ...) 78 | end 79 | end 80 | end 81 | io.write("\n") 82 | end 83 | 84 | local function TestParser(parseFunc, encodeFunc, successNames, failNames, roundTripNames) 85 | testDirectories(parseFunc, encodeFunc, successNames, true, false) 86 | testDirectories(parseFunc, encodeFunc, failNames, false, false) 87 | testDirectories(parseFunc, encodeFunc, roundTripNames, true, true) 88 | end 89 | print("Testing lax/fast mode:") 90 | TestParser(json.decode.getDecoder(), json.encode.getEncoder(), {"test/pass","test/fail_strict"}, {"test/fail_all"},{"test/roundtrip","test/roundtrip_lax"}) 91 | 92 | print("Testing (mostly) strict mode:") 93 | local strict = json.util.merge({}, json.decode.strict, { 94 | number = { 95 | nan = false, 96 | inf = true, 97 | strict = true 98 | } 99 | }) 100 | local strict_encode = json.util.merge({}, json.encode.strict, { 101 | number = { 102 | nan = false, 103 | inf = true, 104 | strict = true 105 | } 106 | }) 107 | TestParser(json.decode.getDecoder(strict), json.encode.getEncoder(strict_encode), {"test/pass"}, {"test/fail_strict","test/fail_all"}, {"test/roundtrip"}) 108 | 109 | print("Testing (mostly) strict encoder with non-strict decodings") 110 | testDirectories(json.decode.getDecoder(), json.encode.getEncoder(json.encode.strict), {"test/fail_strict_encode"}, true, true, true) 111 | 112 | if not success then 113 | os.exit(1) 114 | end 115 | -------------------------------------------------------------------------------- /tests/test.lua: -------------------------------------------------------------------------------- 1 | -- Additional path that may be required 2 | require("json") 3 | 4 | local testStrings = { 5 | [[{1:[1213.3e12, 123 , 123, "hello", [12, 2], {1:true /*test*/}]}]], 6 | [[{"username":"demo1","message":null,"password":""}]], 7 | [[{"challenge":"b64d-fnNQ6bRZ7CYiNIKwmdHoNgl9JR9MIYtzjBhpQzYXCFrgARt9mNmgUuO7FoODGr1NieT9yTeB2SLztGkvIA4NXmN9Bi27hqx1ybJIQq6S2L-AjQ3VTDClSmCsYFPOm9EMVZDZ0jhBX1fXw3o9VYj1j9KzSY5VCSAzGqYo-cBPY\n.b64","cert":"b64MIIGyjCCBbKgAwIBAgIKFAC1ZgAAAAAUYzANBgkqhkiG9w0BAQUFADBZMRUwEwYKCZImiZPyLGQBGRYFbG9tp8uQuFjWGS_KxTHXz9vkLNFjOoZY2bOwzsdEpshuYSdvX-9bRvHTQcoMNz8Q9nXG1aMl5x1nbV5byQNTCJlz4gzMJeNfeKGcipdCj7B6e_VpF-n2P-dFZizUHjxMksCVZ3nTr51x3Uw\n.b64","key":"D79B30BA7954DF520B44897A6FF58919"}]], 8 | [[{"key":"D79B30BA7954DF520B44897A6FF58919"}]], 9 | [[{"val":undefined}]], 10 | [[{ 11 | "Image": { 12 | "Width": 800, 13 | "Height": 600, 14 | "Title": "View from 15th Floor", 15 | "Thumbnail": { 16 | "Url": "http://www.example.com/image/481989943", 17 | "Height": 125, 18 | "Width": "100" 19 | }, 20 | "IDs": [116, 943, 234, 38793] 21 | } 22 | }]], 23 | [[ [ 24 | { 25 | "precision": "zip", 26 | "Latitude": 37.7668, 27 | "Longitude": -122.3959, 28 | "Address": "", 29 | "City": "SAN FRANCISCO", 30 | "State": "CA", 31 | "Zip": "94107", 32 | "Country": "US" 33 | }, 34 | { 35 | "precision": "zip", 36 | "Latitude": 37.371991, 37 | "Longitude": -122.026020, 38 | "Address": "", 39 | "City": "SUNNYVALE", 40 | "State": "CA", 41 | "Zip": "94085", 42 | "Country": "US" 43 | } 44 | ] ]], 45 | [[[null,true,[1,2,3],"hello\"],[world!"] ]], 46 | [[ [{"0":"tan\\\\","model\\\\":"sedan"},{"0":"red","model":"sports"}] ]], 47 | [[ {"1":"one","2":"two","5":"five"} ]], 48 | [=[ [[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] ]=] 49 | } 50 | 51 | for i, v in ipairs(testStrings) do 52 | print("Testing: #" .. i) 53 | local dec = json.decode(v) 54 | json.util.printValue(dec, "JSONVALUE") 55 | local reenc = json.encode(dec) 56 | print("RE_ENC: ", reenc) 57 | local redec = json.decode(reenc) 58 | json.util.printValue(redec, "REDECJSONVALUE") 59 | end 60 | 61 | local testValues = { 62 | {[300] = {nil, true, 1,2,3, nil, 3}} 63 | } 64 | 65 | for _, v in ipairs(testValues) do 66 | local ret = json.encode(v) 67 | print(ret) 68 | local dec = json.decode(ret) 69 | json.util.printValue(dec, "Encoded value") 70 | print("Re-encoded", json.encode(dec)) 71 | end 72 | -------------------------------------------------------------------------------- /tests/test/fail_all/colonInArray.json: -------------------------------------------------------------------------------- 1 | ["Colon instead of comma": false] -------------------------------------------------------------------------------- /tests/test/fail_all/commaAfterValue.json: -------------------------------------------------------------------------------- 1 | ["Comma after the close"], -------------------------------------------------------------------------------- /tests/test/fail_all/doubleColon.json: -------------------------------------------------------------------------------- 1 | {"Double colon":: null} -------------------------------------------------------------------------------- /tests/test/fail_all/extraArrayClose.json: -------------------------------------------------------------------------------- 1 | ["Extra close"]] -------------------------------------------------------------------------------- /tests/test/fail_all/extraValueAfterClose.json: -------------------------------------------------------------------------------- 1 | {"Extra value after close": true} "misplaced quoted value" -------------------------------------------------------------------------------- /tests/test/fail_all/functionExpression.json: -------------------------------------------------------------------------------- 1 | {"Illegal invocation": alert()} -------------------------------------------------------------------------------- /tests/test/fail_all/hexNumber.json: -------------------------------------------------------------------------------- 1 | {"Numbers cannot be hex": 0x14} -------------------------------------------------------------------------------- /tests/test/fail_all/lineBreakInString-escaped.json: -------------------------------------------------------------------------------- 1 | ["line\ 2 | break"] -------------------------------------------------------------------------------- /tests/test/fail_all/mathExpression.json: -------------------------------------------------------------------------------- 1 | {"Illegal expression": 1 + 2} -------------------------------------------------------------------------------- /tests/test/fail_all/missingArrayValue.json: -------------------------------------------------------------------------------- 1 | [ , "<-- missing value"] -------------------------------------------------------------------------------- /tests/test/fail_all/missingColon.json: -------------------------------------------------------------------------------- 1 | {"Missing colon" null} -------------------------------------------------------------------------------- /tests/test/fail_all/missingColon2.json: -------------------------------------------------------------------------------- 1 | {"Comma instead of colon", null} -------------------------------------------------------------------------------- /tests/test/fail_all/octalEscape.json: -------------------------------------------------------------------------------- 1 | ["Illegal backslash escape: \017"] -------------------------------------------------------------------------------- /tests/test/fail_all/octalNumber.json: -------------------------------------------------------------------------------- 1 | {"Numbers cannot have leading zeroes": 013} -------------------------------------------------------------------------------- /tests/test/fail_all/tabInString-escaped.json: -------------------------------------------------------------------------------- 1 | ["tab\ character\ in\ string\ "] -------------------------------------------------------------------------------- /tests/test/fail_all/trailingDoubleComma.json: -------------------------------------------------------------------------------- 1 | ["double extra comma",,] -------------------------------------------------------------------------------- /tests/test/fail_all/unclosed_array.json: -------------------------------------------------------------------------------- 1 | ["Unclosed array" -------------------------------------------------------------------------------- /tests/test/fail_all/unknownLiteral.json: -------------------------------------------------------------------------------- 1 | ["Bad value", truth] -------------------------------------------------------------------------------- /tests/test/fail_strict/hexEscape.json: -------------------------------------------------------------------------------- 1 | ["Illegal backslash escape: \x15"] -------------------------------------------------------------------------------- /tests/test/fail_strict/lineBreakInString.json: -------------------------------------------------------------------------------- 1 | ["line 2 | break"] -------------------------------------------------------------------------------- /tests/test/fail_strict/mustBeArrayOrObject.json: -------------------------------------------------------------------------------- 1 | "A JSON payload should be an object or array, not a string." -------------------------------------------------------------------------------- /tests/test/fail_strict/singleQuoteEscape.json: -------------------------------------------------------------------------------- 1 | ["Illegal backslash escape: \'"] -------------------------------------------------------------------------------- /tests/test/fail_strict/singleQuotes.json: -------------------------------------------------------------------------------- 1 | ['single quote'] -------------------------------------------------------------------------------- /tests/test/fail_strict/tabInString.json: -------------------------------------------------------------------------------- 1 | ["tab character in string "] 2 | -------------------------------------------------------------------------------- /tests/test/fail_strict/trailingArrayComma.json: -------------------------------------------------------------------------------- 1 | ["extra comma",] -------------------------------------------------------------------------------- /tests/test/fail_strict/trailingObjectComma.json: -------------------------------------------------------------------------------- 1 | {"Extra comma": true,} -------------------------------------------------------------------------------- /tests/test/fail_strict/unquotedKey.json: -------------------------------------------------------------------------------- 1 | {unquoted_key: "keys must be quoted"} -------------------------------------------------------------------------------- /tests/test/fail_strict/whitespace_before_value.json: -------------------------------------------------------------------------------- 1 | 2 | "test string" -------------------------------------------------------------------------------- /tests/test/fail_strict_encode/bool.json: -------------------------------------------------------------------------------- 1 | true 2 | -------------------------------------------------------------------------------- /tests/test/fail_strict_encode/inf.json: -------------------------------------------------------------------------------- 1 | {x: Infinity} 2 | -------------------------------------------------------------------------------- /tests/test/fail_strict_encode/nan.json: -------------------------------------------------------------------------------- 1 | {x: NaN} 2 | -------------------------------------------------------------------------------- /tests/test/fail_strict_encode/null.json: -------------------------------------------------------------------------------- 1 | null 2 | -------------------------------------------------------------------------------- /tests/test/fail_strict_encode/number.json: -------------------------------------------------------------------------------- 1 | 100 2 | -------------------------------------------------------------------------------- /tests/test/fail_strict_encode/string.json: -------------------------------------------------------------------------------- 1 | "Heloo world" 2 | -------------------------------------------------------------------------------- /tests/test/fail_strict_encode/undefined.json: -------------------------------------------------------------------------------- 1 | {x: undefined} 2 | -------------------------------------------------------------------------------- /tests/test/pass/extremeNesting.json: -------------------------------------------------------------------------------- 1 | [[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]] -------------------------------------------------------------------------------- /tests/test/pass/pass1.json: -------------------------------------------------------------------------------- 1 | [ 2 | "JSON Test Pattern pass1", 3 | {"object with 1 member":["array with 1 element"]}, 4 | {}, 5 | [], 6 | -42, 7 | true, 8 | false, 9 | null, 10 | { 11 | "integer": 1234567890, 12 | "real": -9876.543210, 13 | "e": 0.123456789e-12, 14 | "E": 1.234567890E+34, 15 | "": 23456789012E666, 16 | "zero": 0, 17 | "one": 1, 18 | "space": " ", 19 | "quote": "\"", 20 | "backslash": "\\", 21 | "controls": "\b\f\n\r\t", 22 | "slash": "/ & \/", 23 | "alpha": "abcdefghijklmnopqrstuvwyz", 24 | "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", 25 | "digit": "0123456789", 26 | "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", 27 | "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", 28 | "true": true, 29 | "false": false, 30 | "null": null, 31 | "array":[ ], 32 | "object":{ }, 33 | "address": "50 St. James Street", 34 | "url": "http://www.JSON.org/", 35 | "comment": "// /* */": " ", 37 | " s p a c e d " :[1,2 , 3 38 | 39 | , 40 | 41 | 4 , 5 , 6 ,7 ], 42 | "compact": [1,2,3,4,5,6,7], 43 | "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", 44 | "quotes": "" \u0022 %22 0x22 034 "", 45 | "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" 46 | : "A key can be any string" 47 | }, 48 | 0.5 ,98.6 49 | , 50 | 99.44 51 | , 52 | 53 | 1066 54 | 55 | 56 | ,"rosebud"] -------------------------------------------------------------------------------- /tests/test/pass/pass3.json: -------------------------------------------------------------------------------- 1 | { 2 | "JSON Test Pattern pass3": { 3 | "The outermost value": "must be an object or array.", 4 | "In this test": "It is an object." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/test/pass/stringWithEscapedAndUnescapedSlash.json: -------------------------------------------------------------------------------- 1 | ["/ & \/"] 2 | -------------------------------------------------------------------------------- /tests/test/pass/whitespace_before_array.json: -------------------------------------------------------------------------------- 1 | [1,2] -------------------------------------------------------------------------------- /tests/test/pass/whitespace_before_object.json: -------------------------------------------------------------------------------- 1 | {"a":"Whitespace before object"} -------------------------------------------------------------------------------- /tests/test/roundtrip/deepArray.json: -------------------------------------------------------------------------------- 1 | [[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] -------------------------------------------------------------------------------- /tests/test/roundtrip/emptyArray.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /tests/test/roundtrip/emptyObject.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /tests/test/roundtrip/simpleArrayInObject.json: -------------------------------------------------------------------------------- 1 | {"object with 1 member":["array with 1 element"]} 2 | -------------------------------------------------------------------------------- /tests/testutil.lua: -------------------------------------------------------------------------------- 1 | local pcall, error = pcall, error 2 | 3 | local lunit = require("lunit") 4 | local assert_error = lunit.assert_error 5 | 6 | -- Allow module to alter decoder 7 | local function setDecoder(d) 8 | _G.decode = d 9 | end 10 | 11 | local function buildPatchedDecoder(f, newDecoder) 12 | return function() 13 | setDecoder(newDecoder) 14 | f() 15 | end 16 | end 17 | local function buildFailedPatchedDecoder(f, newDecoder) 18 | return function() 19 | setDecoder(newDecoder) 20 | assert_error(f) 21 | end 22 | end 23 | 24 | return { 25 | buildPatchedDecoder = buildPatchedDecoder, 26 | buildFailedPatchedDecoder = buildFailedPatchedDecoder 27 | } 28 | -------------------------------------------------------------------------------- /tests/timetrials.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Some Time Trails for the JSON4Lua package 3 | ]]-- 4 | 5 | require('json') 6 | local os = require('os') 7 | local table = require('table') 8 | local string = require("string") 9 | 10 | local skipDecode = (...) == '--skipDecode' 11 | local count = tonumber(select(2, ...) or 500) or 500 12 | local strDup = tonumber(select(3, ...) or 1) or 1 13 | local t1 = os.clock() 14 | local jstr 15 | local v 16 | for i=1,count do 17 | local t = {} 18 | for j=1,500 do 19 | t[#t + 1] = j 20 | end 21 | for j=1,500 do 22 | t[#t + 1] = string.rep("VALUE", strDup) 23 | end 24 | jstr = json.encode(t) 25 | if not skipDecode then v = json.decode(jstr) end 26 | --print(json.encode(t)) 27 | end 28 | 29 | for i = 1,count do 30 | local t = {} 31 | for j=1,500 do 32 | local m= j % 4 33 | local idx = string.rep('a'..j, strDup) 34 | if (m==0) then 35 | t[idx] = true 36 | elseif m==1 then 37 | t[idx] = json.util.null 38 | elseif m==2 then 39 | t[idx] = j 40 | else 41 | t[idx] = string.char(j % 0xFF) 42 | end 43 | end 44 | jstr = json.encode(t) 45 | if not skipDecode then v = json.decode(jstr) end 46 | end 47 | 48 | print (jstr) 49 | --print(type(t1)) 50 | local t2 = os.clock() 51 | 52 | print ("Elapsed time=" .. os.difftime(t2,t1) .. "s") 53 | -------------------------------------------------------------------------------- /tests/utf8_processor.lua: -------------------------------------------------------------------------------- 1 | local lpeg = require("lpeg") 2 | 3 | local string = string 4 | 5 | local floor = require("math").floor 6 | 7 | local _ENV = nil 8 | 9 | local function encode_utf(codepoint) 10 | if codepoint > 0x10FFFF then 11 | error("Codepoint > 10FFFF cannot be encoded") 12 | elseif codepoint > 0xFFFF then 13 | -- Surrogate pair needed 14 | codepoint = codepoint - 0x10000 15 | local first, second = floor(codepoint / 0x0400) + 0xD800, codepoint % 0x0400 + 0xDC00 16 | return ("\\u%.4X\\u%.4X"):format(first, second) 17 | else 18 | return ("\\u%.4X"):format(codepoint) 19 | end 20 | end 21 | 22 | -- decode a two-byte UTF-8 sequence 23 | local function f2 (s) 24 | local c1, c2 = string.byte(s, 1, 2) 25 | return encode_utf(c1 * 64 + c2 - 12416) 26 | end 27 | 28 | -- decode a three-byte UTF-8 sequence 29 | local function f3 (s) 30 | local c1, c2, c3 = string.byte(s, 1, 3) 31 | return encode_utf((c1 * 64 + c2) * 64 + c3 - 925824) 32 | end 33 | 34 | -- decode a four-byte UTF-8 sequence 35 | local function f4 (s) 36 | local c1, c2, c3, c4 = string.byte(s, 1, 4) 37 | return encode_utf(((c1 * 64 + c2) * 64 + c3) * 64 + c4 - 63447168) 38 | end 39 | 40 | local cont = lpeg.R("\128\191") -- continuation byte 41 | 42 | local utf8 = lpeg.R("\0\127") -- Do nothing here 43 | + lpeg.R("\194\223") * cont / f2 44 | + lpeg.R("\224\239") * cont * cont / f3 45 | + lpeg.R("\240\244") * cont * cont * cont / f4 46 | 47 | local utf8_decode_pattern = lpeg.Cs(utf8^0) * -1 48 | 49 | 50 | local function process(s) 51 | return utf8_decode_pattern:match(s) 52 | end 53 | 54 | return { 55 | process = process 56 | } 57 | -------------------------------------------------------------------------------- /util/createRock.lua: -------------------------------------------------------------------------------- 1 | local io = require("io") 2 | 3 | local version = assert((...), "Requires rock version on command-line") 4 | 5 | local template = [=[ 6 | package = "luajson" 7 | version = %VERSION% 8 | source = { 9 | url = "git://github.com/harningt/luajson.git", 10 | tag = %TAG_VERSION% 11 | } 12 | description = { 13 | summary = "customizable JSON decoder/encoder", 14 | detailed = [[ 15 | LuaJSON is a customizable JSON decoder/encoder using 16 | LPEG for parsing. 17 | ]], 18 | homepage = "http://github.com/harningt/luajson", 19 | maintainer = "Thomas Harning ", 20 | license = "MIT/X11" 21 | } 22 | dependencies = { 23 | "lua >= 5.1", 24 | "lpeg >= 0.8.1" 25 | } 26 | build = { 27 | type = "module", 28 | modules = { 29 | %MODULES% 30 | } 31 | } 32 | ]=] 33 | 34 | local in_modules = io.popen("find lua -type f -name '*.lua' -not -iname '.*' | sort", "r") 35 | local modules = in_modules:read("*a") 36 | in_modules:close() 37 | 38 | modules = modules:gsub("lua/([^\n]*)%.lua", function(module) 39 | return "\t\t[" .. ("%q"):format(module:gsub("/",".")) .. "] = " .. ("%q"):format("lua/" .. module .. ".lua") .. "," 40 | end) 41 | tag_version = version:match("^(.*)[-]") 42 | local out = template:gsub("%%(.-)%%", { 43 | VERSION = ("%q"):format(version), 44 | TAG_VERSION = ("%q"):format(tag_version), 45 | MODULES = modules 46 | }) 47 | print(out) 48 | -------------------------------------------------------------------------------- /util/prepareNextRelease.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | local io = require("io") 3 | local from, tover = ... 4 | if not from or not tover then 5 | print("Format: prepareNextRelease.lua ") 6 | return 7 | end 8 | 9 | local f = assert(io.open("docs/ReleaseNotes-" .. tover .. ".txt", "w")) 10 | local headLine = "luajson v" .. tover .. " Release Notes" 11 | f:write(headLine, "\n", ("="):rep(#headLine), "\n\n") 12 | 13 | f:write([[ 14 | User Visible Changes 15 | -------------------- 16 | 17 | Plans for next release 18 | ---------------------- 19 | 20 | ]]) 21 | local tailLine = "Updates since " .. from 22 | f:write(tailLine, "\n", ("="):rep(#tailLine), "\n\n") 23 | 24 | local data = assert(io.popen("git shortlog " .. from .. "..HEAD | util/processShortlog.lua", "r")) 25 | local tail = data:read("*a") 26 | data:close() 27 | 28 | f:write(tail) 29 | f:close() 30 | -------------------------------------------------------------------------------- /util/processShortlog.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | local io = require("io") 3 | local table = require("table") 4 | local authors = {} 5 | 6 | local currentAuthor 7 | 8 | local function pushValue(t, group, value) 9 | local items = t[group] or {} 10 | t[group] = items 11 | items[#items + 1] = value 12 | end 13 | for line in io.lines() do 14 | local author = line:match("^(.- %(%d*%):)$") 15 | line = line:match("^%s*(.-)%s*$") 16 | if author then 17 | currentAuthor = authors[author] or {} 18 | authors[author] = currentAuthor 19 | elseif currentAuthor and #line > 0 then 20 | local msg = line:gsub("^%s*","") 21 | local group, data = msg:match("^([^:]*[%s]*:)[%s]*(.*)$") 22 | if not group then 23 | pushValue(currentAuthor, "-ungrouped-", msg) 24 | else 25 | pushValue(currentAuthor, group, data) 26 | end 27 | end 28 | end 29 | for author, groups in pairs(authors) do 30 | print(author) 31 | -- SORT GROUPS 32 | local sorted = {} 33 | for group, groupValues in pairs(groups) do 34 | sorted[#sorted + 1] = {group, groupValues} 35 | end 36 | table.sort(sorted, function(a,b) return a[1] < b[1] end) 37 | for _, gv in ipairs(sorted) do 38 | local group, groupValues = unpack(gv) 39 | print("\t" .. group) 40 | for _, entry in ipairs(groupValues) do 41 | print("\t\t" .. entry) 42 | end 43 | end 44 | end 45 | --------------------------------------------------------------------------------