├── gradle.properties ├── src ├── test │ ├── resources │ │ └── e2e │ │ │ ├── failures │ │ │ ├── disallow-trailing-array.json5 │ │ │ ├── disallow-trailing-object.json5 │ │ │ ├── disallow-NaN.json5 │ │ │ ├── invalid-array-closing-tags.json5 │ │ │ ├── disallow-Infinity.json5 │ │ │ ├── disallow-octal-number.json5 │ │ │ ├── invalid-array-missing-comma.json5 │ │ │ ├── disallow-binary-number.json5 │ │ │ ├── disallow-hex-floating.json5 │ │ │ ├── disallow-c-digit-separator.json5 │ │ │ ├── invalid-object-missing-key-suffix.json5 │ │ │ ├── disallow-java-digit-separator.json5 │ │ │ ├── invalid-object-closing-tags.json5 │ │ │ ├── invalid-object-missing-comma.json5 │ │ │ └── duplicate-object-key.json5 │ │ │ └── roundtrips │ │ │ ├── array-minify-no-comment.writer.json5 │ │ │ ├── object-minify-no-comment.writer.json5 │ │ │ ├── array-no-comment.writer.json5 │ │ │ ├── extensions-c-style.parser.json5 │ │ │ ├── extensions-java-style.parser.json5 │ │ │ ├── extensions-c-style.writer.json5 │ │ │ ├── extensions-java-style.writer.json5 │ │ │ ├── array-minify.writer.json5 │ │ │ ├── array-minify.parser.json5 │ │ │ ├── object-no-comment.writer.json5 │ │ │ ├── object-minify.writer.json5 │ │ │ ├── object-minify.parser.json5 │ │ │ ├── array-minify-to-prettify.writer.json5 │ │ │ ├── array.writer.json5 │ │ │ ├── array.parser.json5 │ │ │ ├── object-minify-to-prettify.writer.json5 │ │ │ ├── object.writer.json5 │ │ │ └── object.parser.json5 │ └── java │ │ └── de │ │ └── marhali │ │ └── json5 │ │ ├── internal │ │ ├── RadixNumberTest.java │ │ ├── NumberLimitsTest.java │ │ ├── LazilyParsedNumberTest.java │ │ ├── NonNullElementWrapperListTest.java │ │ └── EcmaScriptIdentifierTest.java │ │ ├── fixtures │ │ ├── ToStringFixtures.java │ │ └── Json5OptionsFixtures.java │ │ ├── e2e │ │ ├── failures │ │ │ ├── DisallowNaNTest.java │ │ │ ├── DisallowInfinityTest.java │ │ │ ├── DisallowOctalNumberTest.java │ │ │ ├── DisallowBinaryNumberTest.java │ │ │ ├── DisallowTrailingArrayTest.java │ │ │ ├── DisallowTrailingObjectTest.java │ │ │ ├── DisallowHexFloatingTest.java │ │ │ ├── DisallowCDigitSeparatorTest.java │ │ │ ├── DisallowJavaDigitSeparatorTest.java │ │ │ ├── InvalidArrayParserTest.java │ │ │ ├── InvalidObjectParserTest.java │ │ │ └── DuplicateObjectKeyTest.java │ │ ├── TestResourceHelper.java │ │ └── roundtrips │ │ │ └── ParserAndWriterTest.java │ │ ├── Json5NullTest.java │ │ ├── Json5Test.java │ │ ├── Json5ElementTest.java │ │ ├── Json5ObjectTest.java │ │ └── Json5ArrayTest.java └── main │ └── java │ └── de │ └── marhali │ └── json5 │ ├── config │ ├── DigitSeparatorStrategy.java │ └── DuplicateKeyStrategy.java │ ├── Json5Null.java │ ├── internal │ ├── NumberLimits.java │ ├── RadixNumber.java │ ├── LazilyParsedNumber.java │ ├── NonNullElementWrapperList.java │ └── EcmaScriptIdentifier.java │ ├── exception │ └── Json5Exception.java │ ├── Json5.java │ ├── stream │ └── Json5Parser.java │ └── Json5Object.java ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── settings.gradle.kts ├── .gitignore ├── qodana.yml ├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ └── build.yml ├── .editorconfig ├── .run ├── Run build.run.xml └── Run tests.run.xml ├── CHANGELOG.md ├── gradlew.bat ├── README.md ├── gradlew └── LICENSE /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.configuration-cache=true 2 | -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/disallow-trailing-array.json5: -------------------------------------------------------------------------------- 1 | []abc 2 | -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/disallow-trailing-object.json5: -------------------------------------------------------------------------------- 1 | {}abc 2 | -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/disallow-NaN.json5: -------------------------------------------------------------------------------- 1 | { 2 | "disallowed": NaN 3 | } 4 | -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/invalid-array-closing-tags.json5: -------------------------------------------------------------------------------- 1 | [ 2 | "some value", 3 | -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/disallow-Infinity.json5: -------------------------------------------------------------------------------- 1 | { 2 | "disallowed": Infinity 3 | } 4 | -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/disallow-octal-number.json5: -------------------------------------------------------------------------------- 1 | { 2 | "disallowed": 0o167 3 | } 4 | -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/invalid-array-missing-comma.json5: -------------------------------------------------------------------------------- 1 | [ 2 | [] 3 | [] 4 | ] 5 | -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/disallow-binary-number.json5: -------------------------------------------------------------------------------- 1 | { 2 | "disallowed": 0b1010 3 | } 4 | -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/disallow-hex-floating.json5: -------------------------------------------------------------------------------- 1 | { 2 | "disallowed": 0x1.8p-12 3 | } 4 | -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/disallow-c-digit-separator.json5: -------------------------------------------------------------------------------- 1 | { 2 | "disallowed": 1'123'456 3 | } 4 | -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/invalid-object-missing-key-suffix.json5: -------------------------------------------------------------------------------- 1 | { 2 | "myKey" false, 3 | } 4 | -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/disallow-java-digit-separator.json5: -------------------------------------------------------------------------------- 1 | { 2 | "disallowed": 1_123_456 3 | } 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marhali/json5-java/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/invalid-object-closing-tags.json5: -------------------------------------------------------------------------------- 1 | { 2 | alpha: "firstAlphaValue", 3 | array: [], 4 | -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/invalid-object-missing-comma.json5: -------------------------------------------------------------------------------- 1 | { 2 | firstObject: {} 3 | secondObject: {} 4 | } 5 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "json5-java" 2 | 3 | plugins { 4 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | build 3 | .gradle 4 | 5 | # Eclipse m2e generated files 6 | # Eclipse Core 7 | .project 8 | # JDT-specific (Eclipse Java Development Tools) 9 | .classpath 10 | 11 | # IntelliJ IDE 12 | .idea 13 | -------------------------------------------------------------------------------- /src/test/resources/e2e/failures/duplicate-object-key.json5: -------------------------------------------------------------------------------- 1 | { 2 | alpha: "firstAlphaValue", 3 | alpha: "secondAlphaValue", 4 | bravo: "bravoValue", 5 | charlie: "firstCharlieValue", 6 | charlie: "secondCharlieValue" 7 | } 8 | -------------------------------------------------------------------------------- /qodana.yml: -------------------------------------------------------------------------------- 1 | # Qodana configuration: 2 | # https://www.jetbrains.com/help/qodana/qodana-yaml.html 3 | 4 | version: "1.0" 5 | linter: jetbrains/qodana-jvm-community:2025.2 6 | profile: 7 | name: qodana.recommended 8 | include: 9 | - name: All 10 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | junit = "5.13.4" 3 | 4 | [libraries] 5 | junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } 6 | junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } 7 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/array-minify-no-comment.writer.json5: -------------------------------------------------------------------------------- 1 | [true,false,"Test \" 123","Test ' 123","Test ' 123","\\n\\r\\f\\b\\t\\u000B\\0\\u12Fa\\u007F",true,0x303030,0x303030,-0x303030,NaN,NaN,NaN,Infinity,Infinity,-Infinity,123,123,-123,{key:"value",array:[true,false]}] 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: github-actions 5 | directory: / 6 | schedule: 7 | interval: monthly 8 | 9 | # Maintain dependencies for Gradle 10 | - package-ecosystem: gradle 11 | directory: / 12 | schedule: 13 | interval: monthly 14 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/object-minify-no-comment.writer.json5: -------------------------------------------------------------------------------- 1 | {quotes:{doubleQuoted:"Test \" 123",singleQuoted:"Test ' 123",testMixedQuoted:"Test ' 123"},falsy:false,truly:true,escapes:"\\n\\r\\f\\b\\t\\u000B\\0\\u12Fa\\u007F",memberNames:{$LoremA_Ipsum123指事字:0},multiLineComment:false,hexNumbers:[0x303030,0x303030,-0x303030],specialNumbers:[NaN,NaN,NaN,Infinity,Infinity,-Infinity],literals:[123,123,-123]} 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{json,json5}] 12 | indent_size = 2 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | 20 | [*.{xml,config}] 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/array-no-comment.writer.json5: -------------------------------------------------------------------------------- 1 | [ 2 | true, 3 | false, 4 | "Test \" 123", 5 | "Test ' 123", 6 | "Test ' 123", 7 | "\\n\\r\\f\\b\\t\\u000B\\0\\u12Fa\\u007F", 8 | true, 9 | 0x303030, 10 | 0x303030, 11 | -0x303030, 12 | NaN, 13 | NaN, 14 | NaN, 15 | Infinity, 16 | Infinity, 17 | -Infinity, 18 | 123, 19 | 123, 20 | -123, 21 | { 22 | key: "value", 23 | array: [ 24 | true, 25 | false, 26 | ], 27 | }, 28 | ] 29 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/extensions-c-style.parser.json5: -------------------------------------------------------------------------------- 1 | // Number extensions with digit separator C-style 2 | { 3 | decimalNumber: 1234567, 4 | decimalNumberNegative: -11234567, 5 | decimalNumberPositive: +111234567, 6 | binaryNumber: 0b10101100, 7 | binaryNumberNegative: -0b101100, 8 | binaryNumberPositive: +0b1101100, 9 | octalNumber: 0o1234567, 10 | octalNumberNegative: -0o12345671, 11 | octalNumberPositive: +0o123456712, 12 | hexNumber: 0xDEADBEEF, 13 | hexNumberNegative: -0xADBEEFA, 14 | hexNumberPositive: +0xEADBEEFB, 15 | hexFloatingPoint: 0x1.8p-12 16 | } 17 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/extensions-java-style.parser.json5: -------------------------------------------------------------------------------- 1 | // Number extensions with digit separator Java-style 2 | { 3 | decimalNumber: 1234567, 4 | decimalNumberNegative: -11234567, 5 | decimalNumberPositive: +111234567, 6 | binaryNumber: 0b10101100, 7 | binaryNumberNegative: -0b101100, 8 | binaryNumberPositive: +0b1101100, 9 | octalNumber: 0o1234567, 10 | octalNumberNegative: -0o12345671, 11 | octalNumberPositive: +0o123456712, 12 | hexNumber: 0xDEADBEEF, 13 | hexNumberNegative: -0xADBEEFA, 14 | hexNumberPositive: +0xEADBEEFB, 15 | hexFloatingPoint: 0x1800, 16 | } 17 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/extensions-c-style.writer.json5: -------------------------------------------------------------------------------- 1 | // Number extensions with digit separator C-style 2 | { 3 | decimalNumber: 1'234'567, 4 | decimalNumberNegative: -11'234'567, 5 | decimalNumberPositive: 111'234'567, 6 | binaryNumber: 0b1010'1100, 7 | binaryNumberNegative: -0b10'1100, 8 | binaryNumberPositive: 0b110'1100, 9 | octalNumber: 0o1'234'567, 10 | octalNumberNegative: -0o12'345'671, 11 | octalNumberPositive: 0o123'456'712, 12 | hexNumber: 0xde'ad'be'ef, 13 | hexNumberNegative: -0xa'db'ee'fa, 14 | hexNumberPositive: 0xea'db'ee'fb, 15 | hexFloatingPoint: 0x18'00, 16 | } 17 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/extensions-java-style.writer.json5: -------------------------------------------------------------------------------- 1 | // Number extensions with digit separator Java-style 2 | { 3 | decimalNumber: 1_234_567, 4 | decimalNumberNegative: -11_234_567, 5 | decimalNumberPositive: 111_234_567, 6 | binaryNumber: 0b1010_1100, 7 | binaryNumberNegative: -0b10_1100, 8 | binaryNumberPositive: 0b110_1100, 9 | octalNumber: 0o1_234_567, 10 | octalNumberNegative: -0o12_345_671, 11 | octalNumberPositive: 0o123_456_712, 12 | hexNumber: 0xde_ad_be_ef, 13 | hexNumberNegative: -0xa_db_ee_fa, 14 | hexNumberPositive: 0xea_db_ee_fb, 15 | hexFloatingPoint: 0x18_00, 16 | } 17 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/array-minify.writer.json5: -------------------------------------------------------------------------------- 1 | /*Multi-line comment on root array element*/[/*Comment on truly primitive boolean*/true,/*Comment on falsy primitive boolean*/false,/*Double quoted string with escaped quote*/"Test \" 123",/*Single quoted string with escaped quote*/"Test ' 123",/*String with mixed quotes*/"Test ' 123",/*Escapes*/"\\n\\r\\f\\b\\t\\u000B\\0\\u12Fa\\u007F",/*Multi-line comment on primitive value*/true,/*Neutral hex number*/0x303030,/*Positive hex number*/0x303030,/*Negative hex number*/-0x303030,NaN,NaN,NaN,Infinity,Infinity,-Infinity,123,123,-123,/*Object within array*/{/*Sample member*/key:"value",/*Nested array*/array:[/*any true*/true,/*any false*/false]}] 2 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/array-minify.parser.json5: -------------------------------------------------------------------------------- 1 | /*Multi-line comment on root array element*/[/*Comment on truly primitive boolean*/true,/*Comment on falsy primitive boolean*/false,/*Double quoted string with escaped quote*/"Test \" 123",/*Single quoted string with escaped quote*/'Test \' 123',/*String with mixed quotes*/"Test ' 123",/*Escapes*/"\\n\\r\\f\\b\\t\\u000B\\0\\u12Fa\\u007F",/*Multi-line comment on primitive value*/true,/*Neutral hex number*/0x303030,/*Positive hex number*/0x303030,/*Negative hex number*/-0x303030,NaN,+NaN,-NaN,Infinity,+Infinity,-Infinity,123,+123,-123,/*Object within array*/{/*Sample member*/key:"value",/*Nested array*/array:[/*any true*/true,/*any false*/false]}] 2 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/object-no-comment.writer.json5: -------------------------------------------------------------------------------- 1 | { 2 | quotes: { 3 | doubleQuoted: "Test \" 123", 4 | singleQuoted: "Test ' 123", 5 | testMixedQuoted: "Test ' 123", 6 | }, 7 | falsy: false, 8 | truly: true, 9 | escapes: "\\n\\r\\f\\b\\t\\u000B\\0\\u12Fa\\u007F", 10 | memberNames: { 11 | $LoremA_Ipsum123指事字: 0, 12 | }, 13 | multiLineComment: false, 14 | hexNumbers: [ 15 | 0x303030, 16 | 0x303030, 17 | -0x303030, 18 | ], 19 | specialNumbers: [ 20 | NaN, 21 | NaN, 22 | NaN, 23 | Infinity, 24 | Infinity, 25 | -Infinity, 26 | ], 27 | literals: [ 28 | 123, 29 | 123, 30 | -123, 31 | ], 32 | } 33 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/object-minify.writer.json5: -------------------------------------------------------------------------------- 1 | /*Object comment*/{/*Quotes tests*/quotes:{/*Test double-quoted member name and string value*/doubleQuoted:"Test \" 123",/*Test single-quoted member name and string value*/singleQuoted:"Test ' 123",/*Test mixed quotes with double- and single-quotes*/testMixedQuoted:"Test ' 123"},/*Falsy boolean,*/falsy:false,/*Truly boolean*/truly:true,/*Test escape characters*/escapes:"\\n\\r\\f\\b\\t\\u000B\\0\\u12Fa\\u007F",/*Test member names*/memberNames:{/*Test member names*/$LoremA_Ipsum123指事字:0},/*Some multi-line comment*/multiLineComment:false,/*Hex numbers*/hexNumbers:[/*Neutral hex number*/0x303030,/*Positive hex number*/0x303030,/*Negative hex number*/-0x303030],/*Special numbers*/specialNumbers:[NaN,NaN,NaN,Infinity,Infinity,-Infinity],/*Literals*/literals:[123,123,-123]} 2 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/object-minify.parser.json5: -------------------------------------------------------------------------------- 1 | /*Object comment*/{/*Quotes tests*/quotes:{/*Test double-quoted member name and string value*/"doubleQuoted":"Test \" 123",/*Test single-quoted member name and string value*/'singleQuoted':'Test \' 123',/*Test mixed quotes with double- and single-quotes*/testMixedQuoted:"Test ' 123"},/*Falsy boolean,*/falsy:false,/*Truly boolean*/truly:true,/*Test escape characters*/escapes:"\\n\\r\\f\\b\\t\\u000B\\0\\u12Fa\\u007F",/*Test member names*/memberNames:{/*Test member names*/"$Lorem\u0041_Ipsum123指事字":0},/*Some multi-line comment*/multiLineComment:false,/*Hex numbers*/hexNumbers:[/*Neutral hex number*/0x303030,/*Positive hex number*/+0x303030,/*Negative hex number*/-0x303030],/*Special numbers*/specialNumbers:[NaN,+NaN,-NaN,Infinity,+Infinity,-Infinity],/*Literals*/literals:[123,+123,-123]} 2 | -------------------------------------------------------------------------------- /.run/Run build.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | true 20 | false 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /.run/Run tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | true 20 | false 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/array-minify-to-prettify.writer.json5: -------------------------------------------------------------------------------- 1 | // Multi-line comment on root array element 2 | [ 3 | // Comment on truly primitive boolean 4 | true, 5 | // Comment on falsy primitive boolean 6 | false, 7 | // Double quoted string with escaped quote 8 | "Test \" 123", 9 | // Single quoted string with escaped quote 10 | "Test ' 123", 11 | // String with mixed quotes 12 | "Test ' 123", 13 | // Escapes 14 | "\\n\\r\\f\\b\\t\\u000B\\0\\u12Fa\\u007F", 15 | // Multi-line comment on primitive value 16 | true, 17 | // Neutral hex number 18 | 0x303030, 19 | // Positive hex number 20 | 0x303030, 21 | // Negative hex number 22 | -0x303030, 23 | NaN, 24 | NaN, 25 | NaN, 26 | Infinity, 27 | Infinity, 28 | -Infinity, 29 | 123, 30 | 123, 31 | -123, 32 | // Object within array 33 | { 34 | // Sample member 35 | key: "value", 36 | // Nested array 37 | array: [ 38 | // any true 39 | true, 40 | // any false 41 | false, 42 | ], 43 | }, 44 | ] 45 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/array.writer.json5: -------------------------------------------------------------------------------- 1 | /* 2 | * Multi-line comment 3 | * on root array element 4 | */ 5 | [ 6 | // Comment on truly primitive boolean 7 | true, 8 | // Comment on falsy primitive boolean 9 | false, 10 | // Double quoted string with escaped quote 11 | "Test \" 123", 12 | // Single quoted string with escaped quote 13 | "Test ' 123", 14 | // String with mixed quotes 15 | "Test ' 123", 16 | // Escapes 17 | "\\n\\r\\f\\b\\t\\u000B\\0\\u12Fa\\u007F", 18 | /* 19 | * Multi-line comment 20 | * on primitive value 21 | */ 22 | true, 23 | // Neutral hex number 24 | 0x303030, 25 | // Positive hex number 26 | 0x303030, 27 | // Negative hex number 28 | -0x303030, 29 | NaN, 30 | NaN, 31 | NaN, 32 | Infinity, 33 | Infinity, 34 | -Infinity, 35 | 123, 36 | 123, 37 | -123, 38 | // Object within array 39 | { 40 | // Sample member 41 | key: "value", 42 | // Nested array 43 | array: [ 44 | // any true 45 | true, 46 | // any false 47 | false, 48 | ], 49 | }, 50 | ] 51 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/array.parser.json5: -------------------------------------------------------------------------------- 1 | /* 2 | * Multi-line comment 3 | * on root array element 4 | */ 5 | [ 6 | // Comment on truly primitive boolean 7 | true, 8 | // Comment on falsy primitive boolean 9 | false, 10 | // Double quoted string with escaped quote 11 | "Test \" 123", 12 | // Single quoted string with escaped quote 13 | 'Test \' 123', 14 | // String with mixed quotes 15 | "Test ' 123", 16 | // Escapes 17 | "\\n\\r\\f\\b\\t\\u000B\\0\\u12Fa\\u007F", 18 | /* 19 | * Multi-line comment 20 | * on primitive value 21 | */ 22 | true, 23 | // Neutral hex number 24 | 0x303030, 25 | // Positive hex number 26 | +0x303030, 27 | // Negative hex number 28 | -0x303030, 29 | NaN, 30 | +NaN, 31 | -NaN, 32 | Infinity, 33 | +Infinity, 34 | -Infinity, 35 | 123, 36 | +123, 37 | -123, 38 | // Object within array 39 | { 40 | // Sample member 41 | key: "value", 42 | // Nested array 43 | array: [ 44 | // any true 45 | true, 46 | // any false 47 | false 48 | ] 49 | } 50 | ] 51 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/object-minify-to-prettify.writer.json5: -------------------------------------------------------------------------------- 1 | // Object comment 2 | { 3 | // Quotes tests 4 | quotes: { 5 | // Test double-quoted member name and string value 6 | doubleQuoted: "Test \" 123", 7 | // Test single-quoted member name and string value 8 | singleQuoted: "Test ' 123", 9 | // Test mixed quotes with double- and single-quotes 10 | testMixedQuoted: "Test ' 123", 11 | }, 12 | // Falsy boolean, 13 | falsy: false, 14 | // Truly boolean 15 | truly: true, 16 | // Test escape characters 17 | escapes: "\\n\\r\\f\\b\\t\\u000B\\0\\u12Fa\\u007F", 18 | // Test member names 19 | memberNames: { 20 | // Test member names 21 | $LoremA_Ipsum123指事字: 0, 22 | }, 23 | // Some multi-line comment 24 | multiLineComment: false, 25 | // Hex numbers 26 | hexNumbers: [ 27 | // Neutral hex number 28 | 0x303030, 29 | // Positive hex number 30 | 0x303030, 31 | // Negative hex number 32 | -0x303030, 33 | ], 34 | // Special numbers 35 | specialNumbers: [ 36 | NaN, 37 | NaN, 38 | NaN, 39 | Infinity, 40 | Infinity, 41 | -Infinity, 42 | ], 43 | // Literals 44 | literals: [ 45 | 123, 46 | 123, 47 | -123, 48 | ], 49 | } 50 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/object.writer.json5: -------------------------------------------------------------------------------- 1 | // Object comment 2 | { 3 | // Quotes tests 4 | quotes: { 5 | // Test double-quoted member name and string value 6 | doubleQuoted: "Test \" 123", 7 | // Test single-quoted member name and string value 8 | singleQuoted: "Test ' 123", 9 | // Test mixed quotes with double- and single-quotes 10 | testMixedQuoted: "Test ' 123", 11 | }, 12 | // Falsy boolean, 13 | falsy: false, 14 | // Truly boolean 15 | truly: true, 16 | // Test escape characters 17 | escapes: "\\n\\r\\f\\b\\t\\u000B\\0\\u12Fa\\u007F", 18 | // Test member names 19 | memberNames: { 20 | // Test member names 21 | $LoremA_Ipsum123指事字: 0, 22 | }, 23 | /* 24 | * Some multi-line 25 | * comment 26 | */ 27 | multiLineComment: false, 28 | // Hex numbers 29 | hexNumbers: [ 30 | // Neutral hex number 31 | 0x303030, 32 | // Positive hex number 33 | 0x303030, 34 | // Negative hex number 35 | -0x303030, 36 | ], 37 | // Special numbers 38 | specialNumbers: [ 39 | NaN, 40 | NaN, 41 | NaN, 42 | Infinity, 43 | Infinity, 44 | -Infinity, 45 | ], 46 | // Literals 47 | literals: [ 48 | 123, 49 | 123, 50 | -123, 51 | ], 52 | } 53 | -------------------------------------------------------------------------------- /src/test/resources/e2e/roundtrips/object.parser.json5: -------------------------------------------------------------------------------- 1 | // Object comment 2 | { 3 | // Quotes tests 4 | quotes: { 5 | // Test double-quoted member name and string value 6 | "doubleQuoted": "Test \" 123", 7 | // Test single-quoted member name and string value 8 | 'singleQuoted': 'Test \' 123', 9 | // Test mixed quotes with double- and single-quotes 10 | testMixedQuoted: "Test ' 123" 11 | }, 12 | // Falsy boolean, 13 | falsy: false, 14 | // Truly boolean 15 | truly: true, 16 | // Test escape characters 17 | "escapes": "\\n\\r\\f\\b\\t\\u000B\\0\\u12Fa\\u007F", 18 | // Test member names 19 | memberNames: { 20 | // Test member names 21 | "$Lorem\u0041_Ipsum123指事字": 0 22 | }, 23 | /* 24 | * Some multi-line 25 | * comment 26 | */ 27 | multiLineComment: false, 28 | // Hex numbers 29 | hexNumbers: [ 30 | // Neutral hex number 31 | 0x303030, 32 | // Positive hex number 33 | +0x303030, 34 | // Negative hex number 35 | -0x303030 36 | ], 37 | // Special numbers 38 | specialNumbers: [ 39 | NaN, 40 | +NaN, 41 | -NaN, 42 | Infinity, 43 | +Infinity, 44 | -Infinity 45 | ], 46 | // Literals 47 | literals: [ 48 | 123, 49 | +123, 50 | -123 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/de/marhali/json5/config/DigitSeparatorStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 - 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5.config; 18 | 19 | /** 20 | * An enum containing all supported behaviors for handling digit separators. 21 | * 22 | * @author Marcel Haßlinger 23 | */ 24 | public enum DigitSeparatorStrategy { 25 | 26 | /** 27 | * Expect no digit separators 28 | */ 29 | NONE, 30 | 31 | /** 32 | * Uses Java-style digit separators (e.g. {@code 123_456}). 33 | */ 34 | JAVA_STYLE, 35 | 36 | /** 37 | * Uses C-style digit separators (e.g. {@code 123'456}). 38 | */ 39 | C_STYLE, 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/de/marhali/json5/Json5Null.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 Google Inc. 3 | * Copyright (C) 2025 Marcel Haßlinger 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package de.marhali.json5; 19 | 20 | /** 21 | * A class representing a Json {@code null} literal value. 22 | * 23 | * @author Inderjeet Singh 24 | * @author Joel Leitch 25 | * @author Marcel Haßlinger 26 | */ 27 | public final class Json5Null extends Json5Element { 28 | public Json5Null() { 29 | } 30 | 31 | @Override 32 | public Json5Element deepCopy() { 33 | Json5Null copy = new Json5Null(); 34 | copy.setComment(comment); 35 | return copy; 36 | } 37 | 38 | @Override 39 | public String getAsString() { 40 | return "null"; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/de/marhali/json5/internal/RadixNumberTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5.internal; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | import static org.junit.jupiter.api.Assertions.*; 22 | 23 | /** 24 | * @author Marcel Haßlinger 25 | */ 26 | public class RadixNumberTest { 27 | 28 | @Test 29 | void test_getNumber() { 30 | assertEquals(187, new RadixNumber(187, 10).getNumber()); 31 | } 32 | 33 | @Test 34 | void test_getRadix() { 35 | assertEquals(10, new RadixNumber(187, 10).getRadix()); 36 | } 37 | 38 | @Test 39 | void test_equals() { 40 | assertEquals(new RadixNumber(187, 10), new RadixNumber(187, 10)); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/de/marhali/json5/internal/NumberLimits.java: -------------------------------------------------------------------------------- 1 | package de.marhali.json5.internal; 2 | 3 | import java.math.BigDecimal; 4 | import java.math.BigInteger; 5 | 6 | /** 7 | * This class enforces limits on numbers parsed from Json5 to avoid potential performance problems 8 | * when extremely large numbers are used. 9 | */ 10 | public class NumberLimits { 11 | private NumberLimits() { 12 | } 13 | 14 | private static final int MAX_NUMBER_STRING_LENGTH = 10_000; 15 | 16 | private static void checkNumberStringLength(String s) { 17 | if (s.length() > MAX_NUMBER_STRING_LENGTH) { 18 | throw new NumberFormatException("Number string too large: " + s.substring(0, 30) + "..."); 19 | } 20 | } 21 | 22 | public static BigDecimal parseBigDecimal(String s) throws NumberFormatException { 23 | checkNumberStringLength(s); 24 | BigDecimal decimal = new BigDecimal(s); 25 | 26 | // Cast to long to avoid issues with abs when value is Integer.MIN_VALUE 27 | if (Math.abs((long) decimal.scale()) >= 10_000) { 28 | throw new NumberFormatException("Number has unsupported scale: " + s); 29 | } 30 | return decimal; 31 | } 32 | 33 | public static BigInteger parseBigInteger(String s) throws NumberFormatException { 34 | checkNumberStringLength(s); 35 | return new BigInteger(s); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/de/marhali/json5/fixtures/ToStringFixtures.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5.fixtures; 18 | 19 | import de.marhali.json5.config.DigitSeparatorStrategy; 20 | import de.marhali.json5.config.DuplicateKeyStrategy; 21 | import de.marhali.json5.config.Json5Options; 22 | 23 | /** 24 | * @author Marcel Haßlinger 25 | */ 26 | public class ToStringFixtures { 27 | /** 28 | * Options to use for testing {@link de.marhali.json5.Json5Element#toString(Json5Options)} methods. 29 | */ 30 | public static Json5Options OPTIONS = Json5Options.builder() 31 | .allowNaN() 32 | .allowInfinity() 33 | .allowInvalidSurrogates() 34 | .parseComments() 35 | .writeComments() 36 | .trailingComma() 37 | .quoteSingle() 38 | .digitSeparatorStrategy(DigitSeparatorStrategy.NONE) 39 | .duplicateKeyStrategy(DuplicateKeyStrategy.UNIQUE) 40 | .prettyPrinting() 41 | .build(); 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/de/marhali/json5/e2e/failures/DisallowNaNTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5.e2e.failures; 18 | 19 | import de.marhali.json5.Json5; 20 | import de.marhali.json5.config.Json5Options; 21 | import de.marhali.json5.e2e.TestResourceHelper; 22 | import de.marhali.json5.exception.Json5Exception; 23 | import org.junit.jupiter.api.DisplayName; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import static org.junit.jupiter.api.Assertions.*; 27 | 28 | /** 29 | * @author Marcel Haßlinger 30 | */ 31 | public class DisallowNaNTest { 32 | @Test 33 | @DisplayName("Parse: disallowed NaN throws exception") 34 | void disallowNaN() { 35 | var json5 = Json5.builder(Json5Options.Builder::build); 36 | 37 | var ex = assertThrows(Json5Exception.class, () -> json5.parse(TestResourceHelper.getTestResourceContent("e2e/failures/disallow-NaN.json5"))); 38 | 39 | assertEquals("NaN is not allowed at index 22 [character 1 in line 3]", ex.getMessage()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/de/marhali/json5/e2e/failures/DisallowInfinityTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5.e2e.failures; 18 | 19 | import de.marhali.json5.Json5; 20 | import de.marhali.json5.config.Json5Options; 21 | import de.marhali.json5.e2e.TestResourceHelper; 22 | import de.marhali.json5.exception.Json5Exception; 23 | import org.junit.jupiter.api.DisplayName; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertEquals; 27 | import static org.junit.jupiter.api.Assertions.assertThrows; 28 | 29 | /** 30 | * @author Marcel Haßlinger 31 | */ 32 | public class DisallowInfinityTest { 33 | @Test 34 | @DisplayName("Parse: disallowed Infinity throws exception") 35 | void disallowInfinity() { 36 | var json5 = Json5.builder(Json5Options.Builder::build); 37 | 38 | var ex = assertThrows(Json5Exception.class, () -> json5.parse(TestResourceHelper.getTestResourceContent("e2e/failures/disallow-Infinity.json5"))); 39 | 40 | assertEquals("Infinity is not allowed at index 27 [character 1 in line 3]", ex.getMessage()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/de/marhali/json5/e2e/failures/DisallowOctalNumberTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5.e2e.failures; 18 | 19 | import de.marhali.json5.Json5; 20 | import de.marhali.json5.config.Json5Options; 21 | import de.marhali.json5.e2e.TestResourceHelper; 22 | import de.marhali.json5.exception.Json5Exception; 23 | import org.junit.jupiter.api.DisplayName; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertEquals; 27 | import static org.junit.jupiter.api.Assertions.assertThrows; 28 | 29 | /** 30 | * @author Marcel Haßlinger 31 | */ 32 | public class DisallowOctalNumberTest { 33 | @Test 34 | @DisplayName("Parse: disallowed octal number throws exception") 35 | void disallowOctalNumber() { 36 | var json5 = Json5.builder(Json5Options.Builder::build); 37 | 38 | var ex = assertThrows(Json5Exception.class, () -> json5.parse(TestResourceHelper.getTestResourceContent("e2e/failures/disallow-octal-number.json5"))); 39 | 40 | assertEquals("Octal literals are not allowed at index 24 [character 1 in line 3]", ex.getMessage()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/de/marhali/json5/e2e/failures/DisallowBinaryNumberTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5.e2e.failures; 18 | 19 | import de.marhali.json5.Json5; 20 | import de.marhali.json5.config.Json5Options; 21 | import de.marhali.json5.e2e.TestResourceHelper; 22 | import de.marhali.json5.exception.Json5Exception; 23 | import org.junit.jupiter.api.DisplayName; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertEquals; 27 | import static org.junit.jupiter.api.Assertions.assertThrows; 28 | 29 | /** 30 | * @author Marcel Haßlinger 31 | */ 32 | public class DisallowBinaryNumberTest { 33 | @Test 34 | @DisplayName("Parse: disallowed binary number throws exception") 35 | void disallowBinaryNumber() { 36 | var json5 = Json5.builder(Json5Options.Builder::build); 37 | 38 | var ex = assertThrows(Json5Exception.class, () -> json5.parse(TestResourceHelper.getTestResourceContent("e2e/failures/disallow-binary-number.json5"))); 39 | 40 | assertEquals("Binary literals are not allowed at index 25 [character 1 in line 3]", ex.getMessage()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/de/marhali/json5/e2e/failures/DisallowTrailingArrayTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5.e2e.failures; 18 | 19 | import de.marhali.json5.Json5; 20 | import de.marhali.json5.config.Json5Options; 21 | import de.marhali.json5.e2e.TestResourceHelper; 22 | import de.marhali.json5.exception.Json5Exception; 23 | import org.junit.jupiter.api.DisplayName; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertEquals; 27 | import static org.junit.jupiter.api.Assertions.assertThrows; 28 | 29 | /** 30 | * @author Marcel Haßlinger 31 | */ 32 | public class DisallowTrailingArrayTest { 33 | @Test 34 | @DisplayName("Parse: disallowed trailing data on array throws exception") 35 | void disallowTrailingArray() { 36 | var json5 = Json5.builder(Json5Options.Builder::build); 37 | 38 | var ex = assertThrows(Json5Exception.class, () -> json5.parse(TestResourceHelper.getTestResourceContent("e2e/failures/disallow-trailing-array.json5"))); 39 | 40 | assertEquals("Trailing data after Json5Array at index 2 [character 3 in line 1]", ex.getMessage()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/de/marhali/json5/e2e/failures/DisallowTrailingObjectTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5.e2e.failures; 18 | 19 | import de.marhali.json5.Json5; 20 | import de.marhali.json5.config.Json5Options; 21 | import de.marhali.json5.e2e.TestResourceHelper; 22 | import de.marhali.json5.exception.Json5Exception; 23 | import org.junit.jupiter.api.DisplayName; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertEquals; 27 | import static org.junit.jupiter.api.Assertions.assertThrows; 28 | 29 | /** 30 | * @author Marcel Haßlinger 31 | */ 32 | public class DisallowTrailingObjectTest { 33 | @Test 34 | @DisplayName("Parse: disallowed trailing data on object throws exception") 35 | void disallowTrailingObject() { 36 | var json5 = Json5.builder(Json5Options.Builder::build); 37 | 38 | var ex = assertThrows(Json5Exception.class, () -> json5.parse(TestResourceHelper.getTestResourceContent("e2e/failures/disallow-trailing-object.json5"))); 39 | 40 | assertEquals("Trailing data after Json5Object at index 2 [character 3 in line 1]", ex.getMessage()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/de/marhali/json5/e2e/failures/DisallowHexFloatingTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5.e2e.failures; 18 | 19 | import de.marhali.json5.Json5; 20 | import de.marhali.json5.config.Json5Options; 21 | import de.marhali.json5.e2e.TestResourceHelper; 22 | import de.marhali.json5.exception.Json5Exception; 23 | import org.junit.jupiter.api.DisplayName; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertEquals; 27 | import static org.junit.jupiter.api.Assertions.assertThrows; 28 | 29 | /** 30 | * @author Marcel Haßlinger 31 | */ 32 | public class DisallowHexFloatingTest { 33 | @Test 34 | @DisplayName("Parse: disallowed hex floating-point throws exception") 35 | void disallowHexFloating() { 36 | var json5 = Json5.builder(Json5Options.Builder::build); 37 | 38 | var ex = assertThrows(Json5Exception.class, () -> json5.parse(TestResourceHelper.getTestResourceContent("e2e/failures/disallow-hex-floating.json5"))); 39 | 40 | assertEquals("Hexadecimal floating-point literals are not allowed at index 28 [character 1 in line 3]", ex.getMessage()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/de/marhali/json5/e2e/failures/DisallowCDigitSeparatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5.e2e.failures; 18 | 19 | import de.marhali.json5.Json5; 20 | import de.marhali.json5.config.Json5Options; 21 | import de.marhali.json5.e2e.TestResourceHelper; 22 | import de.marhali.json5.exception.Json5Exception; 23 | import org.junit.jupiter.api.DisplayName; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertEquals; 27 | import static org.junit.jupiter.api.Assertions.assertThrows; 28 | 29 | /** 30 | * @author Marcel Haßlinger 31 | */ 32 | public class DisallowCDigitSeparatorTest { 33 | @Test 34 | @DisplayName("Parse: disallowed C-style digit separators throws exception") 35 | void disallowCCDigitSeparator() { 36 | var json5 = Json5.builder(Json5Options.Builder::build); 37 | 38 | var ex = assertThrows(Json5Exception.class, () -> json5.parse(TestResourceHelper.getTestResourceContent("e2e/failures/disallow-c-digit-separator.json5"))); 39 | 40 | assertEquals("C-style digit separators are not allowed at index 28 [character 1 in line 3]", ex.getMessage()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/de/marhali/json5/e2e/failures/DisallowJavaDigitSeparatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5.e2e.failures; 18 | 19 | import de.marhali.json5.Json5; 20 | import de.marhali.json5.config.Json5Options; 21 | import de.marhali.json5.e2e.TestResourceHelper; 22 | import de.marhali.json5.exception.Json5Exception; 23 | import org.junit.jupiter.api.DisplayName; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import static org.junit.jupiter.api.Assertions.assertEquals; 27 | import static org.junit.jupiter.api.Assertions.assertThrows; 28 | 29 | /** 30 | * @author Marcel Haßlinger 31 | */ 32 | public class DisallowJavaDigitSeparatorTest { 33 | @Test 34 | @DisplayName("Parse: disallowed Java-style digit separators throws exception") 35 | void disallowJavaDigitSeparator() { 36 | var json5 = Json5.builder(Json5Options.Builder::build); 37 | 38 | var ex = assertThrows(Json5Exception.class, () -> json5.parse(TestResourceHelper.getTestResourceContent("e2e/failures/disallow-java-digit-separator.json5"))); 39 | 40 | assertEquals("Java-style digit separators are not allowed at index 28 [character 1 in line 3]", ex.getMessage()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/de/marhali/json5/e2e/TestResourceHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5.e2e; 18 | 19 | import java.io.BufferedInputStream; 20 | import java.io.ByteArrayOutputStream; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.nio.charset.StandardCharsets; 24 | import java.util.Optional; 25 | 26 | /** 27 | * @author Marcel Haßlinger 28 | */ 29 | public class TestResourceHelper { 30 | public static InputStream getTestResource(String fileName) { 31 | return Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); 32 | } 33 | 34 | public static String getTestResourceContent(String fileName) throws IOException { 35 | try (BufferedInputStream bis = new BufferedInputStream(getTestResource(fileName))) { 36 | ByteArrayOutputStream buf = new ByteArrayOutputStream(); 37 | 38 | for (int result = bis.read(); result != -1; result = bis.read()) { 39 | buf.write((byte) result); 40 | } 41 | 42 | return Optional.ofNullable(buf.toString(StandardCharsets.UTF_8)) 43 | .map(s -> s.replace("\r\n", "\n")) 44 | .map(s -> s.replace("\r", "\n")) 45 | .orElse(null); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/de/marhali/json5/Json5NullTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5; 18 | 19 | import de.marhali.json5.fixtures.ToStringFixtures; 20 | import org.junit.jupiter.api.DisplayName; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import static org.junit.jupiter.api.Assertions.*; 24 | 25 | /** 26 | * @author Marcel Haßlinger 27 | */ 28 | public class Json5NullTest { 29 | @Test 30 | @DisplayName("deepCopy(): it should just copy comment") 31 | void test_deepCopy() { 32 | Json5Null source = new Json5Null(); 33 | String sourceComment = "my comment"; 34 | source.setComment(sourceComment); 35 | 36 | Json5Element copy = source.deepCopy(); 37 | String newComment = "new comment"; 38 | source.setComment(newComment); 39 | 40 | assertTrue(copy.isJson5Null()); 41 | assertEquals(sourceComment, copy.getComment()); 42 | assertEquals(newComment, source.getComment()); 43 | } 44 | 45 | @Test 46 | @DisplayName("getAsString(): it should return null value") 47 | void test_getAsString() { 48 | var element = new Json5Null(); 49 | assertEquals("null", element.getAsString()); 50 | } 51 | 52 | @Test 53 | void test_toString() { 54 | var element = new Json5Null(); 55 | assertEquals("null", element.toString(ToStringFixtures.OPTIONS)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/de/marhali/json5/config/DuplicateKeyStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (C) 2021 SyntaxError404 5 | * Copyright (C) 2025 Marcel Haßlinger 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package de.marhali.json5.config; 27 | 28 | /** 29 | * An enum containing all supported behaviors for duplicate keys 30 | * 31 | * @author SyntaxError404 32 | * @author Marcel Haßlinger 33 | */ 34 | public enum DuplicateKeyStrategy { 35 | 36 | /** 37 | * Throws an {@link de.marhali.json5.exception.Json5Exception exception} when a key 38 | * is encountered multiple times within the same object 39 | */ 40 | UNIQUE, 41 | 42 | /** 43 | * Only the last encountered value is significant, 44 | * all previous occurrences are silently discarded 45 | */ 46 | LAST_WINS, 47 | 48 | /** 49 | * Wraps duplicate values inside an {@link de.marhali.json5.Json5Array array}, 50 | * effectively treating them as if they were declared as one 51 | */ 52 | DUPLICATE 53 | 54 | } 55 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ### Added 10 | 11 | ### Changed 12 | 13 | ### Deprecated 14 | 15 | ### Removed 16 | 17 | ### Fixed 18 | 19 | ### Security 20 | 21 | ## 3.0.0 - 2025-09-23 22 | 23 | ### Added 24 | 25 | - Option: `stringifyUnixInstants` 26 | - Option: `stringifyAscii` 27 | - Option: `allowNaN` 28 | - Option: `allowInfinity` 29 | - Option: `quoteSingle` 30 | - Option: `quoteless` 31 | - Option: `allowBinaryLiterals` 32 | - Option: `allowOctalLiterals` 33 | - Option: `allowHexFloatingLiterals` 34 | - Option: `allowLongUnicodeEscapes` 35 | - Option: `allowTrailingData` 36 | - Option: `parseComments` 37 | - Option: `writeComments` 38 | - Option: `insertFinalNewline` 39 | - Option: `digitSeparatorStrategy` 40 | - Option: `duplicateBehaviour` 41 | 42 | ### Changed 43 | 44 | - `Json5Null` is no longer a singleton as it allows individual comments 45 | - `Json5Primitive` holds any primitive value besides `Json5Null` 46 | - `Json5Options` with more advanced builder 47 | 48 | ### Removed 49 | 50 | - `Json5Boolean`, `Json5Hexadecimal`, `Json5Number` and `Json5String` in favor of `Json5Primitive`. 51 | 52 | ## 2.0.1 - 2025-09-03 53 | 54 | ### Changed 55 | 56 | - Update dependencies 57 | 58 | ### Fixed 59 | 60 | - Fix unit tests on Windows (#18) - thanks to @Zim-Inn 61 | 62 | ## 2.0.0 - 2022-02-02 63 | 64 | ### Changed 65 | 66 | - For consistency, all methods that return a Json5 data type have been refactored to use the same naming convention 67 | 68 | ## 1.0.1 - 2022-02-22 69 | 70 | ### Changed 71 | 72 | - Json5Parser#parse will return null if provided Json5Lexer does not contain any data 73 | 74 | ## 1.0.0 - 2022-02-21 75 | 76 | ### Added 77 | 78 | - Object-oriented access to all Json5 types 79 | - Parse json5 data by InputStream / Reader / String 80 | - Serialize json5 data to OutputStream / Writer / String 81 | - Json5Options with builder pattern (Json5OptionsBuilder) to configure Json5 82 | - options: allowInvalidSurrogates, quoteSingle, trailingComma, indentFactor 83 | -------------------------------------------------------------------------------- /src/main/java/de/marhali/json5/internal/RadixNumber.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Marcel Haßlinger 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marhali.json5.internal; 18 | 19 | import java.util.Objects; 20 | 21 | /** 22 | * Simple wrapper around {@link Number} that tracks the used radix base. 23 | * 24 | * @author Marcel Haßlinger 25 | */ 26 | public class RadixNumber { 27 | 28 | /** 29 | * Referenced number. 30 | */ 31 | private final Number number; 32 | 33 | /** 34 | * Radix base to use. 35 | *

36 | * Supported values are: 37 | *