├── .gitignore ├── assets ├── pass4_result.hjson ├── pass4_result.json ├── comments2_result.hjson ├── comments2_result.json ├── comments6_result.hjson ├── comments6_result.json ├── failJSON29_test.json ├── failJSON30_test.json ├── pass4_test.json ├── sorted │ ├── pass4_result.json │ ├── comments2_result.hjson │ ├── comments2_result.json │ ├── comments6_result.hjson │ ├── comments6_result.json │ ├── pass4_result.hjson │ ├── strings4_result.hjson │ ├── strings4_result.json │ ├── empty_result.hjson │ ├── empty_result.json │ ├── passSingle_result.hjson │ ├── passSingle_result.json │ ├── int64_result.hjson │ ├── int64_result.json │ ├── comments3_result.hjson │ ├── comments3_result.json │ ├── mltabs_result.json │ ├── strings3_result.json │ ├── comments7_result.hjson │ ├── pass6_result.hjson │ ├── root_result.hjson │ ├── comments7_result.json │ ├── pass5_result.hjson │ ├── pass5_result.json │ ├── root_result.json │ ├── pass6_result.json │ ├── charset2_result.hjson │ ├── mltabs_result.hjson │ ├── charset2_result.json │ ├── trail_result.hjson │ ├── trail_result.json │ ├── strings3_result.hjson │ ├── oa_result.hjson │ ├── oa_result.json │ ├── pass3_result.hjson │ ├── pass3_result.json │ ├── comments4_result.hjson │ ├── comments4_result.json │ ├── charset_result.hjson │ ├── charset_result.json │ ├── comments5_result.hjson │ ├── windowseol_result.json │ ├── windowseol_result.hjson │ ├── comments5_result.json │ ├── kan_result.hjson │ ├── comments_result.hjson │ ├── kan_result.json │ ├── keys_result.hjson │ ├── strings2_result.hjson │ ├── comments_result.json │ ├── strings2_result.json │ ├── keys_result.json │ ├── pass2_result.hjson │ ├── pass2_result.json │ ├── stringify1_result.hjson │ ├── stringify1_result.json │ ├── strings_result.hjson │ ├── strings_result.json │ ├── pass1_result.hjson │ └── pass1_result.json ├── strings4_result.hjson ├── strings4_result.json ├── strings4_test.hjson ├── comments2 │ ├── pass4_result.hjson │ ├── strings4_result.hjson │ ├── strings4_result.json │ ├── empty_result.hjson │ ├── passSingle_result.hjson │ ├── int64_result.hjson │ ├── comments2_result.hjson │ ├── comments6_result.hjson │ ├── pass5_result.hjson │ ├── comments3_result.hjson │ ├── charset2_result.hjson │ ├── mltabs_result.hjson │ ├── pass6_result.hjson │ ├── root_result.hjson │ ├── oa_result.hjson │ ├── pass3_result.hjson │ ├── trail_result.hjson │ ├── strings3_result.hjson │ ├── comments7_result.hjson │ ├── charset_result.hjson │ ├── windowseol_result.hjson │ ├── kan_result.hjson │ ├── strings2_result.hjson │ ├── keys_result.hjson │ ├── pass2_result.hjson │ ├── stringify1_result.hjson │ ├── comments4_result.hjson │ ├── comments_result.hjson │ ├── comments5_result.hjson │ ├── pass1_result.hjson │ └── strings_result.hjson ├── failJSON16_test.json ├── failJSON31_test.json ├── comments3 │ ├── pass4_result.hjson │ ├── strings4_result.hjson │ ├── strings4_result.json │ ├── empty_result.hjson │ ├── passSingle_result.hjson │ ├── int64_result.hjson │ ├── comments2_result.hjson │ ├── comments6_result.hjson │ ├── pass5_result.hjson │ ├── comments3_result.hjson │ ├── charset2_result.hjson │ ├── mltabs_result.hjson │ ├── pass6_result.hjson │ ├── root_result.hjson │ ├── oa_result.hjson │ ├── pass3_result.hjson │ ├── trail_result.hjson │ ├── comments7_result.hjson │ ├── strings3_result.hjson │ ├── charset_result.hjson │ ├── windowseol_result.hjson │ ├── kan_result.hjson │ ├── strings2_result.hjson │ ├── keys_result.hjson │ ├── pass2_result.hjson │ ├── stringify1_result.hjson │ ├── comments4_result.hjson │ ├── comments_result.hjson │ ├── comments5_result.hjson │ ├── pass1_result.hjson │ └── strings_result.hjson ├── failJSON08_test.json ├── failJSON33_test.json ├── empty_result.hjson ├── empty_test.hjson ├── failJSON02_test.json ├── failJSON23_test.json ├── failJSON28_test.json ├── empty_result.json ├── failJSON05_test.json ├── failJSON06_test.json ├── failJSON07_test.json ├── failJSON19_test.json ├── failJSON20_test.json ├── failStr9a_test.hjson ├── passSingle_result.hjson ├── passSingle_test.hjson ├── failJSON11_test.json ├── failJSON12_test.json ├── failJSON14_test.json ├── passSingle_result.json ├── failJSON15_test.json ├── failJSON17_test.json ├── failJSON21_test.json ├── failJSON22_test.json ├── int64_test.hjson ├── failJSON26_test.json ├── failJSON32_test.json ├── failKey4_test.hjson ├── int64_result.hjson ├── int64_result.json ├── comments3_result.hjson ├── comments3_result.json ├── failJSON13_test.json ├── failKey2_test.hjson ├── failKey3_test.hjson ├── failKey5_test.hjson ├── failKey1_test.hjson ├── failMLStr1_test.hjson ├── failObj2_test.hjson ├── pass2_test.json ├── comments2_test.hjson ├── failCharset1_test.hjson ├── failStr1a_test.hjson ├── failStr1b_test.hjson ├── failStr2a_test.hjson ├── failStr2b_test.hjson ├── failStr3a_test.hjson ├── failStr3b_test.hjson ├── failStr4a_test.hjson ├── failStr4b_test.hjson ├── failStr5a_test.hjson ├── failStr5b_test.hjson ├── failStr6a_test.hjson ├── failStr6b_test.hjson ├── failStr8a_test.hjson ├── mltabs_result.json ├── mltabs_test.json ├── failJSON10_test.json ├── failObj1_test.hjson ├── failObj3_test.hjson ├── failStr1c_test.hjson ├── failStr1d_test.hjson ├── failStr2c_test.hjson ├── failStr2d_test.hjson ├── failStr3c_test.hjson ├── failStr3d_test.hjson ├── failStr4c_test.hjson ├── failStr4d_test.hjson ├── failStr5c_test.hjson ├── failStr5d_test.hjson ├── comments6_test.hjson ├── comments7_result.hjson ├── failJSON34_test.json ├── pass6_test.hjson ├── failStr7a_test.hjson ├── root_result.hjson ├── comments7_result.json ├── pass5_result.hjson ├── root_result.json ├── comments3_test.hjson ├── pass5_result.json ├── charset2_result.hjson ├── mltabs_result.hjson ├── root_test.hjson ├── charset2_result.json ├── trail_result.hjson ├── trail_result.json ├── charset2_test.hjson ├── oa_result.hjson ├── oa_test.hjson ├── strings3_result.hjson ├── strings3_result.json ├── oa_result.json ├── pass3_result.hjson ├── pass3_result.json ├── pass5_test.hjson ├── pass3_test.json ├── failStr6c_test.hjson ├── failStr6d_test.hjson ├── trail_test.hjson ├── comments7_test.hjson ├── strings3_test.hjson ├── comments4_result.hjson ├── comments4_result.json ├── charset_result.hjson ├── charset_result.json ├── charset_test.hjson ├── comments5_result.hjson ├── windowseol_result.json ├── windowseol_result.hjson ├── comments5_result.json ├── windowseol_test.hjson ├── kan_result.hjson ├── comments_result.hjson ├── kan_result.json ├── keys_result.hjson ├── kan_test.hjson ├── strings2_result.hjson ├── comments_result.json ├── strings2_result.json ├── keys_result.json ├── strings2_test.hjson ├── keys_test.hjson ├── pass2_result.hjson ├── pass2_result.json ├── stringify1_result.hjson ├── stringify1_test.hjson ├── comments4_test.hjson ├── stringify1_result.json ├── comments_test.hjson ├── comments5_test.hjson ├── strings_result.hjson ├── strings_result.json ├── pass1_result.hjson ├── pass1_result.json ├── strings_test.hjson ├── pass1_test.json └── testlist.txt ├── go.mod ├── LICENSE ├── .github └── workflows │ ├── test.yml │ └── build.yml ├── parseNumber.go ├── hjson-cli └── main.go ├── orderedmap_test.go ├── orderedmap.go ├── structs.go ├── README.md ├── node_test.go ├── node.go └── encode_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/pass4_result.hjson: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /assets/pass4_result.json: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /assets/comments2_result.hjson: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /assets/comments2_result.json: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /assets/comments6_result.hjson: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /assets/comments6_result.json: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /assets/failJSON29_test.json: -------------------------------------------------------------------------------- 1 | [0e] 2 | -------------------------------------------------------------------------------- /assets/failJSON30_test.json: -------------------------------------------------------------------------------- 1 | [0e+] 2 | -------------------------------------------------------------------------------- /assets/pass4_test.json: -------------------------------------------------------------------------------- 1 | 2 | 10 3 | -------------------------------------------------------------------------------- /assets/sorted/pass4_result.json: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /assets/strings4_result.hjson: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /assets/strings4_result.json: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /assets/strings4_test.hjson: -------------------------------------------------------------------------------- 1 | '''''' 2 | -------------------------------------------------------------------------------- /assets/comments2/pass4_result.hjson: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /assets/failJSON16_test.json: -------------------------------------------------------------------------------- 1 | [\naked] 2 | -------------------------------------------------------------------------------- /assets/failJSON31_test.json: -------------------------------------------------------------------------------- 1 | [0e+-1] 2 | -------------------------------------------------------------------------------- /assets/sorted/comments2_result.hjson: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /assets/sorted/comments2_result.json: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /assets/sorted/comments6_result.hjson: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /assets/sorted/comments6_result.json: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /assets/sorted/pass4_result.hjson: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /assets/sorted/strings4_result.hjson: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /assets/sorted/strings4_result.json: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /assets/comments2/strings4_result.hjson: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /assets/comments2/strings4_result.json: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /assets/comments3/pass4_result.hjson: -------------------------------------------------------------------------------- 1 | 2 | 10 3 | -------------------------------------------------------------------------------- /assets/comments3/strings4_result.hjson: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /assets/comments3/strings4_result.json: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /assets/failJSON08_test.json: -------------------------------------------------------------------------------- 1 | ["Extra close"]] 2 | -------------------------------------------------------------------------------- /assets/failJSON33_test.json: -------------------------------------------------------------------------------- 1 | ["mismatch"} 2 | -------------------------------------------------------------------------------- /assets/empty_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | "": empty 3 | } 4 | -------------------------------------------------------------------------------- /assets/empty_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | "": empty 3 | } 4 | -------------------------------------------------------------------------------- /assets/failJSON02_test.json: -------------------------------------------------------------------------------- 1 | ["Unclosed array" 2 | -------------------------------------------------------------------------------- /assets/failJSON23_test.json: -------------------------------------------------------------------------------- 1 | ["Bad value", truth] 2 | -------------------------------------------------------------------------------- /assets/failJSON28_test.json: -------------------------------------------------------------------------------- 1 | ["line\ 2 | break"] 3 | -------------------------------------------------------------------------------- /assets/empty_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "": "empty" 3 | } 4 | -------------------------------------------------------------------------------- /assets/failJSON05_test.json: -------------------------------------------------------------------------------- 1 | ["double extra comma",,] 2 | -------------------------------------------------------------------------------- /assets/failJSON06_test.json: -------------------------------------------------------------------------------- 1 | [ , "<-- missing value"] 2 | -------------------------------------------------------------------------------- /assets/failJSON07_test.json: -------------------------------------------------------------------------------- 1 | ["Comma after the close"], 2 | -------------------------------------------------------------------------------- /assets/failJSON19_test.json: -------------------------------------------------------------------------------- 1 | {"Missing colon" null} 2 | -------------------------------------------------------------------------------- /assets/failJSON20_test.json: -------------------------------------------------------------------------------- 1 | {"Double colon":: null} 2 | -------------------------------------------------------------------------------- /assets/failStr9a_test.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | = 4 | [['''''' 5 | -------------------------------------------------------------------------------- /assets/passSingle_result.hjson: -------------------------------------------------------------------------------- 1 | allow quoteless strings 2 | -------------------------------------------------------------------------------- /assets/passSingle_test.hjson: -------------------------------------------------------------------------------- 1 | allow quoteless strings 2 | -------------------------------------------------------------------------------- /assets/comments2/empty_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | "": empty 3 | } 4 | -------------------------------------------------------------------------------- /assets/comments3/empty_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | "": empty 3 | } 4 | -------------------------------------------------------------------------------- /assets/failJSON11_test.json: -------------------------------------------------------------------------------- 1 | {"Illegal expression": 1 + 2} 2 | -------------------------------------------------------------------------------- /assets/failJSON12_test.json: -------------------------------------------------------------------------------- 1 | {"Illegal invocation": alert()} 2 | -------------------------------------------------------------------------------- /assets/failJSON14_test.json: -------------------------------------------------------------------------------- 1 | {"Numbers cannot be hex": 0x14} 2 | -------------------------------------------------------------------------------- /assets/passSingle_result.json: -------------------------------------------------------------------------------- 1 | "allow quoteless strings" 2 | -------------------------------------------------------------------------------- /assets/sorted/empty_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | "": empty 3 | } 4 | -------------------------------------------------------------------------------- /assets/sorted/empty_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "": "empty" 3 | } 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hjson/hjson-go/v4 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /assets/comments2/passSingle_result.hjson: -------------------------------------------------------------------------------- 1 | allow quoteless strings 2 | -------------------------------------------------------------------------------- /assets/comments3/passSingle_result.hjson: -------------------------------------------------------------------------------- 1 | allow quoteless strings 2 | -------------------------------------------------------------------------------- /assets/failJSON15_test.json: -------------------------------------------------------------------------------- 1 | ["Illegal backslash escape: \x15"] 2 | -------------------------------------------------------------------------------- /assets/failJSON17_test.json: -------------------------------------------------------------------------------- 1 | ["Illegal backslash escape: \017"] 2 | -------------------------------------------------------------------------------- /assets/failJSON21_test.json: -------------------------------------------------------------------------------- 1 | {"Comma instead of colon", null} 2 | -------------------------------------------------------------------------------- /assets/failJSON22_test.json: -------------------------------------------------------------------------------- 1 | ["Colon instead of comma": false] 2 | -------------------------------------------------------------------------------- /assets/int64_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | bigInt: 144115188075855873 3 | } 4 | -------------------------------------------------------------------------------- /assets/sorted/passSingle_result.hjson: -------------------------------------------------------------------------------- 1 | allow quoteless strings 2 | -------------------------------------------------------------------------------- /assets/sorted/passSingle_result.json: -------------------------------------------------------------------------------- 1 | "allow quoteless strings" 2 | -------------------------------------------------------------------------------- /assets/failJSON26_test.json: -------------------------------------------------------------------------------- 1 | ["tab\ character\ in\ string\ "] 2 | -------------------------------------------------------------------------------- /assets/failJSON32_test.json: -------------------------------------------------------------------------------- 1 | {"Comma instead if closing brace": true, 2 | -------------------------------------------------------------------------------- /assets/failKey4_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid name 3 | : 0 4 | } 5 | -------------------------------------------------------------------------------- /assets/int64_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | bigInt: 144115188075855873 3 | } 4 | -------------------------------------------------------------------------------- /assets/int64_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "bigInt": 144115188075855873 3 | } 4 | -------------------------------------------------------------------------------- /assets/comments3_result.hjson: -------------------------------------------------------------------------------- 1 | a string value // still part of the string 2 | -------------------------------------------------------------------------------- /assets/comments3_result.json: -------------------------------------------------------------------------------- 1 | "a string value // still part of the string" 2 | -------------------------------------------------------------------------------- /assets/failJSON13_test.json: -------------------------------------------------------------------------------- 1 | {"Numbers cannot have leading zeroes": 013} 2 | -------------------------------------------------------------------------------- /assets/failKey2_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid name 3 | {name: 0 4 | } 5 | -------------------------------------------------------------------------------- /assets/failKey3_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid name 3 | key,name: 0 4 | } 5 | -------------------------------------------------------------------------------- /assets/failKey5_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid name 3 | '''foo''': 0 4 | } 5 | -------------------------------------------------------------------------------- /assets/sorted/int64_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | bigInt: 144115188075855870 3 | } 4 | -------------------------------------------------------------------------------- /assets/sorted/int64_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "bigInt": 144115188075855870 3 | } 4 | -------------------------------------------------------------------------------- /assets/comments2/int64_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | bigInt: 144115188075855870 3 | } 4 | -------------------------------------------------------------------------------- /assets/comments3/int64_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | bigInt: 144115188075855870 3 | } 4 | -------------------------------------------------------------------------------- /assets/failKey1_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid name 3 | wrong name: 0 4 | } 5 | -------------------------------------------------------------------------------- /assets/failMLStr1_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid multiline string 3 | ml: ''' 4 | -------------------------------------------------------------------------------- /assets/failObj2_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid obj 3 | noEnd 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /assets/pass2_test.json: -------------------------------------------------------------------------------- 1 | [[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] 2 | -------------------------------------------------------------------------------- /assets/sorted/comments3_result.hjson: -------------------------------------------------------------------------------- 1 | a string value // still part of the string 2 | -------------------------------------------------------------------------------- /assets/comments2_test.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ 3 // after1 3 | // after2 4 | -------------------------------------------------------------------------------- /assets/failCharset1_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid \u char 3 | char: "\uxxxx" 4 | } 5 | -------------------------------------------------------------------------------- /assets/failStr1a_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid quoteless string 3 | ql: ] 4 | } 5 | -------------------------------------------------------------------------------- /assets/failStr1b_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid quoteless string 3 | ql: ]x 4 | } 5 | -------------------------------------------------------------------------------- /assets/failStr2a_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid quoteless string 3 | ql: } 4 | } 5 | -------------------------------------------------------------------------------- /assets/failStr2b_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid quoteless string 3 | ql: }x 4 | } 5 | -------------------------------------------------------------------------------- /assets/failStr3a_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid quoteless string 3 | ql: { 4 | } 5 | -------------------------------------------------------------------------------- /assets/failStr3b_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid quoteless string 3 | ql: {x 4 | } 5 | -------------------------------------------------------------------------------- /assets/failStr4a_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid quoteless string 3 | ql: [ 4 | } 5 | -------------------------------------------------------------------------------- /assets/failStr4b_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid quoteless string 3 | ql: [x 4 | } 5 | -------------------------------------------------------------------------------- /assets/failStr5a_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid quoteless string 3 | ql: : 4 | } 5 | -------------------------------------------------------------------------------- /assets/failStr5b_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid quoteless string 3 | ql: :x 4 | } 5 | -------------------------------------------------------------------------------- /assets/failStr6a_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid quoteless string 3 | ql: , 4 | } 5 | -------------------------------------------------------------------------------- /assets/failStr6b_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid quoteless string 3 | ql: ,x 4 | } 5 | -------------------------------------------------------------------------------- /assets/failStr8a_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid ml-string 3 | foo : ""'text''' 4 | } 5 | -------------------------------------------------------------------------------- /assets/mltabs_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "bar\tjoe\noki\tdoki\n\t\ttwo tabs" 3 | } 4 | -------------------------------------------------------------------------------- /assets/mltabs_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "bar\tjoe\noki\tdoki\n\t\ttwo tabs" 3 | } 4 | -------------------------------------------------------------------------------- /assets/sorted/comments3_result.json: -------------------------------------------------------------------------------- 1 | "a string value // still part of the string" 2 | -------------------------------------------------------------------------------- /assets/failJSON10_test.json: -------------------------------------------------------------------------------- 1 | {"Extra value after close": true} "misplaced quoted value" 2 | -------------------------------------------------------------------------------- /assets/failObj1_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid obj 3 | noDelimiter 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /assets/failObj3_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # missing key 3 | 4 | [ 5 | test 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /assets/failStr1c_test.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | foo 3 | # invalid quoteless string 4 | ] 5 | ] 6 | -------------------------------------------------------------------------------- /assets/failStr1d_test.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | foo 3 | # invalid quoteless string 4 | ]x 5 | ] 6 | -------------------------------------------------------------------------------- /assets/failStr2c_test.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | foo 3 | # invalid quoteless string 4 | } 5 | ] 6 | -------------------------------------------------------------------------------- /assets/failStr2d_test.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | foo 3 | # invalid quoteless string 4 | }x 5 | ] 6 | -------------------------------------------------------------------------------- /assets/failStr3c_test.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | foo 3 | # invalid quoteless string 4 | { 5 | ] 6 | -------------------------------------------------------------------------------- /assets/failStr3d_test.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | foo 3 | # invalid quoteless string 4 | {x 5 | ] 6 | -------------------------------------------------------------------------------- /assets/failStr4c_test.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | foo 3 | # invalid quoteless string 4 | [ 5 | ] 6 | -------------------------------------------------------------------------------- /assets/failStr4d_test.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | foo 3 | # invalid quoteless string 4 | [x 5 | ] 6 | -------------------------------------------------------------------------------- /assets/failStr5c_test.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | foo 3 | # invalid quoteless string 4 | : 5 | ] 6 | -------------------------------------------------------------------------------- /assets/failStr5d_test.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | foo 3 | # invalid quoteless string 4 | :x 5 | ] 6 | -------------------------------------------------------------------------------- /assets/sorted/mltabs_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "bar\tjoe\noki\tdoki\n\t\ttwo tabs" 3 | } 4 | -------------------------------------------------------------------------------- /assets/comments2/comments2_result.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ 3 // after1 3 | // after2 4 | -------------------------------------------------------------------------------- /assets/comments3/comments2_result.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ 3 // after1 3 | // after2 4 | -------------------------------------------------------------------------------- /assets/comments6_test.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ 3 // after1 3 | // after2 4 | 5 | -------------------------------------------------------------------------------- /assets/sorted/strings3_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "", 3 | "b": "­؀؄܏឴឵‌‏\u2028 ⁠￰￿" 4 | } 5 | -------------------------------------------------------------------------------- /assets/comments2/comments6_result.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ 3 // after1 3 | // after2 4 | 5 | -------------------------------------------------------------------------------- /assets/comments3/comments6_result.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ 3 // after1 3 | // after2 4 | 5 | -------------------------------------------------------------------------------- /assets/comments7_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | b: { 3 | sub1: 3 4 | sub2: 4 5 | } 6 | a: 2 7 | } 8 | -------------------------------------------------------------------------------- /assets/failJSON34_test.json: -------------------------------------------------------------------------------- 1 | A quoteless string is OK, 2 | but two must be contained in an array. 3 | -------------------------------------------------------------------------------- /assets/pass6_test.hjson: -------------------------------------------------------------------------------- 1 | a: 1, b: 2,c:3,'d' /*cm1*/ : 4, /*cm2 */ e: /* cm3*/ 5 /* cm 4 */ f : 6 2 | -------------------------------------------------------------------------------- /assets/failStr7a_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # invalid string containing a newline 3 | foo : " 4 | " 5 | } 6 | -------------------------------------------------------------------------------- /assets/root_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | database: { 3 | host: 127.0.0.1 4 | port: 555 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /assets/comments7_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "b": { 3 | "sub1": 3, 4 | "sub2": 4 5 | }, 6 | "a": 2 7 | } 8 | -------------------------------------------------------------------------------- /assets/pass5_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | bigDouble: 9.22337203685478e+58 3 | bigInt: 9.22337203685478e+58 4 | } 5 | -------------------------------------------------------------------------------- /assets/root_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "host": "127.0.0.1", 4 | "port": 555 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /assets/sorted/comments7_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | a: 2 3 | b: { 4 | sub1: 3 5 | sub2: 4 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /assets/sorted/pass6_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | a: 1 3 | b: 2 4 | c: 3 5 | d: 4 6 | e: 5 7 | f: 6 8 | } 9 | -------------------------------------------------------------------------------- /assets/sorted/root_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | database: { 3 | host: 127.0.0.1 4 | port: 555 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /assets/comments3_test.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ a string value // still part of the string 3 | // after2 4 | -------------------------------------------------------------------------------- /assets/pass5_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "bigDouble": 9.22337203685478e+58, 3 | "bigInt": 9.22337203685478e+58 4 | } 5 | -------------------------------------------------------------------------------- /assets/comments2/pass5_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | bigDouble: 9.223372036854776e+58 3 | bigInt: 9.223372036854776e+58 4 | } 5 | -------------------------------------------------------------------------------- /assets/comments3/pass5_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | bigDouble: 9.223372036854776e+58 3 | bigInt: 9.223372036854776e+58 4 | } 5 | -------------------------------------------------------------------------------- /assets/sorted/comments7_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": 2, 3 | "b": { 4 | "sub1": 3, 5 | "sub2": 4 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /assets/sorted/pass5_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | bigDouble: 9.223372036854776e+58 3 | bigInt: 9.223372036854776e+58 4 | } 5 | -------------------------------------------------------------------------------- /assets/sorted/pass5_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "bigDouble": 9.223372036854776e+58, 3 | "bigInt": 9.223372036854776e+58 4 | } 5 | -------------------------------------------------------------------------------- /assets/sorted/root_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "host": "127.0.0.1", 4 | "port": 555 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /assets/charset2_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | uescape: "\u0000,\u0001,\uffff" 3 | "um\u000blaut": äöüßÄÖÜ 4 | hex: ģ䕧覫췯ꯍ 5 | } 6 | -------------------------------------------------------------------------------- /assets/comments2/comments3_result.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ a string value // still part of the string 3 | // after2 4 | -------------------------------------------------------------------------------- /assets/comments3/comments3_result.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ a string value // still part of the string 3 | // after2 4 | -------------------------------------------------------------------------------- /assets/mltabs_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | foo: 3 | ''' 4 | bar joe 5 | oki doki 6 | two tabs 7 | ''' 8 | } 9 | -------------------------------------------------------------------------------- /assets/root_test.hjson: -------------------------------------------------------------------------------- 1 | // a object with the root braces omitted 2 | database: { 3 | host: 127.0.0.1 4 | port: 555 5 | } 6 | -------------------------------------------------------------------------------- /assets/sorted/pass6_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": 1, 3 | "b": 2, 4 | "c": 3, 5 | "d": 4, 6 | "e": 5, 7 | "f": 6 8 | } 9 | -------------------------------------------------------------------------------- /assets/charset2_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "uescape": "\u0000,\u0001,￿", 3 | "um\u000blaut": "äöüßÄÖÜ", 4 | "hex": "ģ䕧覫췯ꯍ" 5 | } 6 | -------------------------------------------------------------------------------- /assets/sorted/charset2_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | hex: ģ䕧覫췯ꯍ 3 | uescape: "\u0000,\u0001,\uffff" 4 | "um\u000blaut": äöüßÄÖÜ 5 | } 6 | -------------------------------------------------------------------------------- /assets/sorted/mltabs_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | foo: 3 | ''' 4 | bar joe 5 | oki doki 6 | two tabs 7 | ''' 8 | } 9 | -------------------------------------------------------------------------------- /assets/comments2/charset2_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | uescape: "\u0000,\u0001,\uffff" 3 | "um\u000blaut": äöüßÄÖÜ 4 | hex: ģ䕧覫췯ꯍ 5 | } 6 | -------------------------------------------------------------------------------- /assets/comments2/mltabs_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | foo: 3 | ''' 4 | bar joe 5 | oki doki 6 | two tabs 7 | ''' 8 | } 9 | -------------------------------------------------------------------------------- /assets/comments3/charset2_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | uescape: "\u0000,\u0001,\uffff" 3 | "um\u000blaut": äöüßÄÖÜ 4 | hex: ģ䕧覫췯ꯍ 5 | } 6 | -------------------------------------------------------------------------------- /assets/comments3/mltabs_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | foo: 3 | ''' 4 | bar joe 5 | oki doki 6 | two tabs 7 | ''' 8 | } 9 | -------------------------------------------------------------------------------- /assets/sorted/charset2_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "hex": "ģ䕧覫췯ꯍ", 3 | "uescape": "\u0000,\u0001,￿", 4 | "um\u000blaut": "äöüßÄÖÜ" 5 | } 6 | -------------------------------------------------------------------------------- /assets/trail_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | foo: 0 -- this string starts at 0 and ends at 1, preceding and trailing whitespace is ignored -- 1 3 | } 4 | -------------------------------------------------------------------------------- /assets/comments2/pass6_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | a: 1 3 | b: 2 4 | c: 3 5 | d: /*cm1*/ 4 /*cm2 */ 6 | e: /* cm3*/ 5 /* cm 4 */ 7 | f: 6 8 | } 9 | -------------------------------------------------------------------------------- /assets/comments3/pass6_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | a: 1 3 | b: 2 4 | c: 3 5 | d: /*cm1*/ 4 /*cm2 */ 6 | e: /* cm3*/ 5 /* cm 4 */ 7 | f: 6 8 | } 9 | -------------------------------------------------------------------------------- /assets/comments3/root_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | // a object with the root braces omitted 3 | database: { 4 | host: 127.0.0.1 5 | port: 555 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /assets/sorted/trail_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | foo: 0 -- this string starts at 0 and ends at 1, preceding and trailing whitespace is ignored -- 1 3 | } 4 | -------------------------------------------------------------------------------- /assets/trail_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "0 -- this string starts at 0 and ends at 1, preceding and trailing whitespace is ignored -- 1" 3 | } 4 | -------------------------------------------------------------------------------- /assets/charset2_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | uescape: "\u0000,\u0001,\uffff" 3 | "um\u000blaut": äöüßÄÖÜ 4 | hex: "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A" 5 | } 6 | -------------------------------------------------------------------------------- /assets/comments2/root_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | // a object with the root braces omitted 3 | database: { 4 | host: 127.0.0.1 5 | port: 555 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /assets/oa_result.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | a 3 | {} 4 | {} 5 | [] 6 | [] 7 | { 8 | b: 1 9 | c: [] 10 | d: {} 11 | } 12 | [] 13 | ] 14 | -------------------------------------------------------------------------------- /assets/oa_test.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | a 3 | {} 4 | {} 5 | [] 6 | [] 7 | { 8 | b: 1 9 | c: [] 10 | d: {} 11 | } 12 | [] 13 | ] 14 | -------------------------------------------------------------------------------- /assets/sorted/trail_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "0 -- this string starts at 0 and ends at 1, preceding and trailing whitespace is ignored -- 1" 3 | } 4 | -------------------------------------------------------------------------------- /assets/strings3_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | a: "" 3 | b: "\u00ad\u0600\u0604\u070f\u17b4\u17b5\u200c\u200f\u2028\u202f\u2060\u206f\ufeff\ufff0\uffff" 4 | } 5 | -------------------------------------------------------------------------------- /assets/strings3_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "", 3 | "b": "\u00ad\u0600\u0604\u070f\u17b4\u17b5\u200c\u200f\u2028\u202f\u2060\u206f\ufeff\ufff0\uffff" 4 | } 5 | -------------------------------------------------------------------------------- /assets/sorted/strings3_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | a: "" 3 | b: "\u00ad\u0600\u0604\u070f\u17b4\u17b5\u200c\u200f\u2028\u202f\u2060\u206f\ufeff\ufff0\uffff" 4 | } 5 | -------------------------------------------------------------------------------- /assets/comments2/oa_result.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | a 3 | {} 4 | {} 5 | [] 6 | [] 7 | { 8 | b: 1 9 | c: [] 10 | d: {} 11 | } 12 | [] 13 | ] 14 | -------------------------------------------------------------------------------- /assets/comments3/oa_result.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | a 3 | {} 4 | {} 5 | [] 6 | [] 7 | { 8 | b: 1 9 | c: [] 10 | d: {} 11 | } 12 | [] 13 | ] 14 | -------------------------------------------------------------------------------- /assets/sorted/oa_result.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | a 3 | {} 4 | {} 5 | [] 6 | [] 7 | { 8 | b: 1 9 | c: [] 10 | d: {} 11 | } 12 | [] 13 | ] 14 | -------------------------------------------------------------------------------- /assets/oa_result.json: -------------------------------------------------------------------------------- 1 | [ 2 | "a", 3 | {}, 4 | {}, 5 | [], 6 | [], 7 | { 8 | "b": 1, 9 | "c": [], 10 | "d": {} 11 | }, 12 | [] 13 | ] 14 | -------------------------------------------------------------------------------- /assets/pass3_result.hjson: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /assets/sorted/oa_result.json: -------------------------------------------------------------------------------- 1 | [ 2 | "a", 3 | {}, 4 | {}, 5 | [], 6 | [], 7 | { 8 | "b": 1, 9 | "c": [], 10 | "d": {} 11 | }, 12 | [] 13 | ] 14 | -------------------------------------------------------------------------------- /assets/pass3_result.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 | -------------------------------------------------------------------------------- /assets/pass5_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | bigDouble: 92233720368547758073829419051489548484843823585675828488686.0 3 | bigInt: 92233720368547758073829419051489548484843823585675828488686 4 | } 5 | -------------------------------------------------------------------------------- /assets/sorted/pass3_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | "JSON Test Pattern pass3": { 3 | "In this test": It is an object. 4 | "The outermost value": must be an object or array. 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /assets/comments2/pass3_result.hjson: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /assets/sorted/pass3_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "JSON Test Pattern pass3": { 3 | "In this test": "It is an object.", 4 | "The outermost value": "must be an object or array." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /assets/pass3_test.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 | -------------------------------------------------------------------------------- /assets/comments3/pass3_result.hjson: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /assets/failStr6c_test.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | # invalid quoteless string 3 | # note that if there were a preceding value the comma would 4 | # be allowed/ignored as a separator/trailing comma 5 | , 6 | ] 7 | -------------------------------------------------------------------------------- /assets/failStr6d_test.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | # invalid quoteless string 3 | # note that if there were a preceding value the comma would 4 | # be allowed/ignored as a separator/trailing comma 5 | ,x 6 | ] 7 | -------------------------------------------------------------------------------- /assets/comments2/trail_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | // the following line contains trailing whitespace: 3 | foo: 0 -- this string starts at 0 and ends at 1, preceding and trailing whitespace is ignored -- 1 4 | } 5 | -------------------------------------------------------------------------------- /assets/comments3/trail_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | // the following line contains trailing whitespace: 3 | foo: 0 -- this string starts at 0 and ends at 1, preceding and trailing whitespace is ignored -- 1 4 | } 5 | -------------------------------------------------------------------------------- /assets/trail_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | // the following line contains trailing whitespace: 3 | foo: 0 -- this string starts at 0 and ends at 1, preceding and trailing whitespace is ignored -- 1 4 | } 5 | -------------------------------------------------------------------------------- /assets/comments7_test.hjson: -------------------------------------------------------------------------------- 1 | # comment before 2 | b: /* key comment */ { 3 | sub1: 3 # comment after 4 | sub2: 4 # comment more after 5 | } # cm after obj 6 | // Comment B4 7 | a: 2 8 | /* Last comment */ 9 | -------------------------------------------------------------------------------- /assets/strings3_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | // Empty string 3 | a: "" 4 | // Unicode code points that require escape inside quotes. 5 | b: "\u00ad\u0600\u0604\u070f\u17b4\u17b5\u200c\u200f\u2028\u202f\u2060\u206f\ufeff\ufff0\uffff" 6 | } 7 | -------------------------------------------------------------------------------- /assets/comments2/strings3_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | // Empty string 3 | a: "" 4 | // Unicode code points that require escape inside quotes. 5 | b: "\u00ad\u0600\u0604\u070f\u17b4\u17b5\u200c\u200f\u2028\u202f\u2060\u206f\ufeff\ufff0\uffff" 6 | } 7 | -------------------------------------------------------------------------------- /assets/comments3/comments7_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # comment before 3 | b: /* key comment */ { 4 | sub1: 3 # comment after 5 | sub2: 4 # comment more after 6 | } # cm after obj 7 | // Comment B4 8 | a: 2 9 | /* Last comment */ 10 | } 11 | -------------------------------------------------------------------------------- /assets/comments3/strings3_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | // Empty string 3 | a: "" 4 | // Unicode code points that require escape inside quotes. 5 | b: "\u00ad\u0600\u0604\u070f\u17b4\u17b5\u200c\u200f\u2028\u202f\u2060\u206f\ufeff\ufff0\uffff" 6 | } 7 | -------------------------------------------------------------------------------- /assets/comments2/comments7_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # comment before 3 | b: /* key comment */ { 4 | sub1: 3 # comment after 5 | sub2: 4 # comment more after 6 | } # cm after obj 7 | // Comment B4 8 | a: 2 9 | /* Last comment */ 10 | } 11 | -------------------------------------------------------------------------------- /assets/comments4_result.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | a string value // still part of the string 3 | a string value 4 | {} 5 | {} 6 | {} 7 | [] 8 | {} 9 | { 10 | val5a: 1 11 | val5b: 2 12 | } 13 | [] 14 | [] 15 | [] 16 | [] 17 | [ 18 | 1 19 | 2 20 | ] 21 | 3 22 | 4 23 | ] 24 | -------------------------------------------------------------------------------- /assets/sorted/comments4_result.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | a string value // still part of the string 3 | a string value 4 | {} 5 | {} 6 | {} 7 | [] 8 | {} 9 | { 10 | val5a: 1 11 | val5b: 2 12 | } 13 | [] 14 | [] 15 | [] 16 | [] 17 | [ 18 | 1 19 | 2 20 | ] 21 | 3 22 | 4 23 | ] 24 | -------------------------------------------------------------------------------- /assets/comments4_result.json: -------------------------------------------------------------------------------- 1 | [ 2 | "a string value // still part of the string", 3 | "a string value", 4 | {}, 5 | {}, 6 | {}, 7 | [], 8 | {}, 9 | { 10 | "val5a": 1, 11 | "val5b": 2 12 | }, 13 | [], 14 | [], 15 | [], 16 | [], 17 | [ 18 | 1, 19 | 2 20 | ], 21 | 3, 22 | 4 23 | ] 24 | -------------------------------------------------------------------------------- /assets/sorted/comments4_result.json: -------------------------------------------------------------------------------- 1 | [ 2 | "a string value // still part of the string", 3 | "a string value", 4 | {}, 5 | {}, 6 | {}, 7 | [], 8 | {}, 9 | { 10 | "val5a": 1, 11 | "val5b": 2 12 | }, 13 | [], 14 | [], 15 | [], 16 | [], 17 | [ 18 | 1, 19 | 2 20 | ], 21 | 3, 22 | 4 23 | ] 24 | -------------------------------------------------------------------------------- /assets/charset_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | ql-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 3 | js-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 4 | ml-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 5 | } 6 | -------------------------------------------------------------------------------- /assets/sorted/charset_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | js-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 3 | ml-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 4 | ql-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 5 | } 6 | -------------------------------------------------------------------------------- /assets/comments2/charset_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | ql-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 3 | js-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 4 | ml-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 5 | } 6 | -------------------------------------------------------------------------------- /assets/comments3/charset_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | ql-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 3 | js-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 4 | ml-ascii: 5 | ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 6 | } 7 | -------------------------------------------------------------------------------- /assets/charset_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "ql-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", 3 | "js-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", 4 | "ml-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" 5 | } 6 | -------------------------------------------------------------------------------- /assets/sorted/charset_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "js-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", 3 | "ml-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", 4 | "ql-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" 5 | } 6 | -------------------------------------------------------------------------------- /assets/charset_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | ql-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 3 | js-ascii: "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" 4 | ml-ascii: 5 | ''' 6 | ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 7 | ''' 8 | } 9 | -------------------------------------------------------------------------------- /assets/comments5_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | key1: a string value // still part of the string 3 | key2: a string value 4 | map1: {} 5 | map2: {} 6 | map3: {} 7 | vec1: [] 8 | map4: {} 9 | map5: { 10 | val5a: 1 11 | val5b: 2 12 | } 13 | vec1b: [] 14 | vec2: [] 15 | vec3: [] 16 | vec4: [] 17 | vec5: [ 18 | 1 19 | 2 20 | ] 21 | key3: 3 22 | key4: 4 23 | } 24 | -------------------------------------------------------------------------------- /assets/sorted/comments5_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | key1: a string value // still part of the string 3 | key2: a string value 4 | key3: 3 5 | key4: 4 6 | map1: {} 7 | map2: {} 8 | map3: {} 9 | map4: {} 10 | map5: { 11 | val5a: 1 12 | val5b: 2 13 | } 14 | vec1: [] 15 | vec1b: [] 16 | vec2: [] 17 | vec3: [] 18 | vec4: [] 19 | vec5: [ 20 | 1 21 | 2 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /assets/windowseol_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "ql-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", 3 | "js-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", 4 | "ml-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", 5 | "newline": "1\n2" 6 | } 7 | -------------------------------------------------------------------------------- /assets/sorted/windowseol_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "js-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", 3 | "ml-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", 4 | "newline": "1\n2", 5 | "ql-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" 6 | } 7 | -------------------------------------------------------------------------------- /assets/windowseol_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | ql-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 3 | js-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 4 | ml-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 5 | newline: 6 | ''' 7 | 1 8 | 2 9 | ''' 10 | } 11 | -------------------------------------------------------------------------------- /assets/comments2/windowseol_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | ql-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 3 | js-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 4 | ml-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 5 | newline: 6 | ''' 7 | 1 8 | 2 9 | ''' 10 | } 11 | -------------------------------------------------------------------------------- /assets/sorted/windowseol_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | js-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 3 | ml-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 4 | newline: 5 | ''' 6 | 1 7 | 2 8 | ''' 9 | ql-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 10 | } 11 | -------------------------------------------------------------------------------- /assets/comments5_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "key1": "a string value // still part of the string", 3 | "key2": "a string value", 4 | "map1": {}, 5 | "map2": {}, 6 | "map3": {}, 7 | "vec1": [], 8 | "map4": {}, 9 | "map5": { 10 | "val5a": 1, 11 | "val5b": 2 12 | }, 13 | "vec1b": [], 14 | "vec2": [], 15 | "vec3": [], 16 | "vec4": [], 17 | "vec5": [ 18 | 1, 19 | 2 20 | ], 21 | "key3": 3, 22 | "key4": 4 23 | } 24 | -------------------------------------------------------------------------------- /assets/comments3/windowseol_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | ql-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 3 | js-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 4 | ml-ascii: 5 | ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 6 | newline: 7 | ''' 8 | 1 9 | 2 10 | ''' 11 | } 12 | -------------------------------------------------------------------------------- /assets/sorted/comments5_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "key1": "a string value // still part of the string", 3 | "key2": "a string value", 4 | "key3": 3, 5 | "key4": 4, 6 | "map1": {}, 7 | "map2": {}, 8 | "map3": {}, 9 | "map4": {}, 10 | "map5": { 11 | "val5a": 1, 12 | "val5b": 2 13 | }, 14 | "vec1": [], 15 | "vec1b": [], 16 | "vec2": [], 17 | "vec3": [], 18 | "vec4": [], 19 | "vec5": [ 20 | 1, 21 | 2 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /assets/windowseol_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | ql-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 3 | js-ascii: "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" 4 | ml-ascii: 5 | ''' 6 | ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 7 | ''' 8 | newline: 9 | ''' 10 | 1 11 | 2 12 | ''' 13 | } 14 | -------------------------------------------------------------------------------- /assets/kan_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | numbers: [ 3 | 0 4 | 0 5 | 0 6 | 42 7 | 42.1 8 | -5 9 | -5.1 10 | 1701 11 | -1701 12 | 12.345 13 | -12.345 14 | ] 15 | native: [ 16 | true 17 | true 18 | false 19 | false 20 | null 21 | null 22 | ] 23 | strings: [ 24 | x 0 25 | .0 26 | 00 27 | 01 28 | 0 0 0 29 | 42 x 30 | 42.1 asdf 31 | 1.2.3 32 | -5 0 - 33 | -5.1 -- 34 | 17.01e2 + 35 | -17.01e2 : 36 | 12345e-3 @ 37 | -12345e-3 $ 38 | true true 39 | x true 40 | false false 41 | x false 42 | null null 43 | x null 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /assets/sorted/kan_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | native: [ 3 | true 4 | true 5 | false 6 | false 7 | null 8 | null 9 | ] 10 | numbers: [ 11 | 0 12 | 0 13 | 0 14 | 42 15 | 42.1 16 | -5 17 | -5.1 18 | 1701 19 | -1701 20 | 12.345 21 | -12.345 22 | ] 23 | strings: [ 24 | x 0 25 | .0 26 | 00 27 | 01 28 | 0 0 0 29 | 42 x 30 | 42.1 asdf 31 | 1.2.3 32 | -5 0 - 33 | -5.1 -- 34 | 17.01e2 + 35 | -17.01e2 : 36 | 12345e-3 @ 37 | -12345e-3 $ 38 | true true 39 | x true 40 | false false 41 | x false 42 | null null 43 | x null 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /assets/comments2/kan_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # the comma forces a whitespace check 3 | numbers: [ 4 | 0 5 | 0 6 | 0 7 | 42 8 | 42.1 9 | -5 10 | -5.1 11 | 1701 12 | -1701 13 | 12.345 14 | -12.345 15 | ] 16 | native: [ 17 | true 18 | true 19 | false 20 | false 21 | null 22 | null 23 | ] 24 | strings: [ 25 | x 0 26 | .0 27 | 00 28 | 01 29 | 0 0 0 30 | 42 x 31 | 42.1 asdf 32 | 1.2.3 33 | -5 0 - 34 | -5.1 -- 35 | 17.01e2 + 36 | -17.01e2 : 37 | 12345e-3 @ 38 | -12345e-3 $ 39 | true true 40 | x true 41 | false false 42 | x false 43 | null null 44 | x null 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /assets/comments_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | foo1: This is a string value. # part of the string 3 | foo2: This is a string value. 4 | bar1: This is a string value. // part of the string 5 | bar2: This is a string value. 6 | foobar1: This is a string value./* part of the string */ 7 | foobar2: This is a string value. 8 | rem1: "# test" 9 | rem2: "// test" 10 | rem3: "/* test */" 11 | num1: 0 12 | num2: 0 13 | num3: 2 14 | true1: true 15 | true2: true 16 | true3: true 17 | false1: false 18 | false2: false 19 | false3: false 20 | null1: null 21 | null2: null 22 | null3: null 23 | str1: 00 # part of the string 24 | str2: 00.0 // part of the string 25 | str3: 02 /* part of the string */ 26 | } 27 | -------------------------------------------------------------------------------- /assets/sorted/comments_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | bar1: This is a string value. // part of the string 3 | bar2: This is a string value. 4 | false1: false 5 | false2: false 6 | false3: false 7 | foo1: This is a string value. # part of the string 8 | foo2: This is a string value. 9 | foobar1: This is a string value./* part of the string */ 10 | foobar2: This is a string value. 11 | null1: null 12 | null2: null 13 | null3: null 14 | num1: 0 15 | num2: 0 16 | num3: 2 17 | rem1: "# test" 18 | rem2: "// test" 19 | rem3: "/* test */" 20 | str1: 00 # part of the string 21 | str2: 00.0 // part of the string 22 | str3: 02 /* part of the string */ 23 | true1: true 24 | true2: true 25 | true3: true 26 | } 27 | -------------------------------------------------------------------------------- /assets/comments3/kan_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # the comma forces a whitespace check 3 | numbers: 4 | [ 5 | 0 6 | 0 7 | 0 8 | 42 9 | 42.1 10 | -5 11 | -5.1 12 | 1701 13 | -1701 14 | 12.345 15 | -12.345 16 | ] 17 | native: 18 | [ 19 | true 20 | true 21 | false 22 | false 23 | null 24 | null 25 | ] 26 | strings: 27 | [ 28 | x 0 29 | .0 30 | 00 31 | 01 32 | 0 0 0 33 | 42 x 34 | 42.1 asdf 35 | 1.2.3 36 | -5 0 - 37 | -5.1 -- 38 | 17.01e2 + 39 | -17.01e2 : 40 | 12345e-3 @ 41 | -12345e-3 $ 42 | true true 43 | x true 44 | false false 45 | x false 46 | null null 47 | x null 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /assets/kan_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "numbers": [ 3 | 0, 4 | 0, 5 | 0, 6 | 42, 7 | 42.1, 8 | -5, 9 | -5.1, 10 | 1701, 11 | -1701, 12 | 12.345, 13 | -12.345 14 | ], 15 | "native": [ 16 | true, 17 | true, 18 | false, 19 | false, 20 | null, 21 | null 22 | ], 23 | "strings": [ 24 | "x 0", 25 | ".0", 26 | "00", 27 | "01", 28 | "0 0 0", 29 | "42 x", 30 | "42.1 asdf", 31 | "1.2.3", 32 | "-5 0 -", 33 | "-5.1 --", 34 | "17.01e2 +", 35 | "-17.01e2 :", 36 | "12345e-3 @", 37 | "-12345e-3 $", 38 | "true true", 39 | "x true", 40 | "false false", 41 | "x false", 42 | "null null", 43 | "x null" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /assets/keys_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | unquoted_key: test 3 | _unquoted: test 4 | test-key: test 5 | -test: test 6 | .key: test 7 | trailing: test 8 | trailing2: test 9 | "#c1": test 10 | "foo#bar": test 11 | "//bar": test 12 | "foo//bar": test 13 | "/*foo*/": test 14 | "foo/*foo*/bar": test 15 | "/*": test 16 | "foo/*bar": test 17 | "\"": test 18 | "foo\"bar": test 19 | "'''": test 20 | "foo'''bar": test 21 | "'": test 22 | "'foo": test 23 | "foo'bar": test 24 | ":": test 25 | "foo:bar": test 26 | "{": test 27 | "foo{bar": test 28 | "}": test 29 | "foo}bar": test 30 | "[": test 31 | "foo[bar": test 32 | "]": test 33 | "foo]bar": test 34 | nl1: test 35 | nl2: test 36 | nl3: test 37 | } 38 | -------------------------------------------------------------------------------- /assets/sorted/kan_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "native": [ 3 | true, 4 | true, 5 | false, 6 | false, 7 | null, 8 | null 9 | ], 10 | "numbers": [ 11 | 0, 12 | 0, 13 | -0, 14 | 42, 15 | 42.1, 16 | -5, 17 | -5.1, 18 | 1701, 19 | -1701, 20 | 12.345, 21 | -12.345 22 | ], 23 | "strings": [ 24 | "x 0", 25 | ".0", 26 | "00", 27 | "01", 28 | "0 0 0", 29 | "42 x", 30 | "42.1 asdf", 31 | "1.2.3", 32 | "-5 0 -", 33 | "-5.1 --", 34 | "17.01e2 +", 35 | "-17.01e2 :", 36 | "12345e-3 @", 37 | "-12345e-3 $", 38 | "true true", 39 | "x true", 40 | "false false", 41 | "x false", 42 | "null null", 43 | "x null" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /assets/sorted/keys_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | "\"": test 3 | "#c1": test 4 | "'": test 5 | "'''": test 6 | "'foo": test 7 | -test: test 8 | .key: test 9 | "/*": test 10 | "/*foo*/": test 11 | "//bar": test 12 | ":": test 13 | "[": test 14 | "]": test 15 | _unquoted: test 16 | "foo\"bar": test 17 | "foo#bar": test 18 | "foo'''bar": test 19 | "foo'bar": test 20 | "foo/*bar": test 21 | "foo/*foo*/bar": test 22 | "foo//bar": test 23 | "foo:bar": test 24 | "foo[bar": test 25 | "foo]bar": test 26 | "foo{bar": test 27 | "foo}bar": test 28 | nl1: test 29 | nl2: test 30 | nl3: test 31 | test-key: test 32 | trailing: test 33 | trailing2: test 34 | unquoted_key: test 35 | "{": test 36 | "}": test 37 | } 38 | -------------------------------------------------------------------------------- /assets/kan_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # the comma forces a whitespace check 3 | numbers: 4 | [ 5 | 0 6 | 0 , 7 | -0 8 | 42 , 9 | 42.1 , 10 | -5 11 | -5.1 12 | 17.01e2 13 | -17.01e2 14 | 12345e-3 , 15 | -12345e-3 , 16 | ] 17 | native: 18 | [ 19 | true , 20 | true 21 | false , 22 | false 23 | null , 24 | null 25 | ] 26 | strings: 27 | [ 28 | x 0 29 | .0 30 | 00 31 | 01 32 | 0 0 0 33 | 42 x 34 | 42.1 asdf 35 | 1.2.3 36 | -5 0 - 37 | -5.1 -- 38 | 17.01e2 + 39 | -17.01e2 : 40 | 12345e-3 @ 41 | -12345e-3 $ 42 | true true 43 | x true 44 | false false 45 | x false 46 | null null 47 | x null 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /assets/strings2_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | key1: a key in single quotes 3 | "key 2": a key in single quotes 4 | "key \"": a key in single quotes 5 | text: [ 6 | single quoted string 7 | '''You need quotes for escapes''' 8 | " untrimmed " 9 | "untrimmed " 10 | containing " double quotes 11 | containing " double quotes 12 | containing " double quotes 13 | '''"containing more " double quotes"''' 14 | containing ' single quotes 15 | containing ' single quotes 16 | containing ' single quotes 17 | "'containing more ' single quotes'" 18 | "'containing more ' single quotes'" 19 | "\n" 20 | " \n" 21 | "\n \n \n \n" 22 | "\t\n" 23 | ] 24 | foo3a: asdf''' 25 | foo3b: "'''asdf" 26 | foo4a: "asdf'''\nasdf" 27 | foo4b: "asdf\n'''asdf" 28 | } 29 | -------------------------------------------------------------------------------- /assets/comments_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo1": "This is a string value. # part of the string", 3 | "foo2": "This is a string value.", 4 | "bar1": "This is a string value. // part of the string", 5 | "bar2": "This is a string value.", 6 | "foobar1": "This is a string value./* part of the string */", 7 | "foobar2": "This is a string value.", 8 | "rem1": "# test", 9 | "rem2": "// test", 10 | "rem3": "/* test */", 11 | "num1": 0, 12 | "num2": 0, 13 | "num3": 2, 14 | "true1": true, 15 | "true2": true, 16 | "true3": true, 17 | "false1": false, 18 | "false2": false, 19 | "false3": false, 20 | "null1": null, 21 | "null2": null, 22 | "null3": null, 23 | "str1": "00 # part of the string", 24 | "str2": "00.0 // part of the string", 25 | "str3": "02 /* part of the string */" 26 | } 27 | -------------------------------------------------------------------------------- /assets/sorted/strings2_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | foo3a: asdf''' 3 | foo3b: "'''asdf" 4 | foo4a: "asdf'''\nasdf" 5 | foo4b: "asdf\n'''asdf" 6 | "key \"": a key in single quotes 7 | "key 2": a key in single quotes 8 | key1: a key in single quotes 9 | text: [ 10 | single quoted string 11 | '''You need quotes for escapes''' 12 | " untrimmed " 13 | "untrimmed " 14 | containing " double quotes 15 | containing " double quotes 16 | containing " double quotes 17 | '''"containing more " double quotes"''' 18 | containing ' single quotes 19 | containing ' single quotes 20 | containing ' single quotes 21 | "'containing more ' single quotes'" 22 | "'containing more ' single quotes'" 23 | "\n" 24 | " \n" 25 | "\n \n \n \n" 26 | "\t\n" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /assets/sorted/comments_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "bar1": "This is a string value. // part of the string", 3 | "bar2": "This is a string value.", 4 | "false1": false, 5 | "false2": false, 6 | "false3": false, 7 | "foo1": "This is a string value. # part of the string", 8 | "foo2": "This is a string value.", 9 | "foobar1": "This is a string value./* part of the string */", 10 | "foobar2": "This is a string value.", 11 | "null1": null, 12 | "null2": null, 13 | "null3": null, 14 | "num1": 0, 15 | "num2": 0, 16 | "num3": 2, 17 | "rem1": "# test", 18 | "rem2": "// test", 19 | "rem3": "/* test */", 20 | "str1": "00 # part of the string", 21 | "str2": "00.0 // part of the string", 22 | "str3": "02 /* part of the string */", 23 | "true1": true, 24 | "true2": true, 25 | "true3": true 26 | } 27 | -------------------------------------------------------------------------------- /assets/strings2_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "key1": "a key in single quotes", 3 | "key 2": "a key in single quotes", 4 | "key \"": "a key in single quotes", 5 | "text": [ 6 | "single quoted string", 7 | "You need quotes\tfor escapes", 8 | " untrimmed ", 9 | "untrimmed ", 10 | "containing \" double quotes", 11 | "containing \" double quotes", 12 | "containing \" double quotes", 13 | "\"containing more \" double quotes\"", 14 | "containing ' single quotes", 15 | "containing ' single quotes", 16 | "containing ' single quotes", 17 | "'containing more ' single quotes'", 18 | "'containing more ' single quotes'", 19 | "\n", 20 | " \n", 21 | "\n \n \n \n", 22 | "\t\n" 23 | ], 24 | "foo3a": "asdf'''", 25 | "foo3b": "'''asdf", 26 | "foo4a": "asdf'''\nasdf", 27 | "foo4b": "asdf\n'''asdf" 28 | } 29 | -------------------------------------------------------------------------------- /assets/keys_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "unquoted_key": "test", 3 | "_unquoted": "test", 4 | "test-key": "test", 5 | "-test": "test", 6 | ".key": "test", 7 | "trailing": "test", 8 | "trailing2": "test", 9 | "#c1": "test", 10 | "foo#bar": "test", 11 | "//bar": "test", 12 | "foo//bar": "test", 13 | "/*foo*/": "test", 14 | "foo/*foo*/bar": "test", 15 | "/*": "test", 16 | "foo/*bar": "test", 17 | "\"": "test", 18 | "foo\"bar": "test", 19 | "'''": "test", 20 | "foo'''bar": "test", 21 | "'": "test", 22 | "'foo": "test", 23 | "foo'bar": "test", 24 | ":": "test", 25 | "foo:bar": "test", 26 | "{": "test", 27 | "foo{bar": "test", 28 | "}": "test", 29 | "foo}bar": "test", 30 | "[": "test", 31 | "foo[bar": "test", 32 | "]": "test", 33 | "foo]bar": "test", 34 | "nl1": "test", 35 | "nl2": "test", 36 | "nl3": "test" 37 | } 38 | -------------------------------------------------------------------------------- /assets/sorted/strings2_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo3a": "asdf'''", 3 | "foo3b": "'''asdf", 4 | "foo4a": "asdf'''\nasdf", 5 | "foo4b": "asdf\n'''asdf", 6 | "key \"": "a key in single quotes", 7 | "key 2": "a key in single quotes", 8 | "key1": "a key in single quotes", 9 | "text": [ 10 | "single quoted string", 11 | "You need quotes\tfor escapes", 12 | " untrimmed ", 13 | "untrimmed ", 14 | "containing \" double quotes", 15 | "containing \" double quotes", 16 | "containing \" double quotes", 17 | "\"containing more \" double quotes\"", 18 | "containing ' single quotes", 19 | "containing ' single quotes", 20 | "containing ' single quotes", 21 | "'containing more ' single quotes'", 22 | "'containing more ' single quotes'", 23 | "\n", 24 | " \n", 25 | "\n \n \n \n", 26 | "\t\n" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /assets/sorted/keys_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "\"": "test", 3 | "#c1": "test", 4 | "'": "test", 5 | "'''": "test", 6 | "'foo": "test", 7 | "-test": "test", 8 | ".key": "test", 9 | "/*": "test", 10 | "/*foo*/": "test", 11 | "//bar": "test", 12 | ":": "test", 13 | "[": "test", 14 | "]": "test", 15 | "_unquoted": "test", 16 | "foo\"bar": "test", 17 | "foo#bar": "test", 18 | "foo'''bar": "test", 19 | "foo'bar": "test", 20 | "foo/*bar": "test", 21 | "foo/*foo*/bar": "test", 22 | "foo//bar": "test", 23 | "foo:bar": "test", 24 | "foo[bar": "test", 25 | "foo]bar": "test", 26 | "foo{bar": "test", 27 | "foo}bar": "test", 28 | "nl1": "test", 29 | "nl2": "test", 30 | "nl3": "test", 31 | "test-key": "test", 32 | "trailing": "test", 33 | "trailing2": "test", 34 | "unquoted_key": "test", 35 | "{": "test", 36 | "}": "test" 37 | } 38 | -------------------------------------------------------------------------------- /assets/comments2/strings2_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # Hjson 3 allows the use of single quotes 3 | 4 | key1: a key in single quotes 5 | "key 2": a key in single quotes 6 | "key \"": a key in single quotes 7 | text: [ 8 | single quoted string 9 | '''You need quotes for escapes''' 10 | " untrimmed " 11 | "untrimmed " 12 | containing " double quotes 13 | containing " double quotes 14 | containing " double quotes 15 | '''"containing more " double quotes"''' 16 | containing ' single quotes 17 | containing ' single quotes 18 | containing ' single quotes 19 | "'containing more ' single quotes'" 20 | "'containing more ' single quotes'" 21 | "\n" 22 | " \n" 23 | "\n \n \n \n" 24 | "\t\n" 25 | ] 26 | 27 | # escapes/no escape 28 | 29 | foo3a: asdf''' 30 | foo3b: "'''asdf" 31 | foo4a: "asdf'''\nasdf" 32 | foo4b: "asdf\n'''asdf" 33 | } 34 | -------------------------------------------------------------------------------- /assets/comments3/strings2_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # Hjson 3 allows the use of single quotes 3 | 4 | key1: a key in single quotes 5 | "key 2": a key in single quotes 6 | "key \"": a key in single quotes 7 | 8 | text: [ 9 | single quoted string 10 | '''You need quotes for escapes''' 11 | " untrimmed " 12 | "untrimmed " 13 | containing " double quotes 14 | containing " double quotes 15 | containing " double quotes 16 | '''"containing more " double quotes"''' 17 | containing ' single quotes 18 | containing ' single quotes 19 | containing ' single quotes 20 | "'containing more ' single quotes'" 21 | "'containing more ' single quotes'" 22 | 23 | "\n" 24 | " \n" 25 | "\n \n \n \n" 26 | "\t\n" 27 | ] 28 | 29 | # escapes/no escape 30 | 31 | foo3a: asdf''' 32 | foo3b: "'''asdf" 33 | 34 | foo4a: "asdf'''\nasdf" 35 | foo4b: "asdf\n'''asdf" 36 | } 37 | -------------------------------------------------------------------------------- /assets/comments2/keys_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # unquoted keys 3 | unquoted_key: test 4 | _unquoted: test 5 | test-key: test 6 | -test: test 7 | .key: test 8 | # trailing spaces in key names are ignored 9 | trailing: test 10 | trailing2: test 11 | # comment char in key name 12 | "#c1": test 13 | "foo#bar": test 14 | "//bar": test 15 | "foo//bar": test 16 | "/*foo*/": test 17 | "foo/*foo*/bar": test 18 | "/*": test 19 | "foo/*bar": test 20 | # quotes in key name 21 | "\"": test 22 | "foo\"bar": test 23 | "'''": test 24 | "foo'''bar": test 25 | "'": test 26 | "'foo": test 27 | "foo'bar": test 28 | # control char in key name 29 | ":": test 30 | "foo:bar": test 31 | "{": test 32 | "foo{bar": test 33 | "}": test 34 | "foo}bar": test 35 | "[": test 36 | "foo[bar": test 37 | "]": test 38 | "foo]bar": test 39 | # newline 40 | nl1: test 41 | nl2: test 42 | nl3: test 43 | } 44 | -------------------------------------------------------------------------------- /assets/strings2_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # Hjson 3 allows the use of single quotes 3 | 4 | 'key1': a key in single quotes 5 | 'key 2': a key in single quotes 6 | 'key "': a key in single quotes 7 | 8 | text: [ 9 | 'single quoted string' 10 | 'You need quotes\tfor escapes' 11 | ' untrimmed ' 12 | 'untrimmed ' 13 | 'containing " double quotes' 14 | 'containing \" double quotes' 15 | "containing \" double quotes" 16 | '"containing more " double quotes"' 17 | 'containing \' single quotes' 18 | "containing ' single quotes" 19 | "containing \' single quotes" 20 | "'containing more ' single quotes'" 21 | "\'containing more \' single quotes\'" 22 | 23 | '\n' 24 | ' \n' 25 | '\n \n \n \n' 26 | '\t\n' 27 | ] 28 | 29 | # escapes/no escape 30 | 31 | foo3a: 'asdf\'\'\'' 32 | foo3b: '\'\'\'asdf' 33 | 34 | foo4a: 'asdf\'\'\'\nasdf' 35 | foo4b: 'asdf\n\'\'\'asdf' 36 | } 37 | -------------------------------------------------------------------------------- /assets/comments3/keys_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # unquoted keys 3 | unquoted_key: test 4 | _unquoted: test 5 | test-key: test 6 | -test: test 7 | .key: test 8 | # trailing spaces in key names are ignored 9 | trailing: test 10 | trailing2: test 11 | # comment char in key name 12 | "#c1": test 13 | "foo#bar": test 14 | "//bar": test 15 | "foo//bar": test 16 | "/*foo*/": test 17 | "foo/*foo*/bar": test 18 | "/*": test 19 | "foo/*bar": test 20 | # quotes in key name 21 | "\"": test 22 | "foo\"bar": test 23 | "'''": test 24 | "foo'''bar": test 25 | "'": test 26 | "'foo": test 27 | "foo'bar": test 28 | # control char in key name 29 | ":": test 30 | "foo:bar": test 31 | "{": test 32 | "foo{bar": test 33 | "}": test 34 | "foo}bar": test 35 | "[": test 36 | "foo[bar": test 37 | "]": test 38 | "foo]bar": test 39 | # newline 40 | nl1: 41 | test 42 | nl2: 43 | test 44 | 45 | nl3: 46 | 47 | test 48 | } 49 | -------------------------------------------------------------------------------- /assets/keys_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # unquoted keys 3 | unquoted_key: test 4 | _unquoted: test 5 | test-key: test 6 | -test: test 7 | .key: test 8 | # trailing spaces in key names are ignored 9 | trailing : test 10 | trailing2 : test 11 | # comment char in key name 12 | "#c1": test 13 | "foo#bar": test 14 | "//bar": test 15 | "foo//bar": test 16 | "/*foo*/": test 17 | "foo/*foo*/bar": test 18 | "/*": test 19 | "foo/*bar": test 20 | # quotes in key name 21 | "\"": test 22 | "foo\"bar": test 23 | "'''": test 24 | "foo'''bar": test 25 | "'": test 26 | "'foo": test 27 | "foo'bar": test 28 | # control char in key name 29 | ":": test 30 | "foo:bar": test 31 | "{": test 32 | "foo{bar": test 33 | "}": test 34 | "foo}bar": test 35 | "[": test 36 | "foo[bar": test 37 | "]": test 38 | "foo]bar": test 39 | # newline 40 | nl1: 41 | test 42 | nl2 43 | : 44 | test 45 | 46 | nl3 47 | 48 | : 49 | 50 | test 51 | } 52 | -------------------------------------------------------------------------------- /assets/pass2_result.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | [ 4 | [ 5 | [ 6 | [ 7 | [ 8 | [ 9 | [ 10 | [ 11 | [ 12 | [ 13 | [ 14 | [ 15 | [ 16 | [ 17 | [ 18 | [ 19 | [ 20 | Not too deep 21 | ] 22 | ] 23 | ] 24 | ] 25 | ] 26 | ] 27 | ] 28 | ] 29 | ] 30 | ] 31 | ] 32 | ] 33 | ] 34 | ] 35 | ] 36 | ] 37 | ] 38 | ] 39 | ] 40 | -------------------------------------------------------------------------------- /assets/pass2_result.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | [ 4 | [ 5 | [ 6 | [ 7 | [ 8 | [ 9 | [ 10 | [ 11 | [ 12 | [ 13 | [ 14 | [ 15 | [ 16 | [ 17 | [ 18 | [ 19 | [ 20 | "Not too deep" 21 | ] 22 | ] 23 | ] 24 | ] 25 | ] 26 | ] 27 | ] 28 | ] 29 | ] 30 | ] 31 | ] 32 | ] 33 | ] 34 | ] 35 | ] 36 | ] 37 | ] 38 | ] 39 | ] 40 | -------------------------------------------------------------------------------- /assets/comments2/pass2_result.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | [ 4 | [ 5 | [ 6 | [ 7 | [ 8 | [ 9 | [ 10 | [ 11 | [ 12 | [ 13 | [ 14 | [ 15 | [ 16 | [ 17 | [ 18 | [ 19 | [ 20 | Not too deep 21 | ] 22 | ] 23 | ] 24 | ] 25 | ] 26 | ] 27 | ] 28 | ] 29 | ] 30 | ] 31 | ] 32 | ] 33 | ] 34 | ] 35 | ] 36 | ] 37 | ] 38 | ] 39 | ] 40 | -------------------------------------------------------------------------------- /assets/comments3/pass2_result.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | [ 4 | [ 5 | [ 6 | [ 7 | [ 8 | [ 9 | [ 10 | [ 11 | [ 12 | [ 13 | [ 14 | [ 15 | [ 16 | [ 17 | [ 18 | [ 19 | [ 20 | Not too deep 21 | ] 22 | ] 23 | ] 24 | ] 25 | ] 26 | ] 27 | ] 28 | ] 29 | ] 30 | ] 31 | ] 32 | ] 33 | ] 34 | ] 35 | ] 36 | ] 37 | ] 38 | ] 39 | ] 40 | -------------------------------------------------------------------------------- /assets/sorted/pass2_result.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | [ 4 | [ 5 | [ 6 | [ 7 | [ 8 | [ 9 | [ 10 | [ 11 | [ 12 | [ 13 | [ 14 | [ 15 | [ 16 | [ 17 | [ 18 | [ 19 | [ 20 | Not too deep 21 | ] 22 | ] 23 | ] 24 | ] 25 | ] 26 | ] 27 | ] 28 | ] 29 | ] 30 | ] 31 | ] 32 | ] 33 | ] 34 | ] 35 | ] 36 | ] 37 | ] 38 | ] 39 | ] 40 | -------------------------------------------------------------------------------- /assets/sorted/pass2_result.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | [ 4 | [ 5 | [ 6 | [ 7 | [ 8 | [ 9 | [ 10 | [ 11 | [ 12 | [ 13 | [ 14 | [ 15 | [ 16 | [ 17 | [ 18 | [ 19 | [ 20 | "Not too deep" 21 | ] 22 | ] 23 | ] 24 | ] 25 | ] 26 | ] 27 | ] 28 | ] 29 | ] 30 | ] 31 | ] 32 | ] 33 | ] 34 | ] 35 | ] 36 | ] 37 | ] 38 | ] 39 | ] 40 | -------------------------------------------------------------------------------- /assets/stringify1_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | quotes: { 3 | num1: "1,2" 4 | num2: "-1.1 ," 5 | num3: "1e10 ,2" 6 | num4: "-1e-10," 7 | kw1: "true," 8 | kw2: "false ," 9 | kw3: "null,123" 10 | close1: "1}" 11 | close1b: "1 }" 12 | close2: "1]" 13 | close2b: "1 ]" 14 | close3: "1," 15 | close3b: "1 ," 16 | comment1: "1#str" 17 | comment2: "1//str" 18 | comment3: "1/*str*/" 19 | punc1: "{" 20 | punc1b: "{foo" 21 | punc2: "}" 22 | punc2b: "}foo" 23 | punc3: "[" 24 | punc3b: "[foo" 25 | punc4: "]" 26 | punc4b: "]foo" 27 | punc5: "," 28 | punc5b: ",foo" 29 | punc6: ":" 30 | punc6b: ":foo" 31 | } 32 | noquotes: { 33 | num0: .1,2 34 | num1: 1.1.1,2 35 | num2: -.1, 36 | num3: 1e10e,2 37 | num4: -1e--10, 38 | kw1: true1, 39 | kw2: false0, 40 | kw3: null0, 41 | close1: a} 42 | close2: a] 43 | comment1: a#str 44 | comment2: a//str 45 | comment3: a/*str*/ 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /assets/sorted/stringify1_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | noquotes: { 3 | close1: a} 4 | close2: a] 5 | comment1: a#str 6 | comment2: a//str 7 | comment3: a/*str*/ 8 | kw1: true1, 9 | kw2: false0, 10 | kw3: null0, 11 | num0: .1,2 12 | num1: 1.1.1,2 13 | num2: -.1, 14 | num3: 1e10e,2 15 | num4: -1e--10, 16 | } 17 | quotes: { 18 | close1: "1}" 19 | close1b: "1 }" 20 | close2: "1]" 21 | close2b: "1 ]" 22 | close3: "1," 23 | close3b: "1 ," 24 | comment1: "1#str" 25 | comment2: "1//str" 26 | comment3: "1/*str*/" 27 | kw1: "true," 28 | kw2: "false ," 29 | kw3: "null,123" 30 | num1: "1,2" 31 | num2: "-1.1 ," 32 | num3: "1e10 ,2" 33 | num4: "-1e-10," 34 | punc1: "{" 35 | punc1b: "{foo" 36 | punc2: "}" 37 | punc2b: "}foo" 38 | punc3: "[" 39 | punc3b: "[foo" 40 | punc4: "]" 41 | punc4b: "]foo" 42 | punc5: "," 43 | punc5b: ",foo" 44 | punc6: ":" 45 | punc6b: ":foo" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /assets/comments2/stringify1_result.hjson: -------------------------------------------------------------------------------- 1 | // test if stringify produces correct output 2 | { 3 | quotes: { 4 | num1: "1,2" 5 | num2: "-1.1 ," 6 | num3: "1e10 ,2" 7 | num4: "-1e-10," 8 | kw1: "true," 9 | kw2: "false ," 10 | kw3: "null,123" 11 | close1: "1}" 12 | close1b: "1 }" 13 | close2: "1]" 14 | close2b: "1 ]" 15 | close3: "1," 16 | close3b: "1 ," 17 | comment1: "1#str" 18 | comment2: "1//str" 19 | comment3: "1/*str*/" 20 | punc1: "{" 21 | punc1b: "{foo" 22 | punc2: "}" 23 | punc2b: "}foo" 24 | punc3: "[" 25 | punc3b: "[foo" 26 | punc4: "]" 27 | punc4b: "]foo" 28 | punc5: "," 29 | punc5b: ",foo" 30 | punc6: ":" 31 | punc6b: ":foo" 32 | } 33 | noquotes: { 34 | num0: .1,2 35 | num1: 1.1.1,2 36 | num2: -.1, 37 | num3: 1e10e,2 38 | num4: -1e--10, 39 | kw1: true1, 40 | kw2: false0, 41 | kw3: null0, 42 | close1: a} 43 | close2: a] 44 | comment1: a#str 45 | comment2: a//str 46 | comment3: a/*str*/ 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /assets/comments3/stringify1_result.hjson: -------------------------------------------------------------------------------- 1 | // test if stringify produces correct output 2 | { 3 | quotes: 4 | { 5 | num1: "1,2" 6 | num2: "-1.1 ," 7 | num3: "1e10 ,2" 8 | num4: "-1e-10," 9 | kw1: "true," 10 | kw2: "false ," 11 | kw3: "null,123" 12 | close1: "1}" 13 | close1b: "1 }" 14 | close2: "1]" 15 | close2b: "1 ]" 16 | close3: "1," 17 | close3b: "1 ," 18 | comment1: "1#str" 19 | comment2: "1//str" 20 | comment3: "1/*str*/" 21 | punc1: "{" 22 | punc1b: "{foo" 23 | punc2: "}" 24 | punc2b: "}foo" 25 | punc3: "[" 26 | punc3b: "[foo" 27 | punc4: "]" 28 | punc4b: "]foo" 29 | punc5: "," 30 | punc5b: ",foo" 31 | punc6: ":" 32 | punc6b: ":foo" 33 | } 34 | noquotes: 35 | { 36 | num0: .1,2 37 | num1: 1.1.1,2 38 | num2: -.1, 39 | num3: 1e10e,2 40 | num4: -1e--10, 41 | kw1: true1, 42 | kw2: false0, 43 | kw3: null0, 44 | close1: a} 45 | close2: a] 46 | comment1: a#str 47 | comment2: a//str 48 | comment3: a/*str*/ 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /assets/stringify1_test.hjson: -------------------------------------------------------------------------------- 1 | // test if stringify produces correct output 2 | { 3 | quotes: 4 | { 5 | num1: "1,2" 6 | num2: "-1.1 ," 7 | num3: "1e10 ,2" 8 | num4: "-1e-10," 9 | kw1: "true," 10 | kw2: "false ," 11 | kw3: "null,123" 12 | close1: "1}" 13 | close1b: "1 }" 14 | close2: "1]" 15 | close2b: "1 ]" 16 | close3: "1," 17 | close3b: "1 ," 18 | comment1: "1#str" 19 | comment2: "1//str" 20 | comment3: "1/*str*/" 21 | punc1: "{" 22 | punc1b: "{foo" 23 | punc2: "}" 24 | punc2b: "}foo" 25 | punc3: "[" 26 | punc3b: "[foo" 27 | punc4: "]" 28 | punc4b: "]foo" 29 | punc5: "," 30 | punc5b: ",foo" 31 | punc6: ":" 32 | punc6b: ":foo" 33 | } 34 | noquotes: 35 | { 36 | num0: ".1,2" 37 | num1: "1.1.1,2" 38 | num2: "-.1," 39 | num3: "1e10e,2" 40 | num4: "-1e--10," 41 | kw1: "true1," 42 | kw2: "false0," 43 | kw3: "null0," 44 | close1: "a}" 45 | close2: "a]" 46 | comment1: "a#str" 47 | comment2: "a//str" 48 | comment3: "a/*str*/" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016, 2017 Christian Zangl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/comments4_test.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ [ #before1 3 | /*key1keycm*/a string value // still part of the string 4 | /* key2keycm */ "a string value" // not part of the string 5 | // map1before 6 | /* map1key */ 7 | {}//map2after 8 | {} 9 | { 10 | // map3 inner comment 11 | } 12 | [] 13 | // map4before 14 | /*map4key*/{ 15 | /* map4inner */ 16 | } // map4after 17 | //map5before 18 | /*map5key*/ { 19 | //map5ab4 20 | val5a: /* map5akey */ 1, // map5aAfter 21 | val5b: 2 /* map5bb4comma */ , #map5bAfter 22 | #map5extra 23 | } /* map5after */ 24 | // vec1bbefore 25 | /* vec1bkey */ 26 | []//vec1bafter 27 | [] 28 | [ 29 | // vec3 inner comment 30 | ] 31 | // vec4before 32 | /*vec4key*/[ 33 | /* vec4inner */ 34 | ] // vec4after 35 | //vec5before 36 | /*vec5key*/ [ 37 | //vec5ab4 38 | 1, // vec5aAfter 39 | 2 /* vec5bb4comma */ , #vec5bAfter 40 | #vec5extra 41 | ] /* map5after */ 42 | // before3 43 | 44 | 3 # after3 45 | # before4 46 | /*before4b*/4/*after4*/ 47 | #after4b 48 | ] 49 | // after2 50 | 51 | /* after3 */ 52 | -------------------------------------------------------------------------------- /assets/comments2/comments4_result.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ [ #before1 3 | /*key1keycm*/a string value // still part of the string 4 | /* key2keycm */ "a string value" // not part of the string 5 | // map1before 6 | /* map1key */ 7 | {}//map2after 8 | {} 9 | { 10 | // map3 inner comment 11 | } 12 | [] 13 | // map4before 14 | /*map4key*/{ 15 | /* map4inner */ 16 | } // map4after 17 | //map5before 18 | /*map5key*/ { 19 | //map5ab4 20 | val5a: /* map5akey */ 1 // map5aAfter 21 | val5b: 2 /* map5bb4comma */ #map5bAfter 22 | #map5extra 23 | } /* map5after */ 24 | // vec1bbefore 25 | /* vec1bkey */ 26 | []//vec1bafter 27 | [] 28 | [ 29 | // vec3 inner comment 30 | ] 31 | // vec4before 32 | /*vec4key*/[ 33 | /* vec4inner */ 34 | ] // vec4after 35 | //vec5before 36 | /*vec5key*/ [ 37 | //vec5ab4 38 | 1 // vec5aAfter 39 | 2 /* vec5bb4comma */ #vec5bAfter 40 | #vec5extra 41 | ] /* map5after */ 42 | // before3 43 | 44 | 3 # after3 45 | # before4 46 | /*before4b*/4/*after4*/ 47 | #after4b 48 | ] 49 | // after2 50 | 51 | /* after3 */ 52 | -------------------------------------------------------------------------------- /assets/stringify1_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "quotes": { 3 | "num1": "1,2", 4 | "num2": "-1.1 ,", 5 | "num3": "1e10 ,2", 6 | "num4": "-1e-10,", 7 | "kw1": "true,", 8 | "kw2": "false ,", 9 | "kw3": "null,123", 10 | "close1": "1}", 11 | "close1b": "1 }", 12 | "close2": "1]", 13 | "close2b": "1 ]", 14 | "close3": "1,", 15 | "close3b": "1 ,", 16 | "comment1": "1#str", 17 | "comment2": "1//str", 18 | "comment3": "1/*str*/", 19 | "punc1": "{", 20 | "punc1b": "{foo", 21 | "punc2": "}", 22 | "punc2b": "}foo", 23 | "punc3": "[", 24 | "punc3b": "[foo", 25 | "punc4": "]", 26 | "punc4b": "]foo", 27 | "punc5": ",", 28 | "punc5b": ",foo", 29 | "punc6": ":", 30 | "punc6b": ":foo" 31 | }, 32 | "noquotes": { 33 | "num0": ".1,2", 34 | "num1": "1.1.1,2", 35 | "num2": "-.1,", 36 | "num3": "1e10e,2", 37 | "num4": "-1e--10,", 38 | "kw1": "true1,", 39 | "kw2": "false0,", 40 | "kw3": "null0,", 41 | "close1": "a}", 42 | "close2": "a]", 43 | "comment1": "a#str", 44 | "comment2": "a//str", 45 | "comment3": "a/*str*/" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /assets/comments3/comments4_result.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ [ #before1 3 | /*key1keycm*/a string value // still part of the string 4 | /* key2keycm */ "a string value" // not part of the string 5 | // map1before 6 | /* map1key */ 7 | {}//map2after 8 | {} 9 | { 10 | // map3 inner comment 11 | } 12 | [] 13 | // map4before 14 | /*map4key*/{ 15 | /* map4inner */ 16 | } // map4after 17 | //map5before 18 | /*map5key*/ { 19 | //map5ab4 20 | val5a: /* map5akey */ 1 // map5aAfter 21 | val5b: 2 /* map5bb4comma */ #map5bAfter 22 | #map5extra 23 | } /* map5after */ 24 | // vec1bbefore 25 | /* vec1bkey */ 26 | []//vec1bafter 27 | [] 28 | [ 29 | // vec3 inner comment 30 | ] 31 | // vec4before 32 | /*vec4key*/[ 33 | /* vec4inner */ 34 | ] // vec4after 35 | //vec5before 36 | /*vec5key*/ [ 37 | //vec5ab4 38 | 1 // vec5aAfter 39 | 2 /* vec5bb4comma */ #vec5bAfter 40 | #vec5extra 41 | ] /* map5after */ 42 | // before3 43 | 44 | 3 # after3 45 | # before4 46 | /*before4b*/4/*after4*/ 47 | #after4b 48 | ] 49 | // after2 50 | 51 | /* after3 */ 52 | -------------------------------------------------------------------------------- /assets/sorted/stringify1_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "noquotes": { 3 | "close1": "a}", 4 | "close2": "a]", 5 | "comment1": "a#str", 6 | "comment2": "a//str", 7 | "comment3": "a/*str*/", 8 | "kw1": "true1,", 9 | "kw2": "false0,", 10 | "kw3": "null0,", 11 | "num0": ".1,2", 12 | "num1": "1.1.1,2", 13 | "num2": "-.1,", 14 | "num3": "1e10e,2", 15 | "num4": "-1e--10," 16 | }, 17 | "quotes": { 18 | "close1": "1}", 19 | "close1b": "1 }", 20 | "close2": "1]", 21 | "close2b": "1 ]", 22 | "close3": "1,", 23 | "close3b": "1 ,", 24 | "comment1": "1#str", 25 | "comment2": "1//str", 26 | "comment3": "1/*str*/", 27 | "kw1": "true,", 28 | "kw2": "false ,", 29 | "kw3": "null,123", 30 | "num1": "1,2", 31 | "num2": "-1.1 ,", 32 | "num3": "1e10 ,2", 33 | "num4": "-1e-10,", 34 | "punc1": "{", 35 | "punc1b": "{foo", 36 | "punc2": "}", 37 | "punc2b": "}foo", 38 | "punc3": "[", 39 | "punc3b": "[foo", 40 | "punc4": "]", 41 | "punc4b": "]foo", 42 | "punc5": ",", 43 | "punc5b": ",foo", 44 | "punc6": ":", 45 | "punc6b": ":foo" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /assets/comments2/comments_result.hjson: -------------------------------------------------------------------------------- 1 | // test 2 | # all 3 | // comment 4 | /* 5 | styles 6 | */ 7 | # with lf 8 | 9 | 10 | 11 | # ! 12 | 13 | { 14 | # hjson style comment 15 | foo1: This is a string value. # part of the string 16 | foo2: "This is a string value." # a comment 17 | 18 | // js style comment 19 | bar1: This is a string value. // part of the string 20 | bar2: "This is a string value." // a comment 21 | 22 | /* js block style comments */foobar1:/* more */This is a string value./* part of the string */ 23 | /* js block style comments */foobar2:/* more */"This is a string value."/* a comment */ 24 | rem1: "# test" 25 | rem2: "// test" 26 | rem3: "/* test */" 27 | num1: 0 # comment 28 | num2: 0 // comment 29 | num3: 2 /* comment */ 30 | true1: true # comment 31 | true2: true // comment 32 | true3: true /* comment */ 33 | false1: false # comment 34 | false2: false // comment 35 | false3: false /* comment */ 36 | null1: null # comment 37 | null2: null // comment 38 | null3: null /* comment */ 39 | str1: 00 # part of the string 40 | str2: 00.0 // part of the string 41 | str3: 02 /* part of the string */ 42 | } 43 | -------------------------------------------------------------------------------- /assets/comments_test.hjson: -------------------------------------------------------------------------------- 1 | // test 2 | # all 3 | // comment 4 | /* 5 | styles 6 | */ 7 | # with lf 8 | 9 | 10 | 11 | # ! 12 | 13 | { 14 | # hjson style comment 15 | foo1: This is a string value. # part of the string 16 | foo2: "This is a string value." # a comment 17 | 18 | // js style comment 19 | bar1: This is a string value. // part of the string 20 | bar2: "This is a string value." // a comment 21 | 22 | /* js block style comments */foobar1:/* more */This is a string value./* part of the string */ 23 | /* js block style comments */foobar2:/* more */"This is a string value."/* a comment */ 24 | 25 | rem1: "# test" 26 | rem2: "// test" 27 | rem3: "/* test */" 28 | 29 | num1: 0 # comment 30 | num2: 0.0 // comment 31 | num3: 2 /* comment */ 32 | 33 | true1: true # comment 34 | true2: true // comment 35 | true3: true /* comment */ 36 | 37 | false1: false # comment 38 | false2: false // comment 39 | false3: false /* comment */ 40 | 41 | null1: null # comment 42 | null2: null // comment 43 | null3: null /* comment */ 44 | 45 | str1: 00 # part of the string 46 | str2: 00.0 // part of the string 47 | str3: 02 /* part of the string */ 48 | } 49 | -------------------------------------------------------------------------------- /assets/comments3/comments_result.hjson: -------------------------------------------------------------------------------- 1 | // test 2 | # all 3 | // comment 4 | /* 5 | styles 6 | */ 7 | # with lf 8 | 9 | 10 | 11 | # ! 12 | 13 | { 14 | # hjson style comment 15 | foo1: This is a string value. # part of the string 16 | foo2: "This is a string value." # a comment 17 | 18 | // js style comment 19 | bar1: This is a string value. // part of the string 20 | bar2: "This is a string value." // a comment 21 | 22 | /* js block style comments */foobar1:/* more */This is a string value./* part of the string */ 23 | /* js block style comments */foobar2:/* more */"This is a string value."/* a comment */ 24 | 25 | rem1: "# test" 26 | rem2: "// test" 27 | rem3: "/* test */" 28 | 29 | num1: 0 # comment 30 | num2: 0 // comment 31 | num3: 2 /* comment */ 32 | 33 | true1: true # comment 34 | true2: true // comment 35 | true3: true /* comment */ 36 | 37 | false1: false # comment 38 | false2: false // comment 39 | false3: false /* comment */ 40 | 41 | null1: null # comment 42 | null2: null // comment 43 | null3: null /* comment */ 44 | 45 | str1: 00 # part of the string 46 | str2: 00.0 // part of the string 47 | str3: 02 /* part of the string */ 48 | } 49 | -------------------------------------------------------------------------------- /assets/comments5_test.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ { #before1 3 | key1:/*key1keycm*/a string value // still part of the string 4 | key2: /* key2keycm */ "a string value" // not part of the string 5 | // map1before 6 | map1: /* map1key */ 7 | {}//map2after 8 | map2: {} 9 | map3: { 10 | // map3 inner comment 11 | } 12 | vec1: [] 13 | // map4before 14 | map4:/*map4key*/{ 15 | /* map4inner */ 16 | } // map4after 17 | //map5before 18 | map5: /*map5key*/ { 19 | //map5ab4 20 | val5a: /* map5akey */ 1, // map5aAfter 21 | val5b: 2 /* map5bb4comma */ , #map5bAfter 22 | #map5extra 23 | } /* map5after */ 24 | // vec1bbefore 25 | vec1b: /* vec1bkey */ 26 | []//vec1bafter 27 | vec2: [] 28 | vec3: [ 29 | // vec3 inner comment 30 | ] 31 | // vec4before 32 | vec4:/*vec4key*/[ 33 | /* vec4inner */ 34 | ] // vec4after 35 | //vec5before 36 | vec5: /*vec5key*/ [ 37 | //vec5ab4 38 | 1, // vec5aAfter 39 | 2 /* vec5bb4comma */ , #vec5bAfter 40 | #vec5extra 41 | ] /* map5after */ 42 | // before3 43 | 44 | key3 : 3 # after3 45 | # before4 46 | /*before4b*/key4:4/*after4*/ 47 | #after4b 48 | } 49 | // after2 50 | 51 | /* after3 */ 52 | -------------------------------------------------------------------------------- /assets/comments2/comments5_result.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ { #before1 3 | key1:/*key1keycm*/a string value // still part of the string 4 | key2: /* key2keycm */ "a string value" // not part of the string 5 | // map1before 6 | map1: /* map1key */ 7 | {}//map2after 8 | map2: {} 9 | map3: { 10 | // map3 inner comment 11 | } 12 | vec1: [] 13 | // map4before 14 | map4:/*map4key*/{ 15 | /* map4inner */ 16 | } // map4after 17 | //map5before 18 | map5: /*map5key*/ { 19 | //map5ab4 20 | val5a: /* map5akey */ 1 // map5aAfter 21 | val5b: 2 /* map5bb4comma */ #map5bAfter 22 | #map5extra 23 | } /* map5after */ 24 | // vec1bbefore 25 | vec1b: /* vec1bkey */ 26 | []//vec1bafter 27 | vec2: [] 28 | vec3: [ 29 | // vec3 inner comment 30 | ] 31 | // vec4before 32 | vec4:/*vec4key*/[ 33 | /* vec4inner */ 34 | ] // vec4after 35 | //vec5before 36 | vec5: /*vec5key*/ [ 37 | //vec5ab4 38 | 1 // vec5aAfter 39 | 2 /* vec5bb4comma */ #vec5bAfter 40 | #vec5extra 41 | ] /* map5after */ 42 | // before3 43 | 44 | key3: 3 # after3 45 | # before4 46 | /*before4b*/key4: 4/*after4*/ 47 | #after4b 48 | } 49 | // after2 50 | 51 | /* after3 */ 52 | -------------------------------------------------------------------------------- /assets/comments3/comments5_result.hjson: -------------------------------------------------------------------------------- 1 | // before 2 | /* before2 */ { #before1 3 | key1:/*key1keycm*/a string value // still part of the string 4 | key2: /* key2keycm */ "a string value" // not part of the string 5 | // map1before 6 | map1: /* map1key */ 7 | {}//map2after 8 | map2: {} 9 | map3: { 10 | // map3 inner comment 11 | } 12 | vec1: [] 13 | // map4before 14 | map4:/*map4key*/{ 15 | /* map4inner */ 16 | } // map4after 17 | //map5before 18 | map5: /*map5key*/ { 19 | //map5ab4 20 | val5a: /* map5akey */ 1 // map5aAfter 21 | val5b: 2 /* map5bb4comma */ #map5bAfter 22 | #map5extra 23 | } /* map5after */ 24 | // vec1bbefore 25 | vec1b: /* vec1bkey */ 26 | []//vec1bafter 27 | vec2: [] 28 | vec3: [ 29 | // vec3 inner comment 30 | ] 31 | // vec4before 32 | vec4:/*vec4key*/[ 33 | /* vec4inner */ 34 | ] // vec4after 35 | //vec5before 36 | vec5: /*vec5key*/ [ 37 | //vec5ab4 38 | 1 // vec5aAfter 39 | 2 /* vec5bb4comma */ #vec5bAfter 40 | #vec5extra 41 | ] /* map5after */ 42 | // before3 43 | 44 | key3: 3 # after3 45 | # before4 46 | /*before4b*/key4: 4/*after4*/ 47 | #after4b 48 | } 49 | // after2 50 | 51 | /* after3 */ 52 | -------------------------------------------------------------------------------- /assets/sorted/strings_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | arr: [ 3 | one 4 | two 5 | three 6 | four 7 | ] 8 | foo1a: asdf\"'a\s\w 9 | foo1b: asdf\"'a\s\w 10 | foo1c: asdf\"'a\s\w 11 | foo2a: '''"asdf"''' 12 | foo2b: '''"asdf"''' 13 | foo3a: asdf''' 14 | foo3b: "'''asdf" 15 | foo4a: "asdf'''\nasdf" 16 | foo4b: "asdf\n'''asdf" 17 | multiline1: 18 | ''' 19 | first line 20 | indented line 21 | last line 22 | ''' 23 | multiline2: 24 | ''' 25 | first line 26 | indented line 27 | last line 28 | ''' 29 | multiline3: 30 | ''' 31 | first line 32 | indented line 33 | last line 34 | 35 | ''' 36 | multiline4: ←→±≠Я 37 | not: { 38 | array: [ 39 | 1 40 | 2 41 | 3 42 | 4 43 | 5 44 | 6 45 | 7 46 | 8 47 | 9 48 | 0 49 | -1 50 | 0.5 51 | ] 52 | negative: -4.2 53 | no: false 54 | null: null 55 | number: 5 56 | yes: true 57 | } 58 | notml1: "\n" 59 | notml2: " \n" 60 | notml3: "\n \n \n \n" 61 | notml4: "\t\n" 62 | special: { 63 | false: "false" 64 | minus: "-3" 65 | null: "null" 66 | one: "1" 67 | true: "true" 68 | two: "2" 69 | zero: "0" 70 | } 71 | text1: This is a valid string value. 72 | text2: a \ is just a \ 73 | text3: '''You need quotes for escapes''' 74 | text4a: " untrimmed " 75 | text4b: " untrimmed" 76 | text4c: "untrimmed " 77 | } 78 | -------------------------------------------------------------------------------- /assets/sorted/strings_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "arr": [ 3 | "one", 4 | "two", 5 | "three", 6 | "four" 7 | ], 8 | "foo1a": "asdf\\\"'a\\s\\w", 9 | "foo1b": "asdf\\\"'a\\s\\w", 10 | "foo1c": "asdf\\\"'a\\s\\w", 11 | "foo2a": "\"asdf\"", 12 | "foo2b": "\"asdf\"", 13 | "foo3a": "asdf'''", 14 | "foo3b": "'''asdf", 15 | "foo4a": "asdf'''\nasdf", 16 | "foo4b": "asdf\n'''asdf", 17 | "multiline1": "first line\n indented line\nlast line", 18 | "multiline2": "first line\n indented line\nlast line", 19 | "multiline3": "first line\n indented line\nlast line\n", 20 | "multiline4": "←→±≠Я", 21 | "not": { 22 | "array": [ 23 | 1, 24 | 2, 25 | 3, 26 | 4, 27 | 5, 28 | 6, 29 | 7, 30 | 8, 31 | 9, 32 | 0, 33 | -1, 34 | 0.5 35 | ], 36 | "negative": -4.2, 37 | "no": false, 38 | "null": null, 39 | "number": 5, 40 | "yes": true 41 | }, 42 | "notml1": "\n", 43 | "notml2": " \n", 44 | "notml3": "\n \n \n \n", 45 | "notml4": "\t\n", 46 | "special": { 47 | "false": "false", 48 | "minus": "-3", 49 | "null": "null", 50 | "one": "1", 51 | "true": "true", 52 | "two": "2", 53 | "zero": "0" 54 | }, 55 | "text1": "This is a valid string value.", 56 | "text2": "a \\ is just a \\", 57 | "text3": "You need quotes\tfor escapes", 58 | "text4a": " untrimmed ", 59 | "text4b": " untrimmed", 60 | "text4c": "untrimmed " 61 | } 62 | -------------------------------------------------------------------------------- /assets/strings_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | text1: This is a valid string value. 3 | text2: a \ is just a \ 4 | text3: '''You need quotes for escapes''' 5 | text4a: " untrimmed " 6 | text4b: " untrimmed" 7 | text4c: "untrimmed " 8 | notml1: "\n" 9 | notml2: " \n" 10 | notml3: "\n \n \n \n" 11 | notml4: "\t\n" 12 | multiline1: 13 | ''' 14 | first line 15 | indented line 16 | last line 17 | ''' 18 | multiline2: 19 | ''' 20 | first line 21 | indented line 22 | last line 23 | ''' 24 | multiline3: 25 | ''' 26 | first line 27 | indented line 28 | last line 29 | 30 | ''' 31 | multiline4: ←→±≠Я 32 | foo1a: asdf\"'a\s\w 33 | foo1b: asdf\"'a\s\w 34 | foo1c: asdf\"'a\s\w 35 | foo2a: '''"asdf"''' 36 | foo2b: '''"asdf"''' 37 | foo3a: asdf''' 38 | foo3b: "'''asdf" 39 | foo4a: "asdf'''\nasdf" 40 | foo4b: "asdf\n'''asdf" 41 | arr: [ 42 | one 43 | two 44 | three 45 | four 46 | ] 47 | not: { 48 | number: 5 49 | negative: -4.2 50 | yes: true 51 | no: false 52 | null: null 53 | array: 54 | [ 55 | 1 56 | 2 57 | 3 58 | 4 59 | 5 60 | 6 61 | 7 62 | 8 63 | 9 64 | 0 65 | -1 66 | 0.5 67 | ] 68 | } 69 | special: { 70 | true: "true" 71 | false: "false" 72 | null: "null" 73 | one: "1" 74 | two: "2" 75 | minus: "-3" 76 | zero: "0" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /assets/strings_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "text1": "This is a valid string value.", 3 | "text2": "a \\ is just a \\", 4 | "text3": "You need quotes\tfor escapes", 5 | "text4a": " untrimmed ", 6 | "text4b": " untrimmed", 7 | "text4c": "untrimmed ", 8 | "notml1": "\n", 9 | "notml2": " \n", 10 | "notml3": "\n \n \n \n", 11 | "notml4": "\t\n", 12 | "multiline1": "first line\n indented line\nlast line", 13 | "multiline2": "first line\n indented line\nlast line", 14 | "multiline3": "first line\n indented line\nlast line\n", 15 | "multiline4": "←→±≠Я", 16 | 17 | "foo1a": "asdf\\\"'a\\s\\w", 18 | "foo1b": "asdf\\\"'a\\s\\w", 19 | "foo1c": "asdf\\\"'a\\s\\w", 20 | "foo2a": "\"asdf\"", 21 | "foo2b": "\"asdf\"", 22 | "foo3a": "asdf'''", 23 | "foo3b": "'''asdf", 24 | "foo4a": "asdf'''\nasdf", 25 | "foo4b": "asdf\n'''asdf", 26 | "arr": [ 27 | "one", 28 | "two", 29 | "three", 30 | "four" 31 | ], 32 | "not": { 33 | "number": 5, 34 | "negative": -4.2, 35 | "yes": true, 36 | "no": false, 37 | "null": null, 38 | "array": [ 39 | 1, 40 | 2, 41 | 3, 42 | 4, 43 | 5, 44 | 6, 45 | 7, 46 | 8, 47 | 9, 48 | 0, 49 | -1, 50 | 0.5 51 | ] 52 | }, 53 | "special": { 54 | "true": "true", 55 | "false": "false", 56 | "null": "null", 57 | "one": "1", 58 | "two": "2", 59 | "minus": "-3", 60 | "zero": "0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /assets/pass1_result.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | JSON Test Pattern pass1 3 | { 4 | "object with 1 member": [ 5 | array with 1 element 6 | ] 7 | } 8 | {} 9 | [] 10 | -42 11 | true 12 | false 13 | null 14 | { 15 | integer: 1234567890 16 | real: -9876.54321 17 | e: 1.23456789e-13 18 | E: 1.23456789e+34 19 | -: 2.3456789012e+76 20 | zero: 0 21 | one: 1 22 | space: " " 23 | quote: '''"''' 24 | backslash: \ 25 | controls: "\b\f\n\r\t" 26 | slash: / & / 27 | alpha: abcdefghijklmnopqrstuvwyz 28 | ALPHA: ABCDEFGHIJKLMNOPQRSTUVWYZ 29 | digit: 0123456789 30 | 0123456789: digit 31 | special: `1~!@#$%^&*()_+-={':[,]}|;.? 32 | hex: ģ䕧覫췯ꯍ 33 | true: true 34 | false: false 35 | null: null 36 | array: [] 37 | object: {} 38 | address: 50 St. James Street 39 | url: http://www.JSON.org/ 40 | comment: "// /* */": " " 42 | " s p a c e d ": [ 43 | 1 44 | 2 45 | 3 46 | 4 47 | 5 48 | 6 49 | 7 50 | ] 51 | compact: [ 52 | 1 53 | 2 54 | 3 55 | 4 56 | 5 57 | 6 58 | 7 59 | ] 60 | jsontext: '''{"object with 1 member":["array with 1 element"]}''' 61 | quotes: " " %22 0x22 034 " 62 | "/\\\"쫾몾ꮘﳞ볚\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?": A key can be any string 63 | } 64 | 0.5 65 | 98.6 66 | 99.44 67 | 1066 68 | 10 69 | 1 70 | 0.1 71 | 1 72 | 2 73 | 2 74 | rosebud 75 | ] 76 | -------------------------------------------------------------------------------- /assets/sorted/pass1_result.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | JSON Test Pattern pass1 3 | { 4 | "object with 1 member": [ 5 | array with 1 element 6 | ] 7 | } 8 | {} 9 | [] 10 | -42 11 | true 12 | false 13 | null 14 | { 15 | " s p a c e d ": [ 16 | 1 17 | 2 18 | 3 19 | 4 20 | 5 21 | 6 22 | 7 23 | ] 24 | "# -- --> */": " " 25 | -: 2.3456789012e+76 26 | "/\\\"쫾몾ꮘﳞ볚\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?": A key can be any string 27 | 0123456789: digit 28 | ALPHA: ABCDEFGHIJKLMNOPQRSTUVWYZ 29 | E: 1.23456789e+34 30 | address: 50 St. James Street 31 | alpha: abcdefghijklmnopqrstuvwyz 32 | array: [] 33 | backslash: \ 34 | comment: "// /* */": " " 42 | " s p a c e d ": [ 43 | 1 44 | 2 45 | 3 46 | 4 47 | 5 48 | 6 49 | 7 50 | ] 51 | compact: [ 52 | 1 53 | 2 54 | 3 55 | 4 56 | 5 57 | 6 58 | 7 59 | ] 60 | jsontext: '''{"object with 1 member":["array with 1 element"]}''' 61 | quotes: " " %22 0x22 034 " 62 | "/\\\"쫾몾ꮘﳞ볚\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?": A key can be any string 63 | } 64 | 0.5 65 | 98.6 66 | 99.44 67 | 1066 68 | 10 69 | 1 70 | 0.1 71 | 1 72 | 2 73 | 2 74 | rosebud 75 | ] 76 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | test: 14 | name: ${{ matrix.name }} 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | include: 20 | - name: ubuntu 21 | os: ubuntu-latest 22 | go-version: 'stable' 23 | - name: mac 24 | os: macos-latest 25 | go-version: 'stable' 26 | - name: windows 27 | os: windows-latest 28 | go-version: 'stable' 29 | - name: ubuntu go 1.12 30 | os: ubuntu-latest 31 | go-version: '1.12.x' 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: actions/setup-go@v5 35 | with: 36 | go-version: ${{ matrix.go-version }} 37 | - run: go version 38 | - run: go test -v 39 | - run: cd hjson-cli && go build 40 | - if: runner.os == 'Linux' 41 | run: hjson-cli/hjson-cli assets/comments_test.hjson | diff assets/sorted/comments_result.hjson - 42 | - if: runner.os == 'Linux' 43 | run: hjson-cli/hjson-cli -j assets/comments_test.hjson | diff assets/sorted/comments_result.json - 44 | - if: runner.os == 'Linux' 45 | run: hjson-cli/hjson-cli -preserveKeyOrder assets/comments_test.hjson | diff assets/comments_result.hjson - 46 | - if: runner.os == 'Linux' 47 | run: hjson-cli/hjson-cli -j -preserveKeyOrder assets/comments_test.hjson | diff assets/comments_result.json - 48 | -------------------------------------------------------------------------------- /assets/comments2/strings_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # simple 3 | 4 | text1: This is a valid string value. 5 | text2: a \ is just a \ 6 | text3: '''You need quotes for escapes''' 7 | text4a: " untrimmed " 8 | text4b: " untrimmed" 9 | text4c: "untrimmed " 10 | notml1: "\n" 11 | notml2: " \n" 12 | notml3: "\n \n \n \n" 13 | notml4: "\t\n" 14 | 15 | # multiline string 16 | 17 | multiline1: 18 | ''' 19 | first line 20 | indented line 21 | last line 22 | ''' 23 | multiline2: 24 | ''' 25 | first line 26 | indented line 27 | last line 28 | ''' 29 | multiline3: 30 | ''' 31 | first line 32 | indented line 33 | last line 34 | 35 | ''' # trailing lf 36 | multiline4: ←→±≠Я 37 | 38 | # escapes/no escape 39 | 40 | foo1a: asdf\"'a\s\w 41 | foo1b: asdf\"'a\s\w 42 | foo1c: asdf\"'a\s\w 43 | foo2a: '''"asdf"''' 44 | foo2b: '''"asdf"''' 45 | foo3a: asdf''' 46 | foo3b: "'''asdf" 47 | foo4a: "asdf'''\nasdf" 48 | foo4b: "asdf\n'''asdf" 49 | 50 | # in arrays 51 | arr: [ 52 | one 53 | two 54 | three 55 | four 56 | ] 57 | 58 | # not strings 59 | not: { 60 | number: 5 61 | negative: -4.2 62 | yes: true 63 | no: false 64 | null: null 65 | array: [ 66 | 1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 0 76 | -1 77 | 0.5 78 | ] 79 | } 80 | 81 | # special quoted 82 | special: { 83 | true: "true" 84 | false: "false" 85 | null: "null" 86 | one: "1" 87 | two: "2" 88 | minus: "-3" 89 | zero: "0" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /assets/pass1_result.json: -------------------------------------------------------------------------------- 1 | [ 2 | "JSON Test Pattern pass1", 3 | { 4 | "object with 1 member": [ 5 | "array with 1 element" 6 | ] 7 | }, 8 | {}, 9 | [], 10 | -42, 11 | true, 12 | false, 13 | null, 14 | { 15 | "integer": 1234567890, 16 | "real": -9876.54321, 17 | "e": 1.23456789e-13, 18 | "E": 1.23456789e+34, 19 | "-": 2.3456789012e+76, 20 | "zero": 0, 21 | "one": 1, 22 | "space": " ", 23 | "quote": "\"", 24 | "backslash": "\\", 25 | "controls": "\b\f\n\r\t", 26 | "slash": "/ & /", 27 | "alpha": "abcdefghijklmnopqrstuvwyz", 28 | "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", 29 | "digit": "0123456789", 30 | "0123456789": "digit", 31 | "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", 32 | "hex": "ģ䕧覫췯ꯍ", 33 | "true": true, 34 | "false": false, 35 | "null": null, 36 | "array": [], 37 | "object": {}, 38 | "address": "50 St. James Street", 39 | "url": "http://www.JSON.org/", 40 | "comment": "// /* */": " ", 42 | " s p a c e d ": [ 43 | 1, 44 | 2, 45 | 3, 46 | 4, 47 | 5, 48 | 6, 49 | 7 50 | ], 51 | "compact": [ 52 | 1, 53 | 2, 54 | 3, 55 | 4, 56 | 5, 57 | 6, 58 | 7 59 | ], 60 | "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", 61 | "quotes": "" \" %22 0x22 034 "", 62 | "/\\\"쫾몾ꮘﳞ볚\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?": "A key can be any string" 63 | }, 64 | 0.5, 65 | 98.6, 66 | 99.44, 67 | 1066, 68 | 10, 69 | 1, 70 | 0.1, 71 | 1, 72 | 2, 73 | 2, 74 | "rosebud" 75 | ] 76 | -------------------------------------------------------------------------------- /assets/strings_test.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # simple 3 | 4 | text1: This is a valid string value. 5 | text2:a \ is just a \ 6 | 7 | text3: "You need quotes\tfor escapes" 8 | 9 | text4a: " untrimmed " 10 | text4b: " untrimmed" 11 | text4c: "untrimmed " 12 | 13 | notml1: "\n" 14 | notml2: " \n" 15 | notml3: "\n \n \n \n" 16 | notml4: "\t\n" 17 | 18 | # multiline string 19 | 20 | multiline1: 21 | ''' 22 | first line 23 | indented line 24 | last line 25 | ''' 26 | 27 | multiline2: 28 | '''first line 29 | indented line 30 | last line''' 31 | 32 | multiline3: 33 | ''' 34 | first line 35 | indented line 36 | last line 37 | 38 | ''' # trailing lf 39 | 40 | multiline4: 41 | ''' 42 | ←→±≠Я 43 | ''' 44 | 45 | # escapes/no escape 46 | 47 | foo1a: asdf\"'a\s\w 48 | foo1b: '''asdf\"'a\s\w''' 49 | foo1c: "asdf\\\"'a\\s\\w" 50 | 51 | foo2a: "\"asdf\"" 52 | foo2b: '''"asdf"''' 53 | 54 | foo3a: "asdf'''" 55 | foo3b: "'''asdf" 56 | 57 | foo4a: "asdf'''\nasdf" 58 | foo4b: "asdf\n'''asdf" 59 | 60 | # in arrays 61 | arr: 62 | [ 63 | one 64 | two 65 | "three" 66 | '''four''' 67 | ] 68 | 69 | # not strings 70 | not: 71 | { 72 | number: 5 73 | negative: -4.2 74 | yes: true 75 | no: false 76 | null: null 77 | array: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, -1, 0.5 ] 78 | } 79 | 80 | # special quoted 81 | special: 82 | { 83 | true: "true" 84 | false: "false" 85 | null: "null" 86 | one: "1" 87 | two: "2" 88 | minus: "-3" 89 | zero: "0" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /assets/sorted/pass1_result.json: -------------------------------------------------------------------------------- 1 | [ 2 | "JSON Test Pattern pass1", 3 | { 4 | "object with 1 member": [ 5 | "array with 1 element" 6 | ] 7 | }, 8 | {}, 9 | [], 10 | -42, 11 | true, 12 | false, 13 | null, 14 | { 15 | " s p a c e d ": [ 16 | 1, 17 | 2, 18 | 3, 19 | 4, 20 | 5, 21 | 6, 22 | 7 23 | ], 24 | "# -- --> */": " ", 25 | "-": 2.3456789012e+76, 26 | "/\\\"쫾몾ꮘﳞ볚\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?": "A key can be any string", 27 | "0123456789": "digit", 28 | "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", 29 | "E": 1.23456789e+34, 30 | "address": "50 St. James Street", 31 | "alpha": "abcdefghijklmnopqrstuvwyz", 32 | "array": [], 33 | "backslash": "\\", 34 | "comment": "// /* */": " ", 38 | " s p a c e d " :[1,2 , 3 39 | 40 | , 41 | 42 | 4 , 5 , 6 ,7 ],"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 | 1e1, 55 | 0.1e1, 56 | 1e-1, 57 | 1e00,2e+00,2e-00 58 | ,"rosebud"] 59 | -------------------------------------------------------------------------------- /assets/comments3/pass1_result.hjson: -------------------------------------------------------------------------------- 1 | [ 2 | JSON Test Pattern pass1 3 | { 4 | "object with 1 member": [ 5 | array with 1 element 6 | ] 7 | } 8 | {} 9 | [] 10 | -42 11 | true 12 | false 13 | null 14 | { 15 | integer: 1234567890 16 | real: -9876.54321 17 | e: 1.23456789e-13 18 | E: 1.23456789e+34 19 | -: 2.3456789012e+76 20 | zero: 0 21 | one: 1 22 | space: " " 23 | quote: '''"''' 24 | backslash: \ 25 | controls: "\b\f\n\r\t" 26 | slash: / & / 27 | alpha: abcdefghijklmnopqrstuvwyz 28 | ALPHA: ABCDEFGHIJKLMNOPQRSTUVWYZ 29 | digit: 0123456789 30 | 0123456789: digit 31 | special: `1~!@#$%^&*()_+-={':[,]}|;.? 32 | hex: ģ䕧覫췯ꯍ 33 | true: true 34 | false: false 35 | null: null 36 | array: [ 37 | ] 38 | object: { 39 | } 40 | address: 50 St. James Street 41 | url: http://www.JSON.org/ 42 | comment: "// /* */": " " 44 | " s p a c e d ": [ 45 | 1 46 | 2 47 | 3 48 | 49 | 50 | 4 51 | 5 52 | 6 53 | 7 54 | ] 55 | compact: [ 56 | 1 57 | 2 58 | 3 59 | 4 60 | 5 61 | 6 62 | 7 63 | ] 64 | jsontext: '''{"object with 1 member":["array with 1 element"]}''' 65 | quotes: " " %22 0x22 034 " 66 | "/\\\"쫾몾ꮘﳞ볚\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?": 67 | A key can be any string 68 | } 69 | 0.5 70 | 98.6 71 | 99.44 72 | 73 | 1066 74 | 10 75 | 1 76 | 0.1 77 | 1 78 | 2 79 | 2 80 | rosebud 81 | ] 82 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build hjson-cli Releases 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v[0-9]+.[0-9]+**' 7 | 8 | jobs: 9 | 10 | build_hjson-cli_releases: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | fail-fast: false 15 | 16 | steps: 17 | - name: Checkout repo 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Build the hjson-cli binaries 23 | run: ./build_release.sh 24 | 25 | - name: Upload hjson-cli artifacts 26 | uses: actions/upload-artifact@v4 27 | with: 28 | name: output 29 | path: binaries/* 30 | if-no-files-found: error 31 | 32 | 33 | release_artifacts: 34 | needs: [build_hjson-cli_releases] 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Download actifacts 38 | uses: actions/download-artifact@v4 39 | with: 40 | path: artifacts 41 | 42 | - name: Show the downloaded artifacts 43 | run: | 44 | pwd 45 | ls -laR * 46 | 47 | - name: Release binaries 48 | uses: ncipollo/release-action@v1 49 | with: 50 | # ncipollo/release-action needs a tag! Either a usual "GIT TAG" or an dedicated TAG, see below! 51 | # set a TAG if you want to build a release, i.e. via "workflow_dispatch" on GitHub _AND_ do not push a regular GIT TAG 52 | # and the other way around, if you want to build releases based on pushed GIT TAGs, make sure you un-comment the "tag:" line below! 53 | tag: ${{ github.ref_name }} 54 | draft: true 55 | artifactErrorsFailBuild: true 56 | artifacts: "artifacts/output/*" 57 | token: ${{ secrets.GITHUB_TOKEN }} 58 | 59 | -------------------------------------------------------------------------------- /assets/comments3/strings_result.hjson: -------------------------------------------------------------------------------- 1 | { 2 | # simple 3 | 4 | text1: This is a valid string value. 5 | text2: a \ is just a \ 6 | 7 | text3: '''You need quotes for escapes''' 8 | 9 | text4a: " untrimmed " 10 | text4b: " untrimmed" 11 | text4c: "untrimmed " 12 | 13 | notml1: "\n" 14 | notml2: " \n" 15 | notml3: "\n \n \n \n" 16 | notml4: "\t\n" 17 | 18 | # multiline string 19 | 20 | multiline1: 21 | ''' 22 | first line 23 | indented line 24 | last line 25 | ''' 26 | 27 | multiline2: 28 | ''' 29 | first line 30 | indented line 31 | last line 32 | ''' 33 | 34 | multiline3: 35 | ''' 36 | first line 37 | indented line 38 | last line 39 | 40 | ''' # trailing lf 41 | 42 | multiline4: 43 | ←→±≠Я 44 | 45 | # escapes/no escape 46 | 47 | foo1a: asdf\"'a\s\w 48 | foo1b: asdf\"'a\s\w 49 | foo1c: asdf\"'a\s\w 50 | 51 | foo2a: '''"asdf"''' 52 | foo2b: '''"asdf"''' 53 | 54 | foo3a: asdf''' 55 | foo3b: "'''asdf" 56 | 57 | foo4a: "asdf'''\nasdf" 58 | foo4b: "asdf\n'''asdf" 59 | 60 | # in arrays 61 | arr: 62 | [ 63 | one 64 | two 65 | three 66 | four 67 | ] 68 | 69 | # not strings 70 | not: 71 | { 72 | number: 5 73 | negative: -4.2 74 | yes: true 75 | no: false 76 | null: null 77 | array: [ 78 | 1 79 | 2 80 | 3 81 | 4 82 | 5 83 | 6 84 | 7 85 | 8 86 | 9 87 | 0 88 | -1 89 | 0.5 90 | ] 91 | } 92 | 93 | # special quoted 94 | special: 95 | { 96 | true: "true" 97 | false: "false" 98 | null: "null" 99 | one: "1" 100 | two: "2" 101 | minus: "-3" 102 | zero: "0" 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /assets/testlist.txt: -------------------------------------------------------------------------------- 1 | charset2_test.hjson 2 | charset_test.hjson 3 | comments2_test.hjson 4 | comments3_test.hjson 5 | comments4_test.hjson 6 | comments5_test.hjson 7 | comments6_test.hjson 8 | comments7_test.hjson 9 | comments_test.hjson 10 | empty_test.hjson 11 | failCharset1_test.hjson 12 | failJSON02_test.json 13 | failJSON05_test.json 14 | failJSON06_test.json 15 | failJSON07_test.json 16 | failJSON08_test.json 17 | failJSON10_test.json 18 | failJSON11_test.json 19 | failJSON12_test.json 20 | failJSON13_test.json 21 | failJSON14_test.json 22 | failJSON15_test.json 23 | failJSON16_test.json 24 | failJSON17_test.json 25 | failJSON19_test.json 26 | failJSON20_test.json 27 | failJSON21_test.json 28 | failJSON22_test.json 29 | failJSON23_test.json 30 | failJSON26_test.json 31 | failJSON28_test.json 32 | failJSON29_test.json 33 | failJSON30_test.json 34 | failJSON31_test.json 35 | failJSON32_test.json 36 | failJSON33_test.json 37 | failJSON34_test.json 38 | failKey1_test.hjson 39 | failKey2_test.hjson 40 | failKey3_test.hjson 41 | failKey4_test.hjson 42 | failKey5_test.hjson 43 | failMLStr1_test.hjson 44 | failObj1_test.hjson 45 | failObj2_test.hjson 46 | failObj3_test.hjson 47 | failStr1a_test.hjson 48 | failStr1b_test.hjson 49 | failStr1c_test.hjson 50 | failStr1d_test.hjson 51 | failStr2a_test.hjson 52 | failStr2b_test.hjson 53 | failStr2c_test.hjson 54 | failStr2d_test.hjson 55 | failStr3a_test.hjson 56 | failStr3b_test.hjson 57 | failStr3c_test.hjson 58 | failStr3d_test.hjson 59 | failStr4a_test.hjson 60 | failStr4b_test.hjson 61 | failStr4c_test.hjson 62 | failStr4d_test.hjson 63 | failStr5a_test.hjson 64 | failStr5b_test.hjson 65 | failStr5c_test.hjson 66 | failStr5d_test.hjson 67 | failStr6a_test.hjson 68 | failStr6b_test.hjson 69 | failStr6c_test.hjson 70 | failStr6d_test.hjson 71 | failStr7a_test.hjson 72 | failStr8a_test.hjson 73 | failStr9a_test.hjson 74 | int64_test.hjson 75 | kan_test.hjson 76 | keys_test.hjson 77 | mltabs_test.json 78 | oa_test.hjson 79 | pass1_test.json 80 | pass2_test.json 81 | pass3_test.json 82 | pass4_test.json 83 | pass5_test.hjson 84 | pass6_test.hjson 85 | passSingle_test.hjson 86 | root_test.hjson 87 | stringify1_test.hjson 88 | strings2_test.hjson 89 | strings3_test.hjson 90 | strings4_test.hjson 91 | strings_test.hjson 92 | trail_test.hjson 93 | stringify/quotes_all_test.hjson 94 | stringify/quotes_always_test.hjson 95 | stringify/quotes_keys_test.hjson 96 | stringify/quotes_strings_ml_test.json 97 | stringify/quotes_strings_test.hjson 98 | windowseol_test.hjson 99 | extra/notabs_test.json 100 | extra/root_test.hjson 101 | extra/separator_test.json 102 | -------------------------------------------------------------------------------- /parseNumber.go: -------------------------------------------------------------------------------- 1 | package hjson 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "math" 7 | "strconv" 8 | ) 9 | 10 | type parseNumber struct { 11 | data []byte 12 | at int // The index of the current character 13 | ch byte // The current character 14 | } 15 | 16 | func (p *parseNumber) next() bool { 17 | // get the next character. 18 | len := len(p.data) 19 | if p.at < len { 20 | p.ch = p.data[p.at] 21 | p.at++ 22 | return true 23 | } 24 | if p.at == len { 25 | p.at++ 26 | p.ch = 0 27 | } 28 | return false 29 | } 30 | 31 | func (p *parseNumber) peek(offs int) byte { 32 | pos := p.at + offs 33 | if pos >= 0 && pos < len(p.data) { 34 | return p.data[pos] 35 | } 36 | return 0 37 | } 38 | 39 | func startsWithNumber(text []byte) bool { 40 | if _, err := tryParseNumber(text, true, false); err == nil { 41 | return true 42 | } 43 | return false 44 | } 45 | 46 | func tryParseNumber(text []byte, stopAtNext, useJSONNumber bool) (interface{}, error) { 47 | // Parse a number value. 48 | 49 | p := parseNumber{ 50 | data: text, 51 | at: 0, 52 | ch: ' ', 53 | } 54 | leadingZeros := 0 55 | testLeading := true 56 | p.next() 57 | if p.ch == '-' { 58 | p.next() 59 | } 60 | for p.ch >= '0' && p.ch <= '9' { 61 | if testLeading { 62 | if p.ch == '0' { 63 | leadingZeros++ 64 | } else { 65 | testLeading = false 66 | } 67 | } 68 | p.next() 69 | } 70 | if testLeading { 71 | leadingZeros-- 72 | } // single 0 is allowed 73 | if p.ch == '.' { 74 | for p.next() && p.ch >= '0' && p.ch <= '9' { 75 | } 76 | } 77 | if p.ch == 'e' || p.ch == 'E' { 78 | p.next() 79 | if p.ch == '-' || p.ch == '+' { 80 | p.next() 81 | } 82 | for p.ch >= '0' && p.ch <= '9' { 83 | p.next() 84 | } 85 | } 86 | 87 | end := p.at 88 | 89 | // skip white/to (newline) 90 | for p.ch > 0 && p.ch <= ' ' { 91 | p.next() 92 | } 93 | 94 | if stopAtNext { 95 | // end scan if we find a punctuator character like ,}] or a comment 96 | if p.ch == ',' || p.ch == '}' || p.ch == ']' || 97 | p.ch == '#' || p.ch == '/' && (p.peek(0) == '/' || p.peek(0) == '*') { 98 | p.ch = 0 99 | } 100 | } 101 | 102 | if p.ch > 0 || leadingZeros != 0 { 103 | return 0, errors.New("Invalid number") 104 | } 105 | if useJSONNumber { 106 | return json.Number(string(p.data[0 : end-1])), nil 107 | } 108 | number, err := strconv.ParseFloat(string(p.data[0:end-1]), 64) 109 | if err != nil { 110 | return 0, err 111 | } 112 | if math.IsInf(number, 0) || math.IsNaN(number) { 113 | return 0, errors.New("Invalid number") 114 | } 115 | return number, nil 116 | } 117 | -------------------------------------------------------------------------------- /hjson-cli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "runtime/debug" 11 | 12 | "github.com/hjson/hjson-go/v4" 13 | ) 14 | 15 | // Can be set when building for example like this: 16 | // go build -ldflags "-X main.Version=v3.0" 17 | var Version string 18 | 19 | func fixJSON(data []byte) []byte { 20 | data = bytes.Replace(data, []byte("\\u003c"), []byte("<"), -1) 21 | data = bytes.Replace(data, []byte("\\u003e"), []byte(">"), -1) 22 | data = bytes.Replace(data, []byte("\\u0026"), []byte("&"), -1) 23 | data = bytes.Replace(data, []byte("\\u0008"), []byte("\\b"), -1) 24 | data = bytes.Replace(data, []byte("\\u000c"), []byte("\\f"), -1) 25 | return data 26 | } 27 | 28 | func main() { 29 | 30 | flag.Usage = func() { 31 | fmt.Println("usage: hjson-cli [OPTIONS] [INPUT]") 32 | fmt.Println("hjson can be used to convert JSON from/to Hjson.") 33 | fmt.Println("") 34 | fmt.Println("hjson will read the given JSON/Hjson input file or read from stdin.") 35 | fmt.Println("") 36 | fmt.Println("Options:") 37 | flag.PrintDefaults() 38 | } 39 | 40 | var help = flag.Bool("h", false, "Show this screen.") 41 | var showJSON = flag.Bool("j", false, "Output as formatted JSON.") 42 | var showCompact = flag.Bool("c", false, "Output as JSON.") 43 | 44 | var indentBy = flag.String("indentBy", " ", "The indent string.") 45 | var bracesSameLine = flag.Bool("bracesSameLine", false, "Print braces on the same line.") 46 | var omitRootBraces = flag.Bool("omitRootBraces", false, "Omit braces at the root.") 47 | var quoteAlways = flag.Bool("quoteAlways", false, "Always quote string values.") 48 | var showVersion = flag.Bool("v", false, "Show version.") 49 | var preserveKeyOrder = flag.Bool("preserveKeyOrder", false, "Preserve key order in objects/maps.") 50 | 51 | flag.Parse() 52 | if *help || flag.NArg() > 1 { 53 | flag.Usage() 54 | os.Exit(1) 55 | } 56 | 57 | if *showVersion { 58 | if Version != "" { 59 | fmt.Println(Version) 60 | } else if bi, ok := debug.ReadBuildInfo(); ok { 61 | fmt.Println(bi.Main.Version) 62 | } else { 63 | fmt.Println("Unknown version") 64 | } 65 | os.Exit(0) 66 | } 67 | 68 | var err error 69 | var data []byte 70 | if flag.NArg() == 1 { 71 | data, err = ioutil.ReadFile(flag.Arg(0)) 72 | } else { 73 | data, err = ioutil.ReadAll(os.Stdin) 74 | } 75 | if err != nil { 76 | panic(err) 77 | } 78 | 79 | var value interface{} 80 | 81 | if *preserveKeyOrder { 82 | var node *hjson.Node 83 | err = hjson.Unmarshal(data, &node) 84 | value = node 85 | } else { 86 | err = hjson.Unmarshal(data, &value) 87 | } 88 | if err != nil { 89 | panic(err) 90 | } 91 | 92 | var out []byte 93 | if *showCompact { 94 | out, err = json.Marshal(value) 95 | if err != nil { 96 | panic(err) 97 | } 98 | out = fixJSON(out) 99 | } else if *showJSON { 100 | out, err = json.MarshalIndent(value, "", *indentBy) 101 | if err != nil { 102 | panic(err) 103 | } 104 | out = fixJSON(out) 105 | } else { 106 | opt := hjson.DefaultOptions() 107 | opt.IndentBy = *indentBy 108 | opt.BracesSameLine = *bracesSameLine 109 | opt.EmitRootBraces = !*omitRootBraces 110 | opt.QuoteAlways = *quoteAlways 111 | opt.Comments = false 112 | out, err = hjson.MarshalWithOptions(value, opt) 113 | if err != nil { 114 | panic(err) 115 | } 116 | } 117 | 118 | fmt.Println(string(out)) 119 | } 120 | -------------------------------------------------------------------------------- /orderedmap_test.go: -------------------------------------------------------------------------------- 1 | package hjson 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func verifyContent(t *testing.T, om *OrderedMap, txtExpected string) { 10 | bOut, err := json.Marshal(om) 11 | if err != nil { 12 | t.Error(err) 13 | } 14 | 15 | if string(bOut) != txtExpected { 16 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n\n", txtExpected, string(bOut)) 17 | } 18 | } 19 | 20 | func TestAppend(t *testing.T) { 21 | om := NewOrderedMap() 22 | om.Set("B", "first") 23 | om.Set("A", 2) 24 | 25 | verifyContent(t, om, `{"B":"first","A":2}`) 26 | } 27 | 28 | func TestInsert(t *testing.T) { 29 | om := NewOrderedMapFromSlice([]KeyValue{ 30 | {"B", "first"}, 31 | {"A", 2}, 32 | }) 33 | 34 | _, ok := om.Insert(1, "C", 1) 35 | if ok { 36 | t.Error("Insert returned true for non-existing key") 37 | } 38 | verifyContent(t, om, `{"B":"first","C":1,"A":2}`) 39 | 40 | oldVal, ok := om.Insert(3, "C", 3) 41 | if !ok { 42 | t.Error("Insert returned false for existing key") 43 | } 44 | if oldVal != 1 { 45 | t.Errorf("Expected old value 1, got: '%v'", oldVal) 46 | } 47 | verifyContent(t, om, `{"B":"first","C":3,"A":2}`) 48 | 49 | if om.Len() != 3 { 50 | t.Errorf("Expected length: 3 Got length: %d\n", om.Len()) 51 | } 52 | 53 | if om.AtIndex(1) != 3 { 54 | t.Errorf("Expected value 3 at index 1. Got value: %d\n", om.AtIndex(3)) 55 | } 56 | 57 | if om.Map["C"] != 3 { 58 | t.Errorf("Expected value 3 for key C. Got value: %d\n", om.AtIndex(3)) 59 | } 60 | 61 | oldVal, found := om.DeleteKey("XYZ") 62 | if found { 63 | t.Errorf("DeleteKey returned true for non-existing key.") 64 | } 65 | 66 | oldVal, found = om.DeleteKey("C") 67 | if !found { 68 | t.Errorf("DeleteKey returned false for existing key.") 69 | } 70 | if oldVal != 3 { 71 | t.Errorf("Expected old value 3, got: '%v'", oldVal) 72 | } 73 | verifyContent(t, om, `{"B":"first","A":2}`) 74 | 75 | key, oldVal := om.DeleteIndex(1) 76 | if key != "A" { 77 | t.Errorf("Expected key 'A', got: '%v'", key) 78 | } 79 | if oldVal != 2 { 80 | t.Errorf("Expected old value 2, got: '%v'", oldVal) 81 | } 82 | verifyContent(t, om, `{"B":"first"}`) 83 | 84 | key, oldVal = om.DeleteIndex(0) 85 | if key != "B" { 86 | t.Errorf("Expected key 'B', got: '%v'", key) 87 | } 88 | if oldVal != "first" { 89 | t.Errorf("Expected old value 'first', got: '%v'", oldVal) 90 | } 91 | verifyContent(t, om, `{}`) 92 | } 93 | 94 | func TestUnmarshalJSON(t *testing.T) { 95 | var om *OrderedMap 96 | err := json.Unmarshal([]byte(`{"B":"first","C":3,"sub":{"z":7,"y":8},"A":2}`), &om) 97 | if err != nil { 98 | t.Error(err) 99 | } 100 | 101 | verifyContent(t, om, `{"B":"first","C":3,"sub":{"z":7,"y":8},"A":2}`) 102 | 103 | if _, ok := om.Map["C"].(float64); !ok { 104 | t.Errorf("Expected type float64, got type %v", reflect.TypeOf(om.Map["C"])) 105 | } 106 | } 107 | 108 | func TestUnmarshalJSON_2(t *testing.T) { 109 | var om OrderedMap 110 | err := json.Unmarshal([]byte(`{"B":"first","C":3,"sub":{"z":7,"y":8},"A":2}`), &om) 111 | if err != nil { 112 | t.Error(err) 113 | } 114 | 115 | verifyContent(t, &om, `{"B":"first","C":3,"sub":{"z":7,"y":8},"A":2}`) 116 | } 117 | 118 | func TestUnmarshalHJSON(t *testing.T) { 119 | var om *OrderedMap 120 | err := Unmarshal([]byte(`{"B":"first","C":3,"sub":{"z":7,"y":8},"A":2}`), &om) 121 | if err != nil { 122 | t.Error(err) 123 | } 124 | 125 | verifyContent(t, om, `{"B":"first","C":3,"sub":{"z":7,"y":8},"A":2}`) 126 | 127 | if _, ok := om.Map["C"].(float64); !ok { 128 | t.Errorf("Expected type float64, got type %v", reflect.TypeOf(om.Map["C"])) 129 | } 130 | } 131 | 132 | func TestUnmarshalHJSON_2(t *testing.T) { 133 | var om OrderedMap 134 | err := Unmarshal([]byte(`{"B":"first","C":3,"sub":{"z":7,"y":8},"A":2}`), &om) 135 | if err != nil { 136 | t.Error(err) 137 | } 138 | 139 | verifyContent(t, &om, `{"B":"first","C":3,"sub":{"z":7,"y":8},"A":2}`) 140 | } 141 | -------------------------------------------------------------------------------- /orderedmap.go: -------------------------------------------------------------------------------- 1 | package hjson 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | ) 7 | 8 | // OrderedMap wraps a map and a slice containing all of the keys from the map, 9 | // so that the order of the keys can be specified. The Keys slice can be sorted 10 | // or rearranged like any other slice, but do not add or remove keys manually 11 | // on it. Use OrderedMap.Insert(), OrderedMap.Set(), OrderedMap.DeleteIndex() or 12 | // OrderedMap.DeleteKey() instead. 13 | // 14 | // Example of how to iterate through the elements of an OrderedMap in order: 15 | // 16 | // for _, key := range om.Keys { 17 | // fmt.Printf("%v\n", om.Map[key]) 18 | // } 19 | // 20 | // Always use the functions Insert() or Set() instead of setting values 21 | // directly on OrderedMap.Map, because any new keys must also be added to 22 | // OrderedMap.Keys. Otherwise those keys will be ignored when iterating through 23 | // the elements of the OrderedMap in order, as for example happens in the 24 | // function hjson.Marshal(). 25 | type OrderedMap struct { 26 | Keys []string 27 | Map map[string]interface{} 28 | } 29 | 30 | // KeyValue is only used as input to NewOrderedMapFromSlice(). 31 | type KeyValue struct { 32 | Key string 33 | Value interface{} 34 | } 35 | 36 | // NewOrderedMap returns a pointer to a new OrderedMap. An OrderedMap should 37 | // always be passed by reference, never by value. If an OrderedMap is passed 38 | // by value then appending new keys won't affect all of the copies of the 39 | // OrderedMap. 40 | func NewOrderedMap() *OrderedMap { 41 | return &OrderedMap{ 42 | Keys: nil, 43 | Map: map[string]interface{}{}, 44 | } 45 | } 46 | 47 | // NewOrderedMapFromSlice is like NewOrderedMap but with initial values. 48 | // Example: 49 | // 50 | // om := NewOrderedMapFromSlice([]KeyValue{ 51 | // {"B", "first"}, 52 | // {"A", "second"}, 53 | // }) 54 | func NewOrderedMapFromSlice(args []KeyValue) *OrderedMap { 55 | c := NewOrderedMap() 56 | for _, elem := range args { 57 | c.Set(elem.Key, elem.Value) 58 | } 59 | return c 60 | } 61 | 62 | // Len returns the number of values contained in the OrderedMap. 63 | func (c *OrderedMap) Len() int { 64 | return len(c.Keys) 65 | } 66 | 67 | // AtIndex returns the value found at the specified index. Panics if 68 | // index < 0 or index >= c.Len(). 69 | func (c *OrderedMap) AtIndex(index int) interface{} { 70 | return c.Map[c.Keys[index]] 71 | } 72 | 73 | // AtKey returns the value found for the specified key, and true if the value 74 | // was found. Returns nil and false if the value was not found. 75 | func (c *OrderedMap) AtKey(key string) (interface{}, bool) { 76 | ret, ok := c.Map[key] 77 | return ret, ok 78 | } 79 | 80 | // Insert inserts a new key/value pair at the specified index. Panics if 81 | // index < 0 or index > c.Len(). If the key already exists in the OrderedMap, 82 | // the new value is set but the position of the key is not changed. Returns 83 | // the old value and true if the key already exists in the OrderedMap, nil and 84 | // false otherwise. 85 | func (c *OrderedMap) Insert(index int, key string, value interface{}) (interface{}, bool) { 86 | oldValue, exists := c.Map[key] 87 | c.Map[key] = value 88 | if exists { 89 | return oldValue, true 90 | } 91 | if index == len(c.Keys) { 92 | c.Keys = append(c.Keys, key) 93 | } else { 94 | c.Keys = append(c.Keys[:index+1], c.Keys[index:]...) 95 | c.Keys[index] = key 96 | } 97 | return nil, false 98 | } 99 | 100 | // Set sets the specified value for the specified key. If the key does not 101 | // already exist in the OrderedMap it is appended to the end of the OrderedMap. 102 | // If the key already exists in the OrderedMap, the new value is set but the 103 | // position of the key is not changed. Returns the old value and true if the 104 | // key already exists in the OrderedMap, nil and false otherwise. 105 | func (c *OrderedMap) Set(key string, value interface{}) (interface{}, bool) { 106 | return c.Insert(len(c.Keys), key, value) 107 | } 108 | 109 | // DeleteIndex deletes the key/value pair found at the specified index. 110 | // Returns the deleted key and value. Panics if index < 0 or index >= c.Len(). 111 | func (c *OrderedMap) DeleteIndex(index int) (string, interface{}) { 112 | key := c.Keys[index] 113 | value := c.Map[key] 114 | delete(c.Map, key) 115 | c.Keys = append(c.Keys[:index], c.Keys[index+1:]...) 116 | return key, value 117 | } 118 | 119 | // DeleteKey deletes the key/value pair with the specified key, if found. 120 | // Returns the deleted value and true if the key was found, nil and false 121 | // otherwise. 122 | func (c *OrderedMap) DeleteKey(key string) (interface{}, bool) { 123 | for index, ck := range c.Keys { 124 | if ck == key { 125 | _, value := c.DeleteIndex(index) 126 | return value, true 127 | } 128 | } 129 | return nil, false 130 | } 131 | 132 | // MarshalJSON is an implementation of the json.Marshaler interface, enabling 133 | // hjson.OrderedMap to be used as input for json.Marshal(). 134 | func (c *OrderedMap) MarshalJSON() ([]byte, error) { 135 | var b bytes.Buffer 136 | 137 | b.WriteString("{") 138 | 139 | for index, key := range c.Keys { 140 | if index > 0 { 141 | b.WriteString(",") 142 | } 143 | jbuf, err := json.Marshal(key) 144 | if err != nil { 145 | return nil, err 146 | } 147 | b.Write(jbuf) 148 | b.WriteString(":") 149 | jbuf, err = json.Marshal(c.Map[key]) 150 | if err != nil { 151 | return nil, err 152 | } 153 | b.Write(jbuf) 154 | } 155 | 156 | b.WriteString("}") 157 | 158 | return b.Bytes(), nil 159 | } 160 | 161 | // UnmarshalJSON is an implementation of the json.Unmarshaler interface, 162 | // enabling hjson.OrderedMap to be used as destination for json.Unmarshal(). 163 | func (c *OrderedMap) UnmarshalJSON(b []byte) error { 164 | c.Keys = nil 165 | c.Map = map[string]interface{}{} 166 | return Unmarshal(b, c) 167 | } 168 | -------------------------------------------------------------------------------- /structs.go: -------------------------------------------------------------------------------- 1 | package hjson 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sort" 7 | "strings" 8 | ) 9 | 10 | type fieldInfo struct { 11 | field reflect.Value 12 | name string 13 | comment string 14 | } 15 | 16 | type structFieldInfo struct { 17 | name string 18 | tagged bool 19 | comment string 20 | omitEmpty bool 21 | indexPath []int 22 | } 23 | 24 | // Use lower key name as key. Values are arrays in case some fields only differ 25 | // by upper/lower case. 26 | type structFieldMap map[string][]structFieldInfo 27 | 28 | func (s structFieldMap) insert(sfi structFieldInfo) { 29 | key := strings.ToLower(sfi.name) 30 | s[key] = append(s[key], sfi) 31 | } 32 | 33 | func (s structFieldMap) getField(name string) (structFieldInfo, bool) { 34 | key := strings.ToLower(name) 35 | if arr, ok := s[key]; ok { 36 | for _, elem := range arr { 37 | if elem.name == name { 38 | return elem, true 39 | } 40 | } 41 | return arr[0], true 42 | } 43 | 44 | return structFieldInfo{}, false 45 | } 46 | 47 | // dominantField looks through the fields, all of which are known to 48 | // have the same name, to find the single field that dominates the 49 | // others using Go's embedding rules, modified by the presence of 50 | // JSON tags. If there are multiple top-level fields, the boolean 51 | // will be false: This condition is an error in Go and we skip all 52 | // the fields. 53 | func dominantField(fields []structFieldInfo) (structFieldInfo, bool) { 54 | // The fields are sorted in increasing index-length order, then by presence of tag. 55 | // That means that the first field is the dominant one. We need only check 56 | // for error cases: two fields at top level, either both tagged or neither tagged. 57 | if len(fields) > 1 && len(fields[0].indexPath) == len(fields[1].indexPath) && fields[0].tagged == fields[1].tagged { 58 | return structFieldInfo{}, false 59 | } 60 | return fields[0], true 61 | } 62 | 63 | // byIndex sorts by index sequence. 64 | type byIndex []structFieldInfo 65 | 66 | func (x byIndex) Len() int { return len(x) } 67 | 68 | func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 69 | 70 | func (x byIndex) Less(i, j int) bool { 71 | for k, xik := range x[i].indexPath { 72 | if k >= len(x[j].indexPath) { 73 | return false 74 | } 75 | if xik != x[j].indexPath[k] { 76 | return xik < x[j].indexPath[k] 77 | } 78 | } 79 | return len(x[i].indexPath) < len(x[j].indexPath) 80 | } 81 | 82 | func getStructFieldInfo(rootType reflect.Type) []structFieldInfo { 83 | type structInfo struct { 84 | typ reflect.Type 85 | indexPath []int 86 | } 87 | var sfis []structFieldInfo 88 | structsToInvestigate := []structInfo{structInfo{typ: rootType}} 89 | // Struct types already visited at an earlier depth. 90 | visited := map[reflect.Type]bool{} 91 | // Count the number of specific struct types on a specific depth. 92 | typeDepthCount := map[reflect.Type]int{} 93 | 94 | for len(structsToInvestigate) > 0 { 95 | curStructs := structsToInvestigate 96 | structsToInvestigate = []structInfo{} 97 | curTDC := typeDepthCount 98 | typeDepthCount = map[reflect.Type]int{} 99 | 100 | for _, curStruct := range curStructs { 101 | if visited[curStruct.typ] { 102 | // The struct type has already appeared on an earlier depth. Fields on 103 | // an earlier depth always have precedence over fields with identical 104 | // name on a later depth, so no point in investigating this type again. 105 | continue 106 | } 107 | visited[curStruct.typ] = true 108 | 109 | for i := 0; i < curStruct.typ.NumField(); i++ { 110 | sf := curStruct.typ.Field(i) 111 | 112 | if sf.Anonymous { 113 | t := sf.Type 114 | if t.Kind() == reflect.Ptr { 115 | t = t.Elem() 116 | } 117 | // If the field is not exported and not a struct. 118 | if sf.PkgPath != "" && t.Kind() != reflect.Struct { 119 | // Ignore embedded fields of unexported non-struct types. 120 | continue 121 | } 122 | // Do not ignore embedded fields of unexported struct types 123 | // since they may have exported fields. 124 | } else if sf.PkgPath != "" { 125 | // Ignore unexported non-embedded fields. 126 | continue 127 | } 128 | 129 | jsonTag := sf.Tag.Get("json") 130 | if jsonTag == "-" { 131 | continue 132 | } 133 | 134 | sfi := structFieldInfo{ 135 | name: sf.Name, 136 | comment: sf.Tag.Get("comment"), 137 | } 138 | 139 | splits := strings.Split(jsonTag, ",") 140 | if splits[0] != "" { 141 | sfi.name = splits[0] 142 | sfi.tagged = true 143 | } 144 | if len(splits) > 1 { 145 | for _, opt := range splits[1:] { 146 | if opt == "omitempty" { 147 | sfi.omitEmpty = true 148 | } 149 | } 150 | } 151 | 152 | sfi.indexPath = make([]int, len(curStruct.indexPath)+1) 153 | copy(sfi.indexPath, curStruct.indexPath) 154 | sfi.indexPath[len(curStruct.indexPath)] = i 155 | 156 | ft := sf.Type 157 | if ft.Name() == "" && ft.Kind() == reflect.Ptr { 158 | // Follow pointer. 159 | ft = ft.Elem() 160 | } 161 | 162 | // If the current field should be included. 163 | if sfi.tagged || !sf.Anonymous || ft.Kind() != reflect.Struct { 164 | sfis = append(sfis, sfi) 165 | if curTDC[curStruct.typ] > 1 { 166 | // If there were multiple instances, add a second, 167 | // so that the annihilation code will see a duplicate. 168 | // It only cares about the distinction between 1 or 2, 169 | // so don't bother generating any more copies. 170 | sfis = append(sfis, sfi) 171 | } 172 | continue 173 | } 174 | 175 | // Record new anonymous struct to explore in next round. 176 | typeDepthCount[ft]++ 177 | if typeDepthCount[ft] == 1 { 178 | structsToInvestigate = append(structsToInvestigate, structInfo{ 179 | typ: ft, 180 | indexPath: sfi.indexPath, 181 | }) 182 | } 183 | } 184 | } 185 | } 186 | 187 | sort.Slice(sfis, func(i, j int) bool { 188 | // sort field by name, breaking ties with depth, then 189 | // breaking ties with "name came from json tag", then 190 | // breaking ties with index sequence. 191 | if sfis[i].name != sfis[j].name { 192 | return sfis[i].name < sfis[j].name 193 | } 194 | if len(sfis[i].indexPath) != len(sfis[j].indexPath) { 195 | return len(sfis[i].indexPath) < len(sfis[j].indexPath) 196 | } 197 | if sfis[i].tagged != sfis[j].tagged { 198 | return sfis[i].tagged 199 | } 200 | return byIndex(sfis).Less(i, j) 201 | }) 202 | 203 | // Delete all fields that are hidden by the Go rules for embedded fields, 204 | // except that fields with JSON tags are promoted. 205 | 206 | // The fields are sorted in primary order of name, secondary order 207 | // of field index length. Loop over names; for each name, delete 208 | // hidden fields by choosing the one dominant field that survives. 209 | out := sfis[:0] 210 | for advance, i := 0, 0; i < len(sfis); i += advance { 211 | // One iteration per name. 212 | // Find the sequence of sfis with the name of this first field. 213 | sfi := sfis[i] 214 | name := sfi.name 215 | for advance = 1; i+advance < len(sfis); advance++ { 216 | fj := sfis[i+advance] 217 | if fj.name != name { 218 | break 219 | } 220 | } 221 | if advance == 1 { // Only one field with this name 222 | out = append(out, sfi) 223 | continue 224 | } 225 | dominant, ok := dominantField(sfis[i : i+advance]) 226 | if ok { 227 | out = append(out, dominant) 228 | } 229 | } 230 | 231 | return out 232 | } 233 | 234 | func getStructFieldInfoSlice(rootType reflect.Type) []structFieldInfo { 235 | sfis := getStructFieldInfo(rootType) 236 | 237 | sort.Sort(byIndex(sfis)) 238 | 239 | return sfis 240 | } 241 | 242 | func getStructFieldInfoMap(rootType reflect.Type) structFieldMap { 243 | sfis := getStructFieldInfo(rootType) 244 | 245 | out := structFieldMap{} 246 | for _, elem := range sfis { 247 | out.insert(elem) 248 | } 249 | 250 | return out 251 | } 252 | 253 | func (e *hjsonEncoder) writeFields( 254 | fis []fieldInfo, 255 | noIndent bool, 256 | separator string, 257 | isRootObject bool, 258 | isObjElement bool, 259 | cm Comments, 260 | ) error { 261 | indent1 := e.indent 262 | if !isRootObject || e.EmitRootBraces || len(fis) == 0 { 263 | e.bracesIndent(isObjElement, len(fis) == 0, cm, separator) 264 | e.WriteString("{" + cm.InsideFirst) 265 | 266 | if len(fis) == 0 { 267 | if cm.InsideFirst != "" || cm.InsideLast != "" { 268 | e.WriteString(e.Eol) 269 | } 270 | e.WriteString(cm.InsideLast) 271 | if cm.InsideLast != "" { 272 | endsInsideComment, endsWithLineFeed := investigateComment(cm.InsideLast) 273 | if endsInsideComment { 274 | e.writeIndent(e.indent) 275 | } 276 | if endsWithLineFeed { 277 | e.writeIndentNoEOL(e.indent) 278 | } 279 | } else if cm.InsideFirst != "" { 280 | e.writeIndentNoEOL(e.indent) 281 | } 282 | e.WriteString("}") 283 | return nil 284 | } 285 | 286 | e.indent++ 287 | } else { 288 | e.WriteString(cm.InsideFirst) 289 | } 290 | 291 | // Join all of the member texts together, separated with newlines 292 | var elemCm Comments 293 | for i, fi := range fis { 294 | var elem reflect.Value 295 | elem, elemCm = e.unpackNode(fi.field, elemCm) 296 | if i > 0 || !isRootObject || e.EmitRootBraces { 297 | e.WriteString(e.Eol) 298 | } 299 | if len(fi.comment) > 0 { 300 | for _, line := range strings.Split(fi.comment, e.Eol) { 301 | e.writeIndentNoEOL(e.indent) 302 | e.WriteString(fmt.Sprintf("# %s\n", line)) 303 | } 304 | } 305 | if elemCm.Before == "" { 306 | e.writeIndentNoEOL(e.indent) 307 | } else { 308 | e.WriteString(elemCm.Before) 309 | } 310 | e.WriteString(e.quoteName(fi.name)) 311 | e.WriteString(":") 312 | e.WriteString(elemCm.Key) 313 | 314 | if err := e.str(elem, false, " ", false, true, elemCm); err != nil { 315 | return err 316 | } 317 | 318 | if len(fi.comment) > 0 && i < len(fis)-1 { 319 | e.WriteString(e.Eol) 320 | } 321 | 322 | e.WriteString(elemCm.After) 323 | } 324 | 325 | if cm.InsideLast != "" { 326 | e.WriteString(e.Eol + cm.InsideLast) 327 | } 328 | 329 | if !isRootObject || e.EmitRootBraces { 330 | if cm.InsideLast == "" { 331 | e.writeIndent(indent1) 332 | } else { 333 | endsInsideComment, endsWithLineFeed := investigateComment(cm.InsideLast) 334 | if endsInsideComment { 335 | e.writeIndent(indent1) 336 | } else if endsWithLineFeed { 337 | e.writeIndentNoEOL(indent1) 338 | } 339 | } 340 | e.WriteString("}") 341 | } 342 | 343 | e.indent = indent1 344 | 345 | return nil 346 | } 347 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hjson-go 2 | 3 | [![Build Status](https://github.com/hjson/hjson-go/workflows/test/badge.svg)](https://github.com/hjson/hjson-go/actions) 4 | [![Go Pkg](https://img.shields.io/github/release/hjson/hjson-go.svg?style=flat-square&label=go-pkg)](https://github.com/hjson/hjson-go/releases) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/hjson/hjson-go?style=flat-square)](https://goreportcard.com/report/github.com/hjson/hjson-go) 6 | [![coverage](https://img.shields.io/badge/coverage-ok-brightgreen.svg?style=flat-square)](https://gocover.io/github.com/hjson/hjson-go/) 7 | [![godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/hjson/hjson-go/v4) 8 | 9 | ![Hjson Intro](https://hjson.github.io/hjson1.gif) 10 | 11 | ``` 12 | { 13 | # specify rate in requests/second (because comments are helpful!) 14 | rate: 1000 15 | 16 | // prefer c-style comments? 17 | /* feeling old fashioned? */ 18 | 19 | # did you notice that rate doesn't need quotes? 20 | hey: look ma, no quotes for strings either! 21 | 22 | # best of all 23 | notice: [] 24 | anything: ? 25 | 26 | # yes, commas are optional! 27 | } 28 | ``` 29 | 30 | The Go implementation of Hjson is based on [hjson-js](https://github.com/hjson/hjson-js). For other platforms see [hjson.github.io](https://hjson.github.io). 31 | 32 | More documentation can be found at https://pkg.go.dev/github.com/hjson/hjson-go/v4 33 | 34 | # Install 35 | 36 | Instructions for installing a pre-built **hjson-cli** tool can be found at https://hjson.github.io/users-bin.html 37 | 38 | If you instead want to build locally, make sure you have a working Go environment. See the [install instructions](https://golang.org/doc/install.html). 39 | 40 | - In order to use Hjson from your own Go source code, just add an import line like the one here below. Before building your project, run `go mod tidy` in order to download the Hjson source files. The suffix `/v4` is required in the import path, unless you specifically want to use an older major version. 41 | ```go 42 | import "github.com/hjson/hjson-go/v4" 43 | ``` 44 | - If you instead want to use the **hjson-cli** command line tool, run the command here below in your terminal. The executable will be installed into your `go/bin` folder, make sure that folder is included in your `PATH` environment variable. 45 | ```bash 46 | go install github.com/hjson/hjson-go/v4/hjson-cli@latest 47 | ``` 48 | # Usage as command line tool 49 | ``` 50 | usage: hjson-cli [OPTIONS] [INPUT] 51 | hjson can be used to convert JSON from/to Hjson. 52 | 53 | hjson will read the given JSON/Hjson input file or read from stdin. 54 | 55 | Options: 56 | -bracesSameLine 57 | Print braces on the same line. 58 | -c Output as JSON. 59 | -h Show this screen. 60 | -indentBy string 61 | The indent string. (default " ") 62 | -j Output as formatted JSON. 63 | -omitRootBraces 64 | Omit braces at the root. 65 | -preserveKeyOrder 66 | Preserve key order in objects/maps. 67 | -quoteAlways 68 | Always quote string values. 69 | -v 70 | Show version. 71 | ``` 72 | 73 | Sample: 74 | - run `hjson-cli test.json > test.hjson` to convert to Hjson 75 | - run `hjson-cli -j test.hjson > test.json` to convert to JSON 76 | 77 | # Usage as a GO library 78 | 79 | ```go 80 | 81 | package main 82 | 83 | import ( 84 | "github.com/hjson/hjson-go/v4" 85 | "fmt" 86 | ) 87 | 88 | func main() { 89 | // Now let's look at decoding Hjson data into Go 90 | // values. 91 | sampleText := []byte(` 92 | { 93 | # specify rate in requests/second 94 | rate: 1000 95 | array: 96 | [ 97 | foo 98 | bar 99 | ] 100 | }`) 101 | 102 | // We need to provide a variable where Hjson 103 | // can put the decoded data. 104 | var dat map[string]interface{} 105 | 106 | // Decode with default options and check for errors. 107 | if err := hjson.Unmarshal(sampleText, &dat); err != nil { 108 | panic(err) 109 | } 110 | // short for: 111 | // options := hjson.DefaultDecoderOptions() 112 | // err := hjson.UnmarshalWithOptions(sampleText, &dat, options) 113 | fmt.Println(dat) 114 | 115 | // In order to use the values in the decoded map, 116 | // we'll need to cast them to their appropriate type. 117 | 118 | rate := dat["rate"].(float64) 119 | fmt.Println(rate) 120 | 121 | array := dat["array"].([]interface{}) 122 | str1 := array[0].(string) 123 | fmt.Println(str1) 124 | 125 | 126 | // To encode to Hjson with default options: 127 | sampleMap := map[string]int{"apple": 5, "lettuce": 7} 128 | hjson, _ := hjson.Marshal(sampleMap) 129 | // short for: 130 | // options := hjson.DefaultOptions() 131 | // hjson, _ := hjson.MarshalWithOptions(sampleMap, options) 132 | fmt.Println(string(hjson)) 133 | } 134 | ``` 135 | 136 | ## Unmarshal to Go structs 137 | 138 | If you prefer, you can also unmarshal to Go structs (including structs implementing the json.Unmarshaler interface or the encoding.TextUnmarshaler interface). The Go JSON package is used for this, so the same rules apply. Specifically for the "json" key in struct field tags. For more details about this type of unmarshalling, see the [documentation for json.Unmarshal()](https://pkg.go.dev/encoding/json#Unmarshal). 139 | 140 | ```go 141 | 142 | package main 143 | 144 | import ( 145 | "github.com/hjson/hjson-go/v4" 146 | "fmt" 147 | ) 148 | 149 | type Sample struct { 150 | Rate int 151 | Array []string 152 | } 153 | 154 | type SampleAlias struct { 155 | Rett int `json:"rate"` 156 | Ashtray []string `json:"array"` 157 | } 158 | 159 | func main() { 160 | sampleText := []byte(` 161 | { 162 | # specify rate in requests/second 163 | rate: 1000 164 | array: 165 | [ 166 | foo 167 | bar 168 | ] 169 | }`) 170 | 171 | // unmarshal 172 | var sample Sample 173 | hjson.Unmarshal(sampleText, &sample) 174 | 175 | fmt.Println(sample.Rate) 176 | fmt.Println(sample.Array) 177 | 178 | // unmarshal using json tags on struct fields 179 | var sampleAlias SampleAlias 180 | hjson.Unmarshal(sampleText, &sampleAlias) 181 | 182 | fmt.Println(sampleAlias.Rett) 183 | fmt.Println(sampleAlias.Ashtray) 184 | } 185 | ``` 186 | 187 | ## Comments on struct fields 188 | 189 | By using key `comment` in struct field tags you can specify comments to be written on one or more lines preceding the struct field in the Hjson output. Another way to output comments is to use *hjson.Node* structs, more on than later. 190 | 191 | ```go 192 | 193 | package main 194 | 195 | import ( 196 | "github.com/hjson/hjson-go/v4" 197 | "fmt" 198 | ) 199 | 200 | type foo struct { 201 | A string `json:"x" comment:"First comment"` 202 | B int32 `comment:"Second comment\nLook ma, new lines"` 203 | C string 204 | D int32 205 | } 206 | 207 | func main() { 208 | a := foo{A: "hi!", B: 3, C: "some text", D: 5} 209 | buf, err := hjson.Marshal(a) 210 | if err != nil { 211 | fmt.Println(err) 212 | } 213 | 214 | fmt.Println(string(buf)) 215 | } 216 | ``` 217 | 218 | Output: 219 | 220 | ``` 221 | { 222 | # First comment 223 | x: hi! 224 | 225 | # Second comment 226 | # Look ma, new lines 227 | B: 3 228 | 229 | C: some text 230 | D: 5 231 | } 232 | ``` 233 | 234 | ## Read and write comments 235 | 236 | The only way to read comments from Hjson input is to use a destination variable of type *hjson.Node* or **hjson.Node*. The *hjson.Node* must be the root destination, it won't work if you create a field of type *hjson.Node* in some other struct and use that struct as destination. An *hjson.Node* struct is simply a wrapper for a value and comments stored in an *hjson.Comments* struct. It also has several convenience functions, for example *AtIndex()* or *SetKey()* that can be used when you know that the node contains a value of type `[]interface{}` or **hjson.OrderedMap*. All of the elements in `[]interface{}` or **hjson.OrderedMap* will be of type **hjson.Node* in trees created by *hjson.Unmarshal*, but the *hjson.Node* convenience functions unpack the actual values from them. 237 | 238 | When *hjson.Node* or **hjson.Node* is used as destination for Hjson unmarshal the output will be a tree of **hjson.Node* where all of the values contained in tree nodes will be of these types: 239 | 240 | * `nil` (no type) 241 | * `float64`   (if *UseJSONNumber* == `false`) 242 | * *json.Number*   (if *UseJSONNumber* == `true`) 243 | * `string` 244 | * `bool` 245 | * `[]interface{}` 246 | * **hjson.OrderedMap* 247 | 248 | These are just the types used by Hjson unmarshal and the convenience functions, you are free to assign any type of values to nodes in your own code. 249 | 250 | The comments will contain all whitespace chars too (including line feeds) so that an Hjson document can be read and written without altering the layout. This can be disabled by setting the decoding option *WhitespaceAsComments* to `false`. 251 | 252 | ```go 253 | 254 | package main 255 | 256 | import ( 257 | "fmt" 258 | 259 | "github.com/hjson/hjson-go/v4" 260 | ) 261 | 262 | func main() { 263 | // Now let's look at decoding Hjson data into hjson.Node. 264 | sampleText := []byte(` 265 | { 266 | # specify rate in requests/second 267 | rate: 1000 268 | array: 269 | [ 270 | foo 271 | bar 272 | ] 273 | }`) 274 | 275 | var node hjson.Node 276 | if err := hjson.Unmarshal(sampleText, &node); err != nil { 277 | panic(err) 278 | } 279 | 280 | node.NK("array").Cm.Before = ` # please specify an array 281 | ` 282 | 283 | if _, _, err := node.NKC("subMap").SetKey("subVal", 1); err != nil { 284 | panic(err) 285 | } 286 | 287 | outBytes, err := hjson.Marshal(node) 288 | if err != nil { 289 | panic(err) 290 | } 291 | 292 | fmt.Println(string(outBytes)) 293 | } 294 | ``` 295 | 296 | Output: 297 | 298 | ``` 299 | 300 | { 301 | # specify rate in requests/second 302 | rate: 1000 303 | # please specify an array 304 | array: 305 | [ 306 | foo 307 | bar 308 | ] 309 | subMap: { 310 | subVal: 1 311 | } 312 | } 313 | ``` 314 | 315 | 316 | ## Type ambiguity 317 | 318 | Hjson allows quoteless strings. But if a value is a valid number, boolean or `null` then it will be unmarshalled into that type instead of a string when unmarshalling into `interface{}`. This can lead to unintended consequences if the creator of an Hjson file meant to write a string but didn't think of that the quoteless string they wrote also was a valid number. 319 | 320 | The ambiguity can be avoided by using typed destinations when unmarshalling. A string destination will receive a string even if the quoteless string also was a valid number, boolean or `null`. Example: 321 | 322 | ```go 323 | 324 | package main 325 | 326 | import ( 327 | "github.com/hjson/hjson-go/v4" 328 | "fmt" 329 | ) 330 | 331 | type foo struct { 332 | A string 333 | } 334 | 335 | func main() { 336 | var dest foo 337 | err := hjson.Unmarshal([]byte(`a: 3`), &dest) 338 | if err != nil { 339 | fmt.Println(err) 340 | } 341 | 342 | fmt.Println(dest) 343 | } 344 | ``` 345 | 346 | Output: 347 | 348 | ``` 349 | {3} 350 | ``` 351 | 352 | String pointer destinations can be set to `nil` by writing `null` in an Hjson file. The same goes for a pointer destination of any type that implements `UnmarshalText()`. 353 | 354 | ## ElemTyper interface 355 | 356 | If a destination type implements hjson.ElemTyper, Unmarshal() will call ElemType() on the destination when unmarshalling an array or an object, to see if any array element or leaf node should be of type string even if it can be treated as a number, boolean or null. This is most useful if the destination also implements the json.Unmarshaler interface, because then there is no other way for Unmarshal() to know the type of the elements on the destination. If a destination implements ElemTyper all of its elements must be of the same type. 357 | 358 | Example implementation for a generic ordered map: 359 | 360 | ```go 361 | 362 | func (o *OrderedMap[T]) ElemType() reflect.Type { 363 | return reflect.TypeOf((*T)(nil)).Elem() 364 | } 365 | ``` 366 | 367 | ## Max nesting depth 368 | 369 | In order to avoid stack overflow all unmarshal-functions return an error if the Hjson input contains a tree more than 10000 levels deep. This is the same limit as in the Go standard library package `encoding/json`. 370 | 371 | # API 372 | 373 | [![godoc](https://godoc.org/github.com/hjson/hjson-go/v4?status.svg)](https://godoc.org/github.com/hjson/hjson-go/v4) 374 | 375 | # History 376 | 377 | [see releases](https://github.com/hjson/hjson-go/releases) 378 | -------------------------------------------------------------------------------- /node_test.go: -------------------------------------------------------------------------------- 1 | package hjson 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func compareStrings(t *testing.T, bOut []byte, txtExpected string) { 9 | if string(bOut) != txtExpected { 10 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n\n", txtExpected, string(bOut)) 11 | } 12 | } 13 | 14 | func verifyNodeContent(t *testing.T, node *Node, txtExpected string) { 15 | opt := DefaultOptions() 16 | opt.EmitRootBraces = false 17 | bOut, err := MarshalWithOptions(node, opt) 18 | if err != nil { 19 | t.Error(err) 20 | } 21 | 22 | compareStrings(t, bOut, txtExpected) 23 | } 24 | 25 | func TestNode1(t *testing.T) { 26 | txt := `b: 1 27 | a: 2` 28 | 29 | var node *Node 30 | err := Unmarshal([]byte(txt), &node) 31 | if err != nil { 32 | t.Error(err) 33 | } 34 | 35 | verifyNodeContent(t, node, txt) 36 | } 37 | 38 | func TestNode2(t *testing.T) { 39 | txt := `# comment before 40 | b: 1 # comment after 41 | // Comment B4 42 | a: 2 43 | /* Last comment */` 44 | 45 | var node *Node 46 | err := Unmarshal([]byte(txt), &node) 47 | if err != nil { 48 | t.Error(err) 49 | } 50 | 51 | if node.Len() != 2 { 52 | t.Errorf("Unexpected map length: %v", node.Len()) 53 | } 54 | 55 | aVal, ok, err := node.AtKey("a") 56 | if err != nil { 57 | t.Error(err) 58 | } 59 | // The value will be a float64 even though it was written without decimals. 60 | if aVal != 2.0 { 61 | t.Errorf("Unexpected value for key 'a': %v", aVal) 62 | } else if !ok { 63 | t.Errorf("node.AtKey('a') returned false") 64 | } 65 | 66 | bKey, bVal, err := node.AtIndex(0) 67 | if err != nil { 68 | t.Error(err) 69 | } 70 | if bKey != "b" { 71 | t.Errorf("Expected key 'b', got: %v", bKey) 72 | } 73 | // The value will be a float64 even though it was written without decimals. 74 | if bVal != 1.0 { 75 | t.Errorf("Unexpected value for key 'b': %v", bVal) 76 | } 77 | 78 | verifyNodeContent(t, node, txt) 79 | 80 | opt := DefaultOptions() 81 | opt.Comments = false 82 | bOut, err := MarshalWithOptions(node, opt) 83 | if err != nil { 84 | t.Error(err) 85 | } 86 | 87 | compareStrings(t, bOut, `{ 88 | b: 1 89 | a: 2 90 | }`) 91 | 92 | bOut, err = json.Marshal(node) 93 | if err != nil { 94 | t.Error(err) 95 | } 96 | 97 | compareStrings(t, bOut, `{"b":1,"a":2}`) 98 | 99 | intLen := node.Value.(*OrderedMap).Map["b"].(*Node).Len() 100 | if intLen != 0 { 101 | t.Errorf("Unexpected int length: %v", intLen) 102 | } 103 | 104 | node.SetIndex(0, 3) 105 | 106 | verifyNodeContent(t, node, `# comment before 107 | b: 3 # comment after 108 | // Comment B4 109 | a: 2 110 | /* Last comment */`) 111 | 112 | oldVal, found, err := node.SetKey("b", "abcdef") 113 | if err != nil { 114 | t.Error(err) 115 | } 116 | if !found { 117 | t.Errorf("Should have returned true, the key should already exist.") 118 | } 119 | if oldVal != 3 { 120 | t.Errorf("Expected old value 3, got: '%v'", oldVal) 121 | } 122 | 123 | verifyNodeContent(t, node, `# comment before 124 | b: "abcdef" # comment after 125 | // Comment B4 126 | a: 2 127 | /* Last comment */`) 128 | 129 | strLen := node.Value.(*OrderedMap).Map["b"].(*Node).Len() 130 | if strLen != 6 { 131 | t.Errorf("Unexpected string length: %v", strLen) 132 | } 133 | 134 | node.Value.(*OrderedMap).Map["b"] = "xyz" 135 | 136 | verifyNodeContent(t, node, `b: xyz 137 | // Comment B4 138 | a: 2 139 | /* Last comment */`) 140 | } 141 | 142 | func TestNode3(t *testing.T) { 143 | txt := `# comment before 144 | [ 145 | # after [ 146 | 1 # comment after 147 | // Comment B4 148 | 2 149 | # COmment After 150 | ] 151 | /* Last comment */` 152 | 153 | var node *Node 154 | Unmarshal([]byte(txt), &node) 155 | 156 | if node.Len() != 2 { 157 | t.Errorf("Unexpected slice length: %v", node.Len()) 158 | } 159 | 160 | firstKey, firstVal, err := node.AtIndex(0) 161 | if err != nil { 162 | t.Error(err) 163 | } 164 | if firstKey != "" { 165 | t.Errorf("Expected empty key, got: %v", firstKey) 166 | } 167 | // The value will be a float64 even though it was written without decimals. 168 | if firstVal != 1.0 { 169 | t.Errorf("Unexpected value for index 0: %v", firstVal) 170 | } 171 | 172 | verifyNodeContent(t, node, txt) 173 | 174 | opt := DefaultOptions() 175 | opt.Comments = false 176 | bOut, err := MarshalWithOptions(node, opt) 177 | if err != nil { 178 | t.Error(err) 179 | } 180 | 181 | compareStrings(t, bOut, `[ 182 | 1 183 | 2 184 | ]`) 185 | 186 | bOut, err = json.Marshal(node) 187 | 188 | compareStrings(t, bOut, `[1,2]`) 189 | 190 | intLen := node.Value.([]interface{})[1].(*Node).Len() 191 | if intLen != 0 { 192 | t.Errorf("Unexpected int length: %v", intLen) 193 | } 194 | 195 | key, oldVal, err := node.SetIndex(1, "abcdef") 196 | if err != nil { 197 | t.Error(err) 198 | } 199 | if key != "" { 200 | t.Errorf("Expected empty key, got: '%v'", key) 201 | } 202 | if oldVal != 2.0 { 203 | t.Errorf("Expected old value 2.0, got: '%v'", oldVal) 204 | } 205 | 206 | verifyNodeContent(t, node, `# comment before 207 | [ 208 | # after [ 209 | 1 # comment after 210 | // Comment B4 211 | abcdef 212 | # COmment After 213 | ] 214 | /* Last comment */`) 215 | 216 | strLen := node.Value.([]interface{})[1].(*Node).Len() 217 | if strLen != 6 { 218 | t.Errorf("Unexpected string length: %v", strLen) 219 | } 220 | 221 | node.Value.([]interface{})[0] = "xyz" 222 | 223 | verifyNodeContent(t, node, `# comment before 224 | [ 225 | xyz 226 | // Comment B4 227 | abcdef 228 | # COmment After 229 | ] 230 | /* Last comment */`) 231 | } 232 | 233 | func TestNode4(t *testing.T) { 234 | txt := `# comment before 235 | b: /* key comment */ { 236 | sub1: 1 # comment after 237 | } # cm after obj 238 | // Comment B4 239 | a: 2 240 | /* Last comment */` 241 | 242 | var node *Node 243 | err := Unmarshal([]byte(txt), &node) 244 | if err != nil { 245 | t.Error(err) 246 | } 247 | 248 | verifyNodeContent(t, node, txt) 249 | 250 | sub1Val, ok, err := node.NK("b").AtKey("sub1") 251 | if err != nil { 252 | t.Error(err) 253 | } 254 | // The value will be a float64 even though it was written without decimals. 255 | if sub1Val != 1.0 { 256 | t.Errorf("Unexpected value for sub1: %v", sub1Val) 257 | } else if !ok { 258 | t.Errorf("AtKey('sub1') returned false") 259 | } 260 | 261 | sub1Val, ok, err = node.NK("Z").AtKey("sub2") 262 | if err != nil { 263 | t.Error(err) 264 | } 265 | if ok { 266 | t.Errorf("Should have returned false when calling AtKey() on nil") 267 | } 268 | 269 | oldVal, found, err := node.NK("Z").SetKey("sub2", 3) 270 | if err == nil { 271 | t.Errorf("Should have returned an error calling SetKey() on nil") 272 | } 273 | 274 | oldVal, found, err = node.NKC("Z").SetKey("sub2", 3) 275 | if err != nil { 276 | t.Error(err) 277 | } 278 | if found { 279 | t.Errorf("Should have returned false, the key should not already exist.") 280 | } 281 | 282 | oldVal, found, err = node.NKC("Z").SetKey("sub2", 4) 283 | if err != nil { 284 | t.Error(err) 285 | } 286 | if !found { 287 | t.Errorf("Should have returned true, the key should already exist.") 288 | } 289 | if oldVal != 3 { 290 | t.Errorf("Expected old value 3, got: '%v'", oldVal) 291 | } 292 | 293 | oldVal, found, err = node.NKC("X").NKC("Y").SetKey("sub3", 5) 294 | if err != nil { 295 | t.Error(err) 296 | } 297 | if found { 298 | t.Errorf("Should have returned false, the key should not already exist.") 299 | } 300 | 301 | verifyNodeContent(t, node, `# comment before 302 | b: /* key comment */ { 303 | sub1: 1 # comment after 304 | } # cm after obj 305 | // Comment B4 306 | a: 2 307 | Z: { 308 | sub2: 4 309 | } 310 | X: { 311 | Y: { 312 | sub3: 5 313 | } 314 | } 315 | /* Last comment */`) 316 | } 317 | 318 | func TestDisallowDuplicateKeys(t *testing.T) { 319 | txt := `a: 1 320 | a: 2 321 | b: 3 322 | c: 4 323 | b: 5` 324 | 325 | var node *Node 326 | err := Unmarshal([]byte(txt), &node) 327 | if err != nil { 328 | t.Error(err) 329 | } 330 | 331 | verifyNodeContent(t, node, `a: 2 332 | b: 5 333 | c: 4`) 334 | 335 | decOpt := DefaultDecoderOptions() 336 | decOpt.DisallowDuplicateKeys = true 337 | err = UnmarshalWithOptions([]byte(txt), &node, decOpt) 338 | if err == nil { 339 | t.Errorf("Should have returned error because of duplicate keys.") 340 | } 341 | } 342 | 343 | func TestWhitespaceAsComments(t *testing.T) { 344 | txt := ` 345 | 346 | a: 2 347 | b: 3 348 | 349 | ` 350 | 351 | var node *Node 352 | err := Unmarshal([]byte(txt), &node) 353 | if err != nil { 354 | t.Error(err) 355 | } 356 | 357 | verifyNodeContent(t, node, txt) 358 | 359 | decOpt := DefaultDecoderOptions() 360 | decOpt.WhitespaceAsComments = false 361 | err = UnmarshalWithOptions([]byte(txt), &node, decOpt) 362 | if err != nil { 363 | t.Error(err) 364 | } 365 | 366 | verifyNodeContent(t, node, `a: 2 367 | b: 3`) 368 | } 369 | 370 | func TestDeclareNodeMap(t *testing.T) { 371 | var node Node 372 | 373 | node2 := node.NK("a") 374 | if node2 != nil { 375 | t.Errorf("node.NK() created a node") 376 | } 377 | 378 | oldVal, found, err := node.NKC("a").NKC("aa").NKC("aaa").SetKey("aaaa", "a string") 379 | if err != nil { 380 | t.Error(err) 381 | } 382 | if found { 383 | t.Errorf("Should have returned false, the key should not already exist.") 384 | } 385 | oldVal, found, err = node.SetKey("b", 2) 386 | if err != nil { 387 | t.Error(err) 388 | } 389 | if found { 390 | t.Errorf("Should have returned false, the key should not already exist.") 391 | } 392 | key, oldVal, err := node.SetIndex(1, 3.0) 393 | if err != nil { 394 | t.Error(err) 395 | } 396 | if key != "b" { 397 | t.Errorf("Expected key 'b', got: '%v'", key) 398 | } 399 | if oldVal != 2 { 400 | t.Errorf("Expected old value 2, got: '%v'", oldVal) 401 | } 402 | 403 | verifyNodeContent(t, &node, `a: { 404 | aa: { 405 | aaa: { 406 | aaaa: a string 407 | } 408 | } 409 | } 410 | b: 3`) 411 | 412 | err = node.Append(4) 413 | if err == nil { 414 | t.Errorf("Should have returned error when trying to append to a map") 415 | } 416 | } 417 | 418 | func TestDeclareNodeSlice(t *testing.T) { 419 | var node Node 420 | 421 | node2 := node.NI(0) 422 | if node2 != nil { 423 | t.Errorf("node.NI() created a node") 424 | } 425 | 426 | err := node.Append(13) 427 | if err != nil { 428 | t.Error(err) 429 | } 430 | err = node.Append("b") 431 | if err != nil { 432 | t.Error(err) 433 | } 434 | key, oldVal, err := node.SetIndex(1, false) 435 | if err != nil { 436 | t.Error(err) 437 | } 438 | if key != "" { 439 | t.Errorf("Expected empty key, got: '%v'", key) 440 | } 441 | if oldVal != "b" { 442 | t.Errorf("Expected old value 'b', got: '%v'", oldVal) 443 | } 444 | 445 | verifyNodeContent(t, &node, `[ 446 | 13 447 | false 448 | ]`) 449 | 450 | node2 = node.NKC("sub") 451 | if node2 != nil { 452 | t.Errorf("Should not have been able to create a node by key in a slice") 453 | } 454 | 455 | _, _, err = node.SetKey("a", 4) 456 | if err == nil { 457 | t.Errorf("Should have returned error when trying to set by key on a slice") 458 | } 459 | } 460 | 461 | func TestNodeNoPointer(t *testing.T) { 462 | txt := `setting1: null # nada 463 | setting2: true // yes` 464 | 465 | var node Node 466 | err := Unmarshal([]byte(txt), &node) 467 | if err != nil { 468 | t.Error(err) 469 | } 470 | oldVal, found, err := node.SetKey("setting1", 3) 471 | if err != nil { 472 | t.Error(err) 473 | } 474 | if !found { 475 | t.Errorf("Should have returned true, the key should already exist") 476 | } 477 | if oldVal != nil { 478 | t.Errorf("Expected old value nil, got: '%v'", oldVal) 479 | } 480 | output, err := Marshal(node) 481 | if err != nil { 482 | t.Error(err) 483 | } 484 | 485 | compareStrings(t, output, `{ 486 | setting1: 3 # nada 487 | setting2: true // yes 488 | }`) 489 | } 490 | 491 | func TestNodeOrderedMapInsertDelete(t *testing.T) { 492 | txt := `a: 1 493 | b: 2` 494 | 495 | var node Node 496 | err := Unmarshal([]byte(txt), &node) 497 | if err != nil { 498 | t.Error(err) 499 | } 500 | 501 | key, val, err := node.DeleteIndex(0) 502 | if err != nil { 503 | t.Error(err) 504 | } 505 | if key != "a" { 506 | t.Errorf("Expected key 'a', got: '%v'", key) 507 | } 508 | if val != 1.0 { 509 | t.Errorf("Expected old value 1.0, got: '%#v'", val) 510 | } 511 | 512 | val, found, err := node.Insert(1, key, val) 513 | if err != nil { 514 | t.Error(err) 515 | } 516 | if found { 517 | t.Errorf("Found key '%v' that should not have been found", key) 518 | } 519 | 520 | val, found, err = node.DeleteKey("a") 521 | if err != nil { 522 | t.Error(err) 523 | } 524 | if !found { 525 | t.Errorf("Expected to find key 'a' but did not") 526 | } 527 | if val != 1.0 { 528 | t.Errorf("Expected deleted value 1.0, got: '%#v'", val) 529 | } 530 | 531 | val, found, err = node.Insert(0, "c", 3) 532 | if err != nil { 533 | t.Error(err) 534 | } 535 | if found { 536 | t.Error("Found key c that should not have been found") 537 | } 538 | 539 | verifyNodeContent(t, &node, `c: 3 540 | b: 2`) 541 | } 542 | 543 | func TestNodeSliceInsertDelete(t *testing.T) { 544 | txt := `[ 545 | 1 546 | 2 547 | ]` 548 | 549 | var node Node 550 | err := Unmarshal([]byte(txt), &node) 551 | if err != nil { 552 | t.Error(err) 553 | } 554 | 555 | key, val, err := node.DeleteIndex(0) 556 | if err != nil { 557 | t.Error(err) 558 | } 559 | if key != "" { 560 | t.Errorf("Expected empty key, got: '%v'", key) 561 | } 562 | if val != 1.0 { 563 | t.Errorf("Expected old value 1.0, got: '%#v'", val) 564 | } 565 | 566 | val, found, err := node.Insert(1, key, val) 567 | if err != nil { 568 | t.Error(err) 569 | } 570 | if found { 571 | t.Errorf("Found key '%v' that should not have been found", key) 572 | } 573 | 574 | val, found, err = node.Insert(0, "c", 3) 575 | if err != nil { 576 | t.Error(err) 577 | } 578 | if found { 579 | t.Error("Found key c that should not have been found") 580 | } 581 | 582 | // The value '2' has a line break as Cm.After 583 | verifyNodeContent(t, &node, `[ 584 | 3 585 | 2 586 | 1 587 | ]`) 588 | } 589 | -------------------------------------------------------------------------------- /node.go: -------------------------------------------------------------------------------- 1 | package hjson 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | type Comments struct { 10 | // Comment/whitespace on line(s) before the value, and before the value on 11 | // the same line. If not empty, is expected to end with a line feed + 12 | // indentation for the value. 13 | Before string 14 | // Comment/whitespace between the key and this value, if this value is an 15 | // element in a map/object. 16 | Key string 17 | // Comment/whitespace after (but still on the same line as) the leading 18 | // bracket ({ or [) for this value, if this value is a slice/array or 19 | // map/object. Is not expected to contain any line feed. 20 | InsideFirst string 21 | // Comment/whitespace from the beginning of the first line after all child 22 | // values belonging to this value, until the closing bracket (} or ]), if 23 | // this value is a slice/array or map/object. If not empty, is expected to 24 | // end with a line feed + indentation for the closing bracket. 25 | InsideLast string 26 | // Comment/whitespace after (but still on the same line as) the value. Is not 27 | // expected to contain any line feed. hjson.Unmarshal() will try to assign 28 | // comments/whitespace from lines between values to `Before` on the value 29 | // after those lines, or to `InsideLast` on the slice/map if the lines 30 | // containing comments appear after the last element inside a slice/map. 31 | After string 32 | } 33 | 34 | // Node must be used as destination for Unmarshal() or UnmarshalWithOptions() 35 | // whenever comments should be read from the input. The struct is simply a 36 | // wrapper for the actual values and a helper struct containing any comments. 37 | // The Value in the destination Node will be overwritten in the call to 38 | // Unmarshal() or UnmarshalWithOptions(), i.e. node trees are not merged. 39 | // After the unmarshal, Node.Value will contain any of these types: 40 | // 41 | // nil (no type) 42 | // float64 (if UseJSONNumber == false) 43 | // json.Number (if UseJSONNumber == true) 44 | // string 45 | // bool 46 | // []interface{} 47 | // *hjson.OrderedMap 48 | // 49 | // All elements in an []interface{} or *hjson.OrderedMap will be of the type 50 | // *hjson.Node, so that they can contain comments. 51 | // 52 | // This example shows unmarshalling input with comments, changing the value on 53 | // a single key (the input is assumed to have an object/map as root) and then 54 | // marshalling the node tree again, including comments and with preserved key 55 | // order in the object/map. 56 | // 57 | // var node hjson.Node 58 | // err := hjson.Unmarshal(input, &node) 59 | // if err != nil { 60 | // return err 61 | // } 62 | // _, err = node.SetKey("setting1", 3) 63 | // if err != nil { 64 | // return err 65 | // } 66 | // output, err := hjson.Marshal(node) 67 | // if err != nil { 68 | // return err 69 | // } 70 | type Node struct { 71 | Value interface{} 72 | Cm Comments 73 | } 74 | 75 | // Len returns the length of the value wrapped by this Node, if the value is of 76 | // type *hjson.OrderedMap, []interface{} or string. Otherwise 0 is returned. 77 | func (c *Node) Len() int { 78 | if c == nil { 79 | return 0 80 | } 81 | switch cont := c.Value.(type) { 82 | case *OrderedMap: 83 | return cont.Len() 84 | case []interface{}: 85 | return len(cont) 86 | case string: 87 | return len(cont) 88 | } 89 | return 0 90 | } 91 | 92 | // AtIndex returns the key (if any) and value (unwrapped from its Node) found 93 | // at the specified index, if this Node contains a value of type 94 | // *hjson.OrderedMap or []interface{}. Returns an error for unexpected types. 95 | // Panics if index < 0 or index >= Len(). 96 | func (c *Node) AtIndex(index int) (string, interface{}, error) { 97 | if c == nil { 98 | return "", nil, fmt.Errorf("Node is nil") 99 | } 100 | var key string 101 | var elem interface{} 102 | switch cont := c.Value.(type) { 103 | case *OrderedMap: 104 | key = cont.Keys[index] 105 | elem = cont.Map[key] 106 | case []interface{}: 107 | elem = cont[index] 108 | default: 109 | return "", nil, fmt.Errorf("Unexpected value type: %v", reflect.TypeOf(c.Value)) 110 | } 111 | node, ok := elem.(*Node) 112 | if !ok { 113 | return "", nil, fmt.Errorf("Unexpected element type: %v", reflect.TypeOf(elem)) 114 | } 115 | return key, node.Value, nil 116 | } 117 | 118 | // AtKey returns the value (unwrapped from its Node) found for the specified 119 | // key, if this Node contains a value of type *hjson.OrderedMap. An error is 120 | // returned for unexpected types. The second returned value is true if the key 121 | // was found, false otherwise. 122 | func (c *Node) AtKey(key string) (interface{}, bool, error) { 123 | if c == nil { 124 | return nil, false, nil 125 | } 126 | om, ok := c.Value.(*OrderedMap) 127 | if !ok { 128 | return nil, false, fmt.Errorf("Unexpected value type: %v", reflect.TypeOf(c.Value)) 129 | } 130 | elem, ok := om.Map[key] 131 | if !ok { 132 | return nil, false, nil 133 | } 134 | node, ok := elem.(*Node) 135 | if !ok { 136 | return nil, false, fmt.Errorf("Unexpected element type: %v", reflect.TypeOf(elem)) 137 | } 138 | return node.Value, true, nil 139 | } 140 | 141 | // Append adds the input value to the end of the []interface{} wrapped by this 142 | // Node. If this Node contains nil without a type, an empty []interface{} is 143 | // first created. If this Node contains a value of any other type, an error is 144 | // returned. 145 | func (c *Node) Append(value interface{}) error { 146 | if c == nil { 147 | return fmt.Errorf("Node is nil") 148 | } 149 | var arr []interface{} 150 | if c.Value == nil { 151 | arr = []interface{}{} 152 | } else { 153 | var ok bool 154 | arr, ok = c.Value.([]interface{}) 155 | if !ok { 156 | return fmt.Errorf("Unexpected value type: %v", reflect.TypeOf(c.Value)) 157 | } 158 | } 159 | c.Value = append(arr, &Node{Value: value}) 160 | return nil 161 | } 162 | 163 | // Insert inserts a new key/value pair at the specified index, if this Node 164 | // contains a value of type *hjson.OrderedMap or []interface{}. Returns an error 165 | // for unexpected types. Panics if index < 0 or index > c.Len(). If the key 166 | // already exists in the OrderedMap, the new value is set but the position of 167 | // the key is not changed. Otherwise the value to insert is wrapped in a new 168 | // Node. If this Node contains []interface{}, the key is ignored. Returns the 169 | // old value and true if the key already exists in the/ OrderedMap, nil and 170 | // false otherwise. 171 | func (c *Node) Insert(index int, key string, value interface{}) (interface{}, bool, error) { 172 | if c == nil { 173 | return nil, false, fmt.Errorf("Node is nil") 174 | } 175 | var oldVal interface{} 176 | var found bool 177 | switch cont := c.Value.(type) { 178 | case *OrderedMap: 179 | oldVal, found := cont.Map[key] 180 | if found { 181 | if node, ok := oldVal.(*Node); ok { 182 | oldVal = node.Value 183 | node.Value = value 184 | } else { 185 | cont.Map[key] = &Node{Value: value} 186 | } 187 | } else { 188 | oldVal, found = cont.Insert(index, key, &Node{Value: value}) 189 | } 190 | case []interface{}: 191 | value = &Node{Value: value} 192 | if index == len(cont) { 193 | c.Value = append(cont, value) 194 | } else { 195 | cont = append(cont[:index+1], cont[index:]...) 196 | cont[index] = value 197 | c.Value = cont 198 | } 199 | default: 200 | return nil, false, fmt.Errorf("Unexpected value type: %v", reflect.TypeOf(c.Value)) 201 | } 202 | return oldVal, found, nil 203 | } 204 | 205 | // SetIndex assigns the specified value to the child Node found at the specified 206 | // index, if this Node contains a value of type *hjson.OrderedMap or 207 | // []interface{}. Returns an error for unexpected types. Returns the key (if 208 | // any) and value previously found at the specified index. Panics if index < 0 209 | // or index >= Len(). 210 | func (c *Node) SetIndex(index int, value interface{}) (string, interface{}, error) { 211 | if c == nil { 212 | return "", nil, fmt.Errorf("Node is nil") 213 | } 214 | var key string 215 | var elem interface{} 216 | switch cont := c.Value.(type) { 217 | case *OrderedMap: 218 | key = cont.Keys[index] 219 | elem = cont.Map[key] 220 | case []interface{}: 221 | elem = cont[index] 222 | default: 223 | return "", nil, fmt.Errorf("Unexpected value type: %v", reflect.TypeOf(c.Value)) 224 | } 225 | var oldVal interface{} 226 | node, ok := elem.(*Node) 227 | if ok { 228 | oldVal = node.Value 229 | node.Value = value 230 | } else { 231 | oldVal = elem 232 | switch cont := c.Value.(type) { 233 | case *OrderedMap: 234 | cont.Map[key] = &Node{Value: value} 235 | case []interface{}: 236 | cont[index] = &Node{Value: value} 237 | } 238 | } 239 | return key, oldVal, nil 240 | } 241 | 242 | // SetKey assigns the specified value to the child Node identified by the 243 | // specified key, if this Node contains a value of the type *hjson.OrderedMap. 244 | // If this Node contains nil without a type, an empty *hjson.OrderedMap is 245 | // first created. If this Node contains a value of any other type an error is 246 | // returned. If the key cannot be found in the OrderedMap, a new Node is 247 | // created, wrapping the specified value, and appended to the end of the 248 | // OrderedMap. Returns the old value and true if the key already existed in 249 | // the OrderedMap, nil and false otherwise. 250 | func (c *Node) SetKey(key string, value interface{}) (interface{}, bool, error) { 251 | if c == nil { 252 | return nil, false, fmt.Errorf("Node is nil") 253 | } 254 | var om *OrderedMap 255 | if c.Value == nil { 256 | om = NewOrderedMap() 257 | c.Value = om 258 | } else { 259 | var ok bool 260 | om, ok = c.Value.(*OrderedMap) 261 | if !ok { 262 | return nil, false, fmt.Errorf("Unexpected value type: %v", reflect.TypeOf(c.Value)) 263 | } 264 | } 265 | var oldVal interface{} 266 | elem, ok := om.Map[key] 267 | if ok { 268 | var node *Node 269 | node, ok = elem.(*Node) 270 | if ok { 271 | oldVal = node.Value 272 | node.Value = value 273 | } 274 | } 275 | foundKey := true 276 | if !ok { 277 | oldVal, foundKey = om.Set(key, &Node{Value: value}) 278 | } 279 | return oldVal, foundKey, nil 280 | } 281 | 282 | // DeleteIndex deletes the value or key/value pair found at the specified index, 283 | // if this Node contains a value of type *hjson.OrderedMap or []interface{}. 284 | // Returns an error for unexpected types. Panics if index < 0 or 285 | // index >= c.Len(). Returns the deleted key (if any) and value. 286 | func (c *Node) DeleteIndex(index int) (string, interface{}, error) { 287 | if c == nil { 288 | return "", nil, fmt.Errorf("Node is nil") 289 | } 290 | var key string 291 | var value interface{} 292 | switch cont := c.Value.(type) { 293 | case *OrderedMap: 294 | key, value = cont.DeleteIndex(index) 295 | case []interface{}: 296 | value = cont[index] 297 | cont = append(cont[:index], cont[index+1:]...) 298 | c.Value = cont 299 | default: 300 | return "", nil, fmt.Errorf("Unexpected value type: %v", reflect.TypeOf(c.Value)) 301 | } 302 | if node, ok := value.(*Node); ok { 303 | value = node.Value 304 | } 305 | return key, value, nil 306 | } 307 | 308 | // DeleteKey deletes the key/value pair with the specified key, if found and if 309 | // this Node contains a value of type *hjson.OrderedMap. Returns an error for 310 | // unexpected types. Returns the deleted value and true if the key was found, 311 | // nil and false otherwise. 312 | func (c *Node) DeleteKey(key string) (interface{}, bool, error) { 313 | if c == nil { 314 | return nil, false, fmt.Errorf("Node is nil") 315 | } 316 | if om, ok := c.Value.(*OrderedMap); ok { 317 | oldValue, found := om.DeleteKey(key) 318 | if node, ok := oldValue.(*Node); ok { 319 | oldValue = node.Value 320 | } 321 | return oldValue, found, nil 322 | } 323 | return nil, false, fmt.Errorf("Unexpected value type: %v", reflect.TypeOf(c.Value)) 324 | } 325 | 326 | // NI is an acronym formed from "get Node pointer by Index". Returns the *Node 327 | // element found at the specified index, if this Node contains a value of type 328 | // *hjson.OrderedMap or []interface{}. Returns nil otherwise. Panics if 329 | // index < 0 or index >= Len(). Does not create or alter any value. 330 | func (c *Node) NI(index int) *Node { 331 | if c == nil { 332 | return nil 333 | } 334 | var elem interface{} 335 | switch cont := c.Value.(type) { 336 | case *OrderedMap: 337 | elem = cont.AtIndex(index) 338 | case []interface{}: 339 | elem = cont[index] 340 | default: 341 | return nil 342 | } 343 | if node, ok := elem.(*Node); ok { 344 | return node 345 | } 346 | return nil 347 | } 348 | 349 | // NK is an acronym formed from "get Node pointer by Key". Returns the *Node 350 | // element found for the specified key, if this Node contains a value of type 351 | // *hjson.OrderedMap. Returns nil otherwise. Does not create or alter anything. 352 | func (c *Node) NK(key string) *Node { 353 | if c == nil { 354 | return nil 355 | } 356 | om, ok := c.Value.(*OrderedMap) 357 | if !ok { 358 | return nil 359 | } 360 | if elem, ok := om.Map[key]; ok { 361 | if node, ok := elem.(*Node); ok { 362 | return node 363 | } 364 | } 365 | return nil 366 | } 367 | 368 | // NKC is an acronym formed from "get Node pointer by Key, Create if not found". 369 | // Returns the *Node element found for the specified key, if this Node contains 370 | // a value of type *hjson.OrderedMap. If this Node contains nil without a type, 371 | // an empty *hjson.OrderedMap is first created. If this Node contains a value of 372 | // any other type or if the element idendified by the specified key is not of 373 | // type *Node, an error is returned. If the key cannot be found in the 374 | // OrderedMap, a new Node is created for the specified key. Example usage: 375 | // 376 | // var node hjson.Node 377 | // node.NKC("rootKey1").NKC("subKey1").SetKey("valKey1", "my value") 378 | func (c *Node) NKC(key string) *Node { 379 | if c == nil { 380 | return nil 381 | } 382 | var om *OrderedMap 383 | if c.Value == nil { 384 | om = NewOrderedMap() 385 | c.Value = om 386 | } else { 387 | var ok bool 388 | om, ok = c.Value.(*OrderedMap) 389 | if !ok { 390 | return nil 391 | } 392 | } 393 | if elem, ok := om.Map[key]; ok { 394 | if node, ok := elem.(*Node); ok { 395 | return node 396 | } 397 | } else { 398 | node := &Node{} 399 | om.Set(key, node) 400 | return node 401 | } 402 | return nil 403 | } 404 | 405 | // MarshalJSON is an implementation of the json.Marshaler interface, enabling 406 | // hjson.Node trees to be used as input for json.Marshal(). 407 | func (c Node) MarshalJSON() ([]byte, error) { 408 | return json.Marshal(c.Value) 409 | } 410 | 411 | // UnmarshalJSON is an implementation of the json.Unmarshaler interface, 412 | // enabling hjson.Node to be used as destination for json.Unmarshal(). 413 | func (c *Node) UnmarshalJSON(b []byte) error { 414 | return Unmarshal(b, c) 415 | } 416 | -------------------------------------------------------------------------------- /encode_test.go: -------------------------------------------------------------------------------- 1 | package hjson 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "net" 8 | "reflect" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func marshalUnmarshalExpected( 14 | t *testing.T, 15 | expectedHjson string, 16 | expectedDst, 17 | src, 18 | dst interface{}, 19 | ) { 20 | buf, err := Marshal(src) 21 | if err != nil { 22 | t.Error(err) 23 | } 24 | if string(buf) != expectedHjson { 25 | t.Errorf("Expected:\n%s\nGot:\n%s\n\n", expectedHjson, string(buf)) 26 | } 27 | 28 | decOpt := DefaultDecoderOptions() 29 | decOpt.DisallowUnknownFields = true 30 | err = UnmarshalWithOptions(buf, dst, decOpt) 31 | if err != nil { 32 | t.Error(err) 33 | } 34 | if !reflect.DeepEqual(expectedDst, dst) { 35 | t.Errorf("Expected:\n%#v\nGot:\n%#v\n\n", expectedDst, dst) 36 | } 37 | } 38 | 39 | type TestStruct struct { 40 | A int 41 | B uint 42 | C string `json:"S"` 43 | D string `json:",omitempty"` 44 | E string `json:"-"` 45 | F string `json:"-,"` 46 | G string `json:"H,omitempty"` 47 | J string `json:"J,omitempty"` 48 | U int `json:",omitempty"` 49 | V uint `json:",omitempty"` 50 | W float32 `json:",omitempty"` 51 | X bool `json:",omitempty"` 52 | Y []int `json:",omitempty"` 53 | Z *TestStruct `json:",omitempty"` 54 | } 55 | 56 | func TestEncodeEmptyStruct(t *testing.T) { 57 | buf, err := Marshal(struct{}{}) 58 | if err != nil { 59 | t.Error(err) 60 | } 61 | if string(buf) != "{}" { 62 | t.Error("Empty struct encoding error") 63 | } 64 | } 65 | 66 | func TestEncodeStruct(t *testing.T) { 67 | var output map[string]interface{} 68 | input := TestStruct{ 69 | A: 1, 70 | B: 2, 71 | C: "foo", 72 | D: "bar", 73 | E: "baz", 74 | F: "qux", 75 | G: "thud", 76 | J: "<", 77 | U: 3, 78 | V: 4, 79 | W: 5.0, 80 | X: true, 81 | Y: []int{1, 2, 3}, 82 | Z: &TestStruct{}, 83 | } 84 | 85 | buf, err := Marshal(input) 86 | if err != nil { 87 | t.Error(err) 88 | } 89 | if !bytes.Contains(buf, []byte("J: <\n")) { 90 | t.Errorf("Missing 'J: <' in marshal output:\n%s", string(buf)) 91 | } 92 | err = Unmarshal(buf, &output) 93 | if err != nil { 94 | t.Error(err) 95 | } 96 | checkKeyValue(t, output, "A", 1.0) 97 | checkKeyValue(t, output, "B", 2.0) 98 | checkKeyValue(t, output, "S", "foo") 99 | checkKeyValue(t, output, "D", "bar") 100 | checkKeyValue(t, output, "-", "qux") 101 | checkKeyValue(t, output, "H", "thud") 102 | checkMissing(t, output, "C") 103 | checkKeyValue(t, output, "J", "<") 104 | checkMissing(t, output, "E") 105 | checkMissing(t, output, "F") 106 | checkKeyValue(t, output, "U", 3.0) 107 | checkKeyValue(t, output, "V", 4.0) 108 | checkKeyValue(t, output, "W", 5.0) 109 | checkKeyValue(t, output, "X", true) 110 | checkKeyValue(t, output, "Y", []interface{}{1.0, 2.0, 3.0}) 111 | checkKeyValue(t, output, "Z", map[string]interface{}{ 112 | "A": 0.0, 113 | "B": 0.0, 114 | "S": "", 115 | "-": "", 116 | }) 117 | input.D = "" 118 | input.G = "" 119 | input.U = 0 120 | input.V = 0 121 | input.W = 0.0 122 | input.X = false 123 | input.Y = nil 124 | input.Z = nil 125 | buf, err = Marshal(input) 126 | if err != nil { 127 | t.Error(err) 128 | } 129 | output = map[string]interface{}{} 130 | err = Unmarshal(buf, &output) 131 | if err != nil { 132 | t.Error(err) 133 | } 134 | checkKeyValue(t, output, "A", 1.0) 135 | checkKeyValue(t, output, "B", 2.0) 136 | checkMissing(t, output, "C") 137 | checkMissing(t, output, "D") 138 | checkMissing(t, output, "E") 139 | checkMissing(t, output, "G") 140 | checkMissing(t, output, "H") 141 | checkMissing(t, output, "U") 142 | checkMissing(t, output, "V") 143 | checkMissing(t, output, "W") 144 | checkMissing(t, output, "X") 145 | checkMissing(t, output, "Y") 146 | checkMissing(t, output, "Z") 147 | } 148 | 149 | func checkKeyValue(t *testing.T, m map[string]interface{}, key string, exp interface{}) { 150 | obs, ok := m[key] 151 | if !ok { 152 | t.Error("Missing key", key) 153 | } 154 | if !reflect.DeepEqual(exp, obs) { 155 | t.Errorf("%v != %v", exp, obs) 156 | } 157 | } 158 | 159 | func checkMissing(t *testing.T, m map[string]interface{}, key string) { 160 | _, ok := m[key] 161 | if ok { 162 | t.Error("Unexpected key", key) 163 | } 164 | } 165 | 166 | func TestAnonymousStruct1(t *testing.T) { 167 | type TestStruct2 struct { 168 | TestStruct 169 | Q int 170 | } 171 | 172 | ts2 := TestStruct2{ 173 | Q: 4, 174 | } 175 | ts2.D = "ddd" 176 | 177 | ts3 := ts2 178 | 179 | marshalUnmarshalExpected(t, `{ 180 | A: 0 181 | B: 0 182 | S: "" 183 | D: ddd 184 | -: "" 185 | Q: 4 186 | }`, &ts3, &ts2, &TestStruct2{}) 187 | } 188 | 189 | func TestAnonymousStruct2(t *testing.T) { 190 | type TestStruct2 struct { 191 | TestStruct `json:"subObj,omitempty"` 192 | Q int 193 | } 194 | 195 | ts2 := TestStruct2{ 196 | Q: 4, 197 | } 198 | ts2.D = "ddd" 199 | 200 | marshalUnmarshalExpected(t, `{ 201 | subObj: { 202 | A: 0 203 | B: 0 204 | S: "" 205 | D: ddd 206 | -: "" 207 | } 208 | Q: 4 209 | }`, &ts2, &ts2, &TestStruct2{}) 210 | } 211 | 212 | func TestAnonymousStruct3(t *testing.T) { 213 | type S1 struct { 214 | someField string `json:"smFil"` 215 | Afi int 216 | Bfi int `json:"BFI"` 217 | Cfi int 218 | Dfi int `json:"Dfi"` 219 | } 220 | 221 | type S2 struct { 222 | OtherField int `json:",omitempty"` 223 | Afi int 224 | Bfi int 225 | Dfi int 226 | } 227 | 228 | type s4 struct { 229 | YesIncluded bool 230 | OmittedBool bool `json:",omitempty"` 231 | } 232 | 233 | type S3 struct { 234 | S1 235 | S2 236 | s4 237 | Cfi int `json:"-"` 238 | } 239 | 240 | ts := S3{ 241 | Cfi: 4, 242 | } 243 | ts.S1.Afi = 3 244 | ts.S2.Afi = 5 245 | ts.S1.Bfi = 7 246 | ts.S2.Bfi = 8 247 | ts.S1.Cfi = 9 248 | ts.S1.Dfi = 11 249 | ts.S2.Dfi = 22 250 | 251 | ts2 := ts 252 | ts2.S1.Afi = 0 253 | ts2.S2.Afi = 0 254 | ts2.S2.Dfi = 0 255 | ts2.Cfi = 0 256 | 257 | marshalUnmarshalExpected(t, `{ 258 | BFI: 7 259 | Cfi: 9 260 | Dfi: 11 261 | Bfi: 8 262 | YesIncluded: false 263 | }`, &ts2, &ts, &S3{}) 264 | } 265 | 266 | func TestAnonymousStruct4(t *testing.T) { 267 | type S2 struct { 268 | S2Field int 269 | } 270 | type S1 struct { 271 | S2 272 | Anon struct { 273 | S2 274 | ReallyAnonymous int 275 | } 276 | S2a S2 277 | S2b S2 278 | S2c S2 279 | } 280 | 281 | marshalUnmarshalExpected(t, `{ 282 | S2Field: 0 283 | Anon: { 284 | S2Field: 0 285 | ReallyAnonymous: 0 286 | } 287 | S2a: { 288 | S2Field: 0 289 | } 290 | S2b: { 291 | S2Field: 0 292 | } 293 | S2c: { 294 | S2Field: 0 295 | } 296 | }`, &S1{}, &S1{}, &S1{}) 297 | } 298 | 299 | func TestEmptyMapsAndSlices(t *testing.T) { 300 | type S2 struct { 301 | S2Field int 302 | } 303 | 304 | type S1 struct { 305 | MapNil map[string]interface{} 306 | MapEmpty map[string]interface{} 307 | IntSliceNil []int 308 | IntSliceEmpty []int 309 | S2Pointer *S2 310 | } 311 | ts := S1{ 312 | MapEmpty: map[string]interface{}{}, 313 | IntSliceEmpty: []int{}, 314 | } 315 | 316 | ts2 := map[string]interface{}{ 317 | "MapNil": map[string]interface{}{}, 318 | "MapEmpty": map[string]interface{}{}, 319 | "IntSliceNil": []interface{}{}, 320 | "IntSliceEmpty": []interface{}{}, 321 | "S2Pointer": nil, 322 | } 323 | 324 | ds2 := map[string]interface{}{} 325 | 326 | marshalUnmarshalExpected(t, `{ 327 | MapNil: {} 328 | MapEmpty: {} 329 | IntSliceNil: [] 330 | IntSliceEmpty: [] 331 | S2Pointer: null 332 | }`, &ts2, &ts, &ds2) 333 | 334 | ts3 := map[string]interface{}{ 335 | "MapNil": ts.MapNil, 336 | "MapEmpty": ts.MapEmpty, 337 | "IntSliceNil": ts.IntSliceNil, 338 | "IntSliceEmpty": ts.IntSliceEmpty, 339 | "S2Pointer": ts.S2Pointer, 340 | } 341 | ds3 := map[string]interface{}{} 342 | 343 | marshalUnmarshalExpected(t, `{ 344 | IntSliceEmpty: [] 345 | IntSliceNil: [] 346 | MapEmpty: {} 347 | MapNil: {} 348 | S2Pointer: null 349 | }`, &ts2, &ts3, &ds3) 350 | } 351 | 352 | func TestStructPointers(t *testing.T) { 353 | type S2 struct { 354 | S2Field int 355 | } 356 | 357 | type S1 struct { 358 | MapNil map[string]interface{} 359 | MapEmpty map[string]interface{} 360 | IntSliceNil []int 361 | IntSliceEmpty []int 362 | S2Pointer *S2 363 | } 364 | ts := S1{ 365 | MapEmpty: map[string]interface{}{}, 366 | IntSliceEmpty: []int{}, 367 | } 368 | 369 | ts2 := ts 370 | ts2.MapNil = map[string]interface{}{} 371 | ts2.IntSliceNil = []int{} 372 | 373 | marshalUnmarshalExpected(t, `{ 374 | MapNil: {} 375 | MapEmpty: {} 376 | IntSliceNil: [] 377 | IntSliceEmpty: [] 378 | S2Pointer: null 379 | }`, &ts2, &ts, &S1{}) 380 | } 381 | 382 | type TestMarshalStruct struct { 383 | TestStruct 384 | } 385 | 386 | func (s TestMarshalStruct) MarshalJSON() ([]byte, error) { 387 | return []byte(`{ 388 | "arr": [ 389 | "foo", 390 | "bar" 391 | ], 392 | "map": { 393 | "key1": 1, 394 | "key2": "B" 395 | } 396 | }`), nil 397 | } 398 | 399 | type TestMarshalAlias TestMarshalStruct 400 | 401 | func TestEncodeMarshalJSON(t *testing.T) { 402 | input := TestMarshalStruct{} 403 | expected1 := `{ 404 | arr: [ 405 | foo 406 | bar 407 | ] 408 | map: { 409 | key1: 1 410 | key2: B 411 | } 412 | }` 413 | buf, err := Marshal(input) 414 | if err != nil { 415 | t.Error(err) 416 | } 417 | if string(buf) != expected1 { 418 | t.Errorf("Expected:\n%s\nGot:\n%s\n\n", expected1, string(buf)) 419 | } 420 | 421 | buf, err = Marshal(&input) 422 | if err != nil { 423 | t.Error(err) 424 | } 425 | if string(buf) != expected1 { 426 | t.Errorf("Expected:\n%s\nGot:\n%s\n\n", expected1, string(buf)) 427 | } 428 | 429 | inputAlias := TestMarshalAlias(input) 430 | buf, err = Marshal(inputAlias) 431 | if err != nil { 432 | t.Error(err) 433 | } 434 | if string(buf) == expected1 { 435 | t.Error("Used interface on underlying type, even though the alias type doesn't implement it.") 436 | } 437 | 438 | buf, err = Marshal(&inputAlias) 439 | if err != nil { 440 | t.Error(err) 441 | } 442 | if string(buf) == expected1 { 443 | t.Error("Used interface on underlying type, even though the alias type doesn't implement it.") 444 | } 445 | 446 | myMap := map[string]interface{}{ 447 | "Zero": -0, 448 | "A": "FirstField", 449 | "B": TestMarshalStruct{}, 450 | "C": struct { 451 | D string 452 | Zero int 453 | }{ 454 | D: "struct field", 455 | Zero: -0, 456 | }, 457 | } 458 | buf, err = Marshal(&myMap) 459 | if err != nil { 460 | t.Error(err) 461 | } 462 | expected2 := `{ 463 | A: FirstField 464 | B: { 465 | arr: [ 466 | foo 467 | bar 468 | ] 469 | map: { 470 | key1: 1 471 | key2: B 472 | } 473 | } 474 | C: { 475 | D: struct field 476 | Zero: 0 477 | } 478 | Zero: 0 479 | }` 480 | if string(buf) != expected2 { 481 | t.Errorf("Expected:\n%s\nGot:\n%s\n\n", expected2, string(buf)) 482 | } 483 | } 484 | 485 | type myNet net.IP 486 | 487 | func TestEncodeMarshalText(t *testing.T) { 488 | input := net.ParseIP("127.0.0.1") 489 | buf, err := Marshal(input) 490 | if err != nil { 491 | t.Error(err) 492 | } 493 | if !reflect.DeepEqual(buf, []byte(`127.0.0.1`)) { 494 | t.Errorf("Expected '127.0.0.1', got '%s'", string(buf)) 495 | } 496 | 497 | buf, err = Marshal(&input) 498 | if err != nil { 499 | t.Error(err) 500 | } 501 | if !reflect.DeepEqual(buf, []byte(`127.0.0.1`)) { 502 | t.Errorf("Expected '127.0.0.1', got '%s'", string(buf)) 503 | } 504 | 505 | myInput := myNet(input) 506 | buf, err = Marshal(myInput) 507 | if err != nil { 508 | t.Error(err) 509 | } 510 | if reflect.DeepEqual(buf, []byte(`127.0.0.1`)) { 511 | t.Error("Used interface on underlying type, even though the alias type doesn't implement it.") 512 | } 513 | 514 | buf, err = Marshal(&myInput) 515 | if err != nil { 516 | t.Error(err) 517 | } 518 | if reflect.DeepEqual(buf, []byte(`127.0.0.1`)) { 519 | t.Error("Used interface on underlying type, even though the alias type doesn't implement it.") 520 | } 521 | } 522 | 523 | type marshallerStruct struct { 524 | A int 525 | } 526 | 527 | func (s marshallerStruct) MarshalText() ([]byte, error) { 528 | return []byte(fmt.Sprintf("key%d", s.A)), nil 529 | } 530 | 531 | func TestEncodeMarshalTextMapKey(t *testing.T) { 532 | input := map[marshallerStruct]int{ 533 | marshallerStruct{1}: 11, 534 | marshallerStruct{2}: 22, 535 | } 536 | expectedUnmarshal := map[string]interface{}{ 537 | "key1": 11.0, 538 | "key2": 22.0, 539 | } 540 | marshalUnmarshalExpected(t, `{ 541 | key1: 11 542 | key2: 22 543 | }`, &expectedUnmarshal, &input, &map[string]interface{}{}) 544 | } 545 | 546 | type TestMarshalInt int 547 | 548 | func (s TestMarshalInt) MarshalJSON() ([]byte, error) { 549 | return []byte(`"foobar"`), nil 550 | } 551 | 552 | func TestEncodeMarshalInt(t *testing.T) { 553 | var input TestMarshalInt 554 | buf, err := Marshal(input) 555 | if err != nil { 556 | t.Error(err) 557 | } 558 | if !reflect.DeepEqual(buf, []byte(`foobar`)) { 559 | t.Errorf("Expected '\"foobar\"', got '%s'", string(buf)) 560 | } 561 | buf, err = Marshal(&input) 562 | if err != nil { 563 | t.Error(err) 564 | } 565 | if !reflect.DeepEqual(buf, []byte(`foobar`)) { 566 | t.Errorf("Expected '\"foobar\"', got '%s'", string(buf)) 567 | } 568 | } 569 | 570 | func TestEncodeSliceOfPtrOfPtrOfString(t *testing.T) { 571 | s := "1" 572 | s1 := &s 573 | input := []**string{&s1} 574 | buf, err := Marshal(input) 575 | if err != nil { 576 | t.Error(err) 577 | return 578 | } 579 | if !reflect.DeepEqual(buf, []byte(`[ 580 | "1" 581 | ]`)) { 582 | t.Error("Marshaler interface error") 583 | } 584 | } 585 | 586 | func TestNoRootBraces(t *testing.T) { 587 | input := struct { 588 | Foo string 589 | }{ 590 | Foo: "Bar", 591 | } 592 | opt := DefaultOptions() 593 | opt.EmitRootBraces = false 594 | buf, err := MarshalWithOptions(input, opt) 595 | if err != nil { 596 | t.Error(err) 597 | } 598 | if !reflect.DeepEqual(buf, []byte(`Foo: Bar`)) { 599 | t.Error("Encode struct with EmitRootBraces false") 600 | } 601 | 602 | theMap := map[string]interface{}{ 603 | "Foo": "Bar", 604 | } 605 | buf, err = MarshalWithOptions(theMap, opt) 606 | if err != nil { 607 | t.Error(err) 608 | } 609 | if !reflect.DeepEqual(buf, []byte(`Foo: Bar`)) { 610 | t.Error("Encode map with EmitRootBraces false") 611 | } 612 | } 613 | 614 | func TestBaseIndentation(t *testing.T) { 615 | input := struct { 616 | Foo string 617 | }{ 618 | Foo: "Bar", 619 | } 620 | facit := []byte(` { 621 | Foo: Bar 622 | }`) 623 | opt := DefaultOptions() 624 | opt.BaseIndentation = " " 625 | buf, err := MarshalWithOptions(input, opt) 626 | if err != nil { 627 | t.Error(err) 628 | } 629 | if !reflect.DeepEqual(buf, facit) { 630 | t.Error("Encode with BaseIndentation, comparison:\n", string(buf), "\n", string(facit)) 631 | } 632 | } 633 | 634 | func TestQuoteAmbiguousStrings(t *testing.T) { 635 | theMap := map[string]interface{}{ 636 | "One": "1", 637 | "Null": "null", 638 | "False": "false", 639 | } 640 | facit := []byte(`{ 641 | False: false 642 | Null: null 643 | One: 1 644 | }`) 645 | opt := DefaultOptions() 646 | opt.QuoteAlways = false 647 | opt.QuoteAmbiguousStrings = false 648 | buf, err := MarshalWithOptions(theMap, opt) 649 | if err != nil { 650 | t.Error(err) 651 | } 652 | if !reflect.DeepEqual(buf, facit) { 653 | t.Error("Encode with QuoteAmbiguousStrings false, comparison:\n", string(buf), "\n", string(facit)) 654 | } 655 | } 656 | 657 | func marshalUnmarshal(t *testing.T, input string) { 658 | buf, err := Marshal(input) 659 | if err != nil { 660 | t.Error(err) 661 | return 662 | } 663 | var resultPlain string 664 | err = Unmarshal(buf, &resultPlain) 665 | if err != nil { 666 | t.Error(err) 667 | return 668 | } 669 | if resultPlain != input { 670 | t.Errorf("Expected: '%v' Got: '%v'\n", []byte(input), []byte(resultPlain)) 671 | } 672 | 673 | type t_obj struct { 674 | F string 675 | } 676 | obj := t_obj{ 677 | F: input, 678 | } 679 | buf, err = Marshal(obj) 680 | if err != nil { 681 | t.Error(err) 682 | return 683 | } 684 | var out map[string]interface{} 685 | err = Unmarshal(buf, &out) 686 | if err != nil { 687 | t.Error(err) 688 | return 689 | } 690 | if out["F"] != input { 691 | t.Errorf("Expected: '%v' Got: '%v'\n", []byte(input), []byte(out["F"].(string))) 692 | } 693 | } 694 | 695 | func TestMarshalUnmarshal(t *testing.T) { 696 | marshalUnmarshal(t, "0\r'") 697 | marshalUnmarshal(t, "0\r") 698 | marshalUnmarshal(t, "0\n'") 699 | marshalUnmarshal(t, "0\n") 700 | marshalUnmarshal(t, "\t0\na\tb\t") 701 | marshalUnmarshal(t, "\t0\n\tab") 702 | marshalUnmarshal(t, "0\r\n'") 703 | marshalUnmarshal(t, "0\r\n") 704 | } 705 | 706 | func TestCircularReference(t *testing.T) { 707 | timeout := time.After(3 * time.Second) 708 | done := make(chan bool) 709 | go func() { 710 | type Node struct { 711 | Self *Node 712 | } 713 | var obj Node 714 | obj.Self = &obj 715 | _, err := Marshal(obj) 716 | if err == nil { 717 | t.Error("No error returned for circular reference") 718 | } 719 | done <- true 720 | }() 721 | 722 | select { 723 | case <-timeout: 724 | t.Error("The circular reference test is taking too long, is probably stuck in an infinite loop.") 725 | case <-done: 726 | } 727 | } 728 | 729 | func TestPrivateStructFields(t *testing.T) { 730 | obj := struct { 731 | somePrivateField string 732 | }{ 733 | "TEST", 734 | } 735 | b, err := Marshal(obj) 736 | if err != nil { 737 | t.Error(err) 738 | return 739 | } 740 | if string(b) != "{}" { 741 | t.Errorf("Expected '{}', got '%s'", string(b)) 742 | } 743 | } 744 | 745 | func TestMarshalDuplicateFields(t *testing.T) { 746 | type A struct { 747 | B int `json:"rate"` 748 | C []string `json:"rate"` 749 | } 750 | 751 | a := A{ 752 | B: 3, 753 | C: []string{"D", "E"}, 754 | } 755 | 756 | buf, err := Marshal(&a) 757 | if err != nil { 758 | t.Error(err) 759 | } 760 | expected := `{}` 761 | if string(buf) != expected { 762 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", expected, string(buf)) 763 | } 764 | 765 | var a2 A 766 | err = Unmarshal([]byte("rate: 5"), &a2) 767 | if err != nil { 768 | t.Error(err) 769 | } 770 | // json.Unmarshal will not write at all to fields with duplicate names. 771 | if !reflect.DeepEqual(a2, A{}) { 772 | t.Errorf("Expected empty struct") 773 | } 774 | 775 | type B struct { 776 | B int `json:"rate"` 777 | } 778 | var b B 779 | err = Unmarshal([]byte("rate: 5"), &b) 780 | if err != nil { 781 | t.Error(err) 782 | } 783 | if b.B != 5 { 784 | t.Errorf("Expected 5, got %d\n", b.B) 785 | } 786 | } 787 | 788 | func TestMarshalMapIntKey(t *testing.T) { 789 | m := map[int]bool{ 790 | 3: true, 791 | } 792 | buf, err := Marshal(m) 793 | if err != nil { 794 | t.Error(err) 795 | } 796 | expected := `{ 797 | 3: true 798 | }` 799 | if string(buf) != expected { 800 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", expected, string(buf)) 801 | } 802 | 803 | m2 := map[int]bool{} 804 | err = Unmarshal(buf, &m2) 805 | if err != nil { 806 | t.Error(err) 807 | } 808 | if !m2[3] { 809 | t.Errorf("Failed to unmarshal into map with int key") 810 | } 811 | } 812 | 813 | func TestMarshalJsonNumber(t *testing.T) { 814 | var n json.Number 815 | buf, err := Marshal(n) 816 | if err != nil { 817 | t.Error(err) 818 | } 819 | expected := `0` 820 | if string(buf) != expected { 821 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", expected, string(buf)) 822 | } 823 | 824 | n = json.Number("3e5") 825 | buf, err = Marshal(n) 826 | if err != nil { 827 | t.Error(err) 828 | } 829 | expected = `3e5` 830 | if string(buf) != expected { 831 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", expected, string(buf)) 832 | } 833 | } 834 | 835 | func TestStructComment(t *testing.T) { 836 | type foo struct { 837 | A string `json:"x" comment:"First comment"` 838 | B int32 `comment:"Second comment\nLook ma, new lines"` 839 | C string 840 | D int32 841 | } 842 | a := foo{A: "hi!", B: 3, C: "some text", D: 5} 843 | h, err := Marshal(a) 844 | if err != nil { 845 | t.Error(err) 846 | } 847 | expected := `{ 848 | # First comment 849 | x: hi! 850 | 851 | # Second comment 852 | # Look ma, new lines 853 | B: 3 854 | 855 | C: some text 856 | D: 5 857 | }` 858 | if string(h) != expected { 859 | t.Errorf("Expected:\n%s\nGot:\n%s\n\n", expected, string(h)) 860 | } 861 | 862 | opt := DefaultOptions() 863 | opt.Comments = false 864 | h, err = MarshalWithOptions(a, opt) 865 | if err != nil { 866 | t.Error(err) 867 | } 868 | expected = `{ 869 | x: hi! 870 | B: 3 871 | C: some text 872 | D: 5 873 | }` 874 | if string(h) != expected { 875 | t.Errorf("Expected:\n%s\nGot:\n%s\n\n", expected, string(h)) 876 | } 877 | } 878 | --------------------------------------------------------------------------------