diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c7ac70..b333485 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ What changed for someone using zig-hocon: the Zig package, the `hocon` command l ## [Unreleased] +### Added + +- Twelve conformance cases from bug reports in lightbend/config and ekrich/sconfig where the spec has the answer: typesafe/config fails inside `${a} { … }`, turns integers past 2^63 into strings and accepts `\u+041`; pyhocon loses `+=` across an include and the text of a substituted number. zig-hocon passes all 469. + ### Fixed - An object after a substitution, `top = ${x} { … }`, is a merge, the same as `top = ${x}` and `top { … }` written as two fields. A `+=` inside it appends to the list `${x}` gives (`top = ${x} { arr += four }` was `[four]`, is `[one, four]`), a self-reference looks back at it, and `${top.n}` inside it sees the merged object instead of reporting a cycle. Eight new conformance cases pin it. diff --git a/README.md b/README.md index 283b405..9c06b56 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ The rest of this page reads these two files. ## Why zig-hocon -- **Correct.** Passes all 457 tests in the [conformance suite](https://htmlpreview.github.io/?https://github.com/lejmr/zig-hocon/blob/main/conformance/report.html), one test for each sentence of the spec. +- **Correct.** Passes all 469 tests in the [conformance suite](https://htmlpreview.github.io/?https://github.com/lejmr/zig-hocon/blob/main/conformance/report.html), one test for each sentence of the spec. - **Your schema is a Zig struct.** HOCON is only what sits on disk; the struct says what it holds, with defaults, optionals, enums, durations and sizes. - **Fast.** A 10 MB config in a quarter of a second.\* - **Clear errors.** File, line, column and a caret, also for a value of the wrong type. @@ -210,13 +210,13 @@ b.step("check-config", "Check the config against Config").dependOn(&check.step); | implementation | against the spec | against typesafe/config 1.4.9 | |---|---|---| -| Java typesafe/config 1.4.9 | 88.4% (404/457) | 100% (457/457) | -| hocon2 hocon2 0.6.3 | 84.7% (387/457) | 78.8% (360/457) | -| pulse-pyhocon pulse-pyhocon 0.2.1 | 69.4% (317/457) | 67.0% (306/457) | -| py.hocon hocon-parser 1.14.2 (Python) | 87.1% (398/457) | 86.4% (395/457) | -| pyhocon pyhocon 0.3.63 | 70.0% (320/457) | 65.6% (300/457) | -| rs.hocon hocon-parser 1.14.2 (Rust) | 87.1% (398/457) | 84.5% (386/457) | -| zig-hocon zig-hocon code@5b9031b | 100% (457/457) | 88.4% (404/457) | +| Java typesafe/config 1.4.9 | 87.4% (410/469) | 100% (469/469) | +| hocon2 hocon2 0.6.3 | 82.5% (387/469) | 76.8% (360/469) | +| pulse-pyhocon pulse-pyhocon 0.2.1 | 67.6% (317/469) | 65.2% (306/469) | +| py.hocon hocon-parser 1.14.2 (Python) | 84.9% (398/469) | 84.2% (395/469) | +| pyhocon pyhocon 0.3.63 | 69.1% (324/469) | 64.2% (301/469) | +| rs.hocon hocon-parser 1.14.2 (Rust) | 84.9% (398/469) | 82.3% (386/469) | +| zig-hocon zig-hocon code@d844ec9 | 100% (469/469) | 87.4% (410/469) | The table keeps the best parsers; the others measured, and why they are not in it: [conformance/README.md](conformance/README.md#not-in-the-table). diff --git a/conformance/maintaining/COVERAGE.html b/conformance/maintaining/COVERAGE.html index ed147cc..a08a941 100644 --- a/conformance/maintaining/COVERAGE.html +++ b/conformance/maintaining/COVERAGE.html @@ -70,9 +70,9 @@
- files must be valid UTF-8 - - quoted strings are in the same format as JSON strings - - values have possible types: string, number, object, array, boolean, null - - allowed number formats matches JSON; as in JSON, some possible + - quoted strings are in the same format as JSON strings + - values have possible types: string, number, object, array, boolean, null + - allowed number formats matches JSON; as in JSON, some possible floating-point values are not represented, such as `NaN`
Anything between `//` or `#` and the next newline is considered a comment
@@ -121,7 +121,7 @@ Whitespace
0x000A.
The JSON spec does not clarify how duplicate keys in the same -object should be handled. In HOCON, duplicate keys that appear +object should be handled. In HOCON, duplicate keys that appear later override those that appear earlier, unless both values are objects. If both values are objects, then the objects are merged.
Note: this would make HOCON a non-superset of JSON if you assume @@ -259,12 +259,12 @@
- `true` and `false` become the strings `"true"` and `"false"`. - `null` becomes the string `"null"`. - quoted and unquoted strings are themselves. - - numbers should be kept as they were originally written in the + - numbers should be kept as they were originally written in the file. For example, if you parse `1e5` then you might render it alternatively as `1E5` with capital `E`, or just `100000`. For purposes of value concatenation, it should be rendered as it was written in the file. - - a substitution is replaced with its value which is then + - a substitution is replaced with its value which is then converted to a string as above. - it is invalid for arrays or objects to appear in a string value concatenation.
@@ -276,7 +276,7 @@Arrays can be concatenated with arrays, and objects with objects, but it is an error if they are mixed.
-For purposes of concatenation, "array" also means "substitution
+ For purposes of concatenation, "array" also means "substitution
that resolves to an array" and "object" also means "substitution
that resolves to an object." Within a field value or array element, if only non-newline
@@ -285,7 +285,7 @@ For objects, "concatenation" means "merging", so the second object
+ For objects, "concatenation" means "merging", so the second object
overrides the first. Arrays and objects cannot be field keys, whether concatenation is
involved or not. If a substitution with the `${?foo}` syntax is undefined: - if it is the value of an object field then the field should not
- be created. If the field would have overridden a previously-set
+ - if it is the value of an object field then the field should not
+ be created. If the field would have overridden a previously-set
value for the same field, then the previous value remains.
- if it is an array element then the element should not be added.
- if it is part of a value concatenation with another string then
@@ -529,7 +529,7 @@ a += b becomes: a = ${?a} [b] `+=` appends an element to a previous array. If the previous value
+ `+=` appends an element to a previous array. If the previous value
was not an array, an error will result just as it would in the
long form `a = ${?a} [b]`. Note that the previous value is
optional (`${?a}` not `${a}`), which allows `a += b` to be the
@@ -560,7 +560,7 @@ foo : ${?foo} // this field just disappears silently If a substitution is hidden by a value that could not be merged
+ If a substitution is hidden by a value that could not be merged
with it (by a non-object value) then it is never evaluated and no
error will be reported. So for example: foo : ${does-not-exist}
@@ -569,7 +569,7 @@ A self-reference resolves to the value "below" even if it's part
+ A self-reference resolves to the value "below" even if it's part
of a path expression. So for example: foo : { a : { c : 1 } }
foo : ${foo.a}
@@ -724,7 +724,7 @@ If an included file contains an array as the root value, it is
invalid and an error should be generated. The included file should be parsed, producing a root object. The
+ The included file should be parsed, producing a root object. The
keys from the root object are conceptually substituted for the
include statement in the including file. - If a key in the included object occurred prior to the include
@@ -748,7 +748,7 @@ And "foo.conf" might look like this: { x : 10, y : ${x} } If you parsed "foo.conf" in isolation, then `${x}` would evaluate
-to 10, the value at the path `x`. If you include "foo.conf" in an
+to 10, the value at the path `x`. If you include "foo.conf" in an
object at key `a`, however, then it must be fixed up to be
`${a.x}` rather than `${x}`. Say that the root configuration redefines `a.x`, like this: Each column is what that implementation actually returns, keys in the spec's order. The spec column is the bar being scored; Java's is, by definition, the other one.
Hover over a result to compare it with the spec: green is only in the result, red only in the spec, yellow differs.Array and object concatenation
substitution, the two values are concatenated. Newlines may occur
_within_ the array or object, but not _between_ them. Newlines
_between_ prevent concatenation.Substitutions
is undefined. An undefined substitution with the `${foo}` syntax
is invalid and should generate an error.The `+=` field separator
Examples of Self-Referential Substitutions
rather than "intractable cycle"; as a result, the optional
substitution syntax `${?foo}` does not create a cycle:Examples of Self-Referential Substitutions
know `foo` is `42`, so `${does-not-exist}` is never evaluated and
there is no error. The same is true for cycles like `foo : ${foo},
foo : 42`, where the initial self-reference must simply be ignored.Include semantics: merging
values in a document.Include semantics: substitution
HOCON Conformance
- HOCON Conformance
Where the two bars disagree
- 53 of 457 rows. Everywhere else they ask the same question.
+ 59 of 469 rows. Everywhere else they ask the same question.
input the spec requires Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon x = { p = 1 }\nn = { a = {}\n a = ${x} } ${y}\ny = { q = 2 }\nunsupported {"x":{"p":1},"n":{"a":{"p":1},"q":2},"y":{"q":2}} rejected {"x":{"p":1},"n":{"a":{"p":1},"q":2},"y":{"q":2}} {"x":{"p":1},"n":{"a":{"p":1},"q":2},"y":{"q":2}} {"x":{"p":1},"n":{"a":{"p":1},"q":2},"y":{"q":2}} {"x":{"p":1},"n":{"a":{"p":1},"q":2},"y":{"q":2}} {"x":{"p":1},"n":{"a":{"p":1},"q":2},"y":{"q":2}} {"x":{"p":1},"n":{"a":{"p":1},"q":2},"y":{"q":2}} a = { q = 1 }\na = { p = {}\n p = ${x} } ${a}\nx = { r = 2 }\nunsupported {"a":{"p":{"r":2},"q":1},"x":{"r":2}} rejected {"a":{"q":1,"p":{"r":2}},"x":{"r":2}} {"a":{"p":{"r":2},"q":1},"x":{"r":2}} {"a":{"q":1,"p":{"r":2}},"x":{"r":2}} {"a":{"q":1,"p":{"r":2}},"x":{"r":2}} {"a":{"q":1,"p":{"r":2}},"x":{"r":2}} {"a":{"q":1,"p":{"r":2}},"x":{"r":2}} x = [1]\ny = [2]\na = ${x}${y}\nlenient rejects {"a":[1,2],"x":[1],"y":[2]} rejected rejected rejected rejected rejected rejected x = [1]\na = ${?absent} ${x}\nunsupported {"x":[1],"a":[1]} rejected {"x":[1],"a":[1]} {"x":[1],"a":[1]} {"x":[1],"a":[1]} {"x":[1],"a":[1]} {"x":[1],"a":[1]} {"x":[1],"a":[1]} v = { x = 1 }\nr = null\nr = ${v}\na = { helper = 0 }\na = ${r}\ndiverges {"v":{"x":1},"r":{"x":1},"a":{"helper":0,"x":1}} {"a":{"x":1},"r":{"x":1},"v":{"x":1}} {"v":{"x":1},"r":{"x":1},"a":{"helper":0,"x":1}} {"v":{"x":1},"r":{"x":1},"a":{"x":1}} {"v":{"x":1},"r":{"x":1},"a":{"helper":0,"x":1}} {"v":{"x":1},"r":{"x":1},"a":{"helper":0,"x":1}} {"v":{"x":1},"r":{"x":1},"a":{"helper":0,"x":1}} {"v":{"x":1},"r":{"x":1},"a":{"helper":0,"x":1}} v = { nested = { old = 2 }, nested = null, nested = { x = 1 } }\na = { nested = { helper = 0 } }\na = ${v}\ndiverges {"v":{"nested":{"x":1}},"a":{"nested":{"helper":0,"x":1}}} {"a":{"nested":{"x":1}},"v":{"nested":{"x":1}}} {"v":{"nested":{"x":1}},"a":{"nested":{"helper":0,"x":1}}} {"v":{"nested":{"x":1}},"a":{"nested":{"x":1}}} {"v":{"nested":{"x":1}},"a":{"nested":{"helper":0,"x":1}}} {"v":{"nested":{"x":1}},"a":{"nested":{"helper":0,"x":1}}} {"v":{"nested":{"x":1}},"a":{"nested":{"helper":0,"x":1}}} {"v":{"nested":{"x":1}},"a":{"nested":{"helper":0,"x":1}}} v = { nested = null, nested = ${payload}, sibling = ${payload} }\npayload = { x = 1 }\na = { nested = { helper = 0 } }\na = ${v}\no24bbd = ${a.nested}\ndiverges {"v":{"nested":{"x":1},"sibling":{"x":1}},"payload":{"x":1},"a":{"nested":{"helper":0,"x":1},"sibling":{"x":1}},"o24bbd":{"helper":0,"x":1}} {"a":{"nested":{"x":1},"sibling":{"x":1}},"o24bbd":{"x":1},"payload":{"x":1},"v":{"nested":{"x":1},"sibling":{"x":1}}} {"v":{"nested":{"x":1},"sibling":{"x":1}},"payload":{"x":1},"a":{"nested":{"helper":0,"x":1},"sibling":{"x":1}},"o24bbd":{"helper":0,"x":1}} {"v":{"nested":{"x":1},"sibling":{"x":1}},"payload":{"x":1},"a":{"nested":{"helper":0,"x":1},"sibling":{"x":1}},"o24bbd":{"helper":0,"x":1}} {"v":{"nested":{"x":1},"sibling":{"x":1}},"payload":{"x":1},"a":{"nested":{"helper":0,"x":1},"sibling":{"x":1}},"o24bbd":{"helper":0,"x":1}} {"v":{"nested":{"x":1},"sibling":{"x":1}},"payload":{"x":1},"a":{"nested":{"helper":0,"x":1},"sibling":{"x":1}},"o24bbd":{"helper":0,"x":1}} {"v":{"nested":{"x":1},"sibling":{"x":1}},"payload":{"x":1},"a":{"nested":{"helper":0,"x":1},"sibling":{"x":1}},"o24bbd":{"helper":0,"x":1}} {"v":{"nested":{"x":1},"sibling":{"x":1}},"payload":{"x":1},"a":{"nested":{"helper":0,"x":1},"sibling":{"x":1}},"o24bbd":{"helper":0,"x":1}} v = null\nv = { x = ${pending} }\npending = 1\na = { helper = 0 }\na = ${v}\ndiverges {"v":{"x":1},"pending":1,"a":{"helper":0,"x":1}} {"a":{"x":1},"pending":1,"v":{"x":1}} {"v":{"x":1},"pending":1,"a":{"helper":0,"x":1}} {"v":{"x":1},"pending":1,"a":{"x":1}} {"v":{"x":1},"pending":1,"a":{"helper":0,"x":1}} {"v":{"x":1},"pending":1,"a":{"helper":0,"x":1}} {"v":{"x":1},"pending":1,"a":{"helper":0,"x":1}} {"v":{"x":1},"pending":1,"a":{"helper":0,"x":1}} r = null\nr = { y = 1 }\na = { x = ${missing} }\na = ${r}\nlenient rejects {"a":{"y":1},"r":{"y":1}} rejected {"r":{"y":1},"a":{"y":1}} rejected rejected rejected rejected v = { nested = null, nested = ${pending} }\nv = ${pendingTop}\na = { nested = { helper = 0 } }\na = ${v}\npending = { x = 1 }\npendingTop = { y = 2 }\ndiverges {"v":{"nested":{"x":1},"y":2},"a":{"nested":{"helper":0,"x":1},"y":2},"pending":{"x":1},"pendingTop":{"y":2}} {"a":{"nested":{"x":1},"y":2},"pending":{"x":1},"pendingTop":{"y":2},"v":{"nested":{"x":1},"y":2}} {"v":{"nested":{"x":1},"y":2},"a":{"nested":{"helper":0,"x":1},"y":2},"pending":{"x":1},"pendingTop":{"y":2}} {"v":{"y":2},"a":{"y":2},"pending":{"x":1},"pendingTop":{"y":2}} {"v":{"nested":{"x":1},"y":2},"a":{"nested":{"helper":0,"x":1},"y":2},"pending":{"x":1},"pendingTop":{"y":2}} rejected {"v":{"nested":{"x":1},"y":2},"a":{"nested":{"helper":0,"x":1},"y":2},"pending":{"x":1},"pendingTop":{"y":2}} {"v":{"nested":{"x":1},"y":2},"a":{"nested":{"helper":0,"x":1},"y":2},"pending":{"x":1},"pendingTop":{"y":2}} a = { n = null, n = { d = null, d = ${p} } }\np = { x = 1 }\no24bbd = ${a.n.d}\nunsupported {"a":{"n":{"d":{"x":1}}},"p":{"x":1},"o24bbd":{"x":1}} rejected {"a":{"n":{"d":{"x":1}}},"p":{"x":1},"o24bbd":{"x":1}} {"a":{"n":{"d":{"x":1}}},"p":{"x":1},"o24bbd":{"x":1}} {"a":{"n":{"d":{"x":1}}},"p":{"x":1},"o24bbd":{"x":1}} {"a":{"n":{"d":{"x":1}}},"p":{"x":1},"o24bbd":{"x":1}} {"a":{"n":{"d":{"x":1}}},"p":{"x":1},"o24bbd":{"x":1}} {"a":{"n":{"d":{"x":1}}},"p":{"x":1},"o24bbd":{"x":1}} p = ${a.m}\na = [ 1 ]\na = { m = ${x} }\nx = 1\nunsupported {"p":1,"a":{"m":1},"x":1} rejected {"p":1,"a":{"m":1},"x":1} {"p":1,"a":{"m":1},"x":1} {"p":1,"a":{"m":1},"x":1} {"p":1,"a":{"m":1},"x":1} {"p":1,"a":{"m":1},"x":1} {"p":1,"a":{"m":1},"x":1} 006-json-extension-means-json-syntax/ (directory)lenient rejects {"a":"b"} {"a":"b"} {"a":"b"} {"a":"b"} {"a":"b"} {"a":"b"} rejected 007-properties-extension-means-properties-syntax/ (directory)unsupported {"a":"b=c","n":"1"} rejected rejected {"a":"b=c","n":1} {"a":"b=c","n":"1"} rejected {"a":"b=c","n":"1"} {"a":"b=c","n":"1"} 023-json-leading-zero-illegal/ (directory)lenient rejects {"a":1} {} {} {"a":1} {} {"a":01} rejected 024-unknown-extension-is-not-a-basename/ (directory)diverges {"a":1} {} {"a":1} {"a":1} {} {"a":1} {"a":1} {"a":1} 011-include-inside-a-list-with-a-substitution/ (directory)unsupported {"a":[{"x":1}],"y":1} rejected {"a":[{"x":1}],"y":1} {"a":[{"x":1}],"y":1} {"a":[{"x":1}],"y":1} {"a":[{"x":1}],"y":1} {"a":[{"x":1}],"y":1} {"a":[{"x":1}],"y":1} 009-undefined-optional-at-fixed-up-path-falls-back/ (directory)unsupported {"common":{"x":{},"a":0},"x":{"y":0}} rejected {"common":{"x":{},"a":0},"x":{"y":0}} {"common":{"x":{},"a":0},"x":{"y":0}} {"common":{"x":{}},"x":{"y":0}} {"common":{"x":{},"a":0},"x":{"y":0}} {"common":{"x":{}},"x":{"y":0}} {"common":{"x":{},"a":0},"x":{"y":0}} 011-optional-reference-falls-back-too/ (directory)diverges {"common":{"x":{},"a":0},"x":{"y":0}} {"common":{"x":{}},"x":{"y":0}} {"common":{"x":{},"a":0},"x":{"y":0}} {"common":{"x":{},"a":0},"x":{"y":0}} {"common":{"x":{}},"x":{"y":0}} {"common":{"x":{},"a":0},"x":{"y":0}} {"common":{"x":{}},"x":{"y":0}} {"common":{"x":{},"a":0},"x":{"y":0}} 014-unrelated-key-does-not-change-the-result/ (directory)unsupported {"common":{"x":{},"a":0},"x":{"y":0},"zzz":{}} rejected {"common":{"x":{},"a":0},"x":{"y":0},"zzz":{}} {"common":{"x":{},"a":0},"x":{"y":0},"zzz":{}} {"common":{"x":{}},"x":{"y":0},"zzz":{}} {"common":{"x":{},"a":0},"x":{"y":0},"zzz":{}} {"common":{"x":{}},"x":{"y":0},"zzz":{}} {"common":{"x":{},"a":0},"x":{"y":0},"zzz":{}} [1, 2, 3]\nunsupported [1,2,3] rejected [1,2,3] [1,2,3] rejected [1,2,3] rejected [1,2,3] []\nunsupported [] rejected [] [] rejected [] rejected [] # a leading comment\n[1, 2]\nunsupported [1,2] rejected [1,2] [1,2] rejected [1,2] rejected [1,2] \n[1, 2]\nunsupported [1,2] rejected [1,2] [1,2] rejected [1,2] rejected [1,2] include.foo : 42\nlenient rejects {"include":{"foo":42}} rejected {"include":{"foo":42}} rejected {"include":{"foo":42}} rejected rejected a = [ { b += 1 } ]\nunsupported {"a":[{"b":[1]}]} rejected {"a":[{"b":[1]}]} {"a":[{"b":1}]} {"a":[{"b":[1]}]} {"a":[{"b":1}]} {"a":[{"b":[1]}]} {"a":[{"b":[1]}]} x = { p = 1 }\nl += { a = {}\n a = ${x} }\nunsupported {"x":{"p":1},"l":[{"a":{"p":1}}]} rejected {"x":{"p":1},"l":[{"a":{"p":1}}]} {"x":{"p":1},"l":{"a":{"p":1}}} {"x":{"p":1},"l":[{"a":{"p":1}}]} {"x":{"p":1},"l":{"a":{"p":1}}} {"x":{"p":1},"l":[{"a":{"p":1}}]} {"x":{"p":1},"l":[{"a":{"p":1}}]} x { arr = [one] }\ntop : ${x} { arr += four }\ndiverges {"x":{"arr":["one"]},"top":{"arr":["one","four"]}} {"top":{"arr":["four"]},"x":{"arr":["one"]}} — — — {"x":{"arr":["one"]},"top":{"arr":" four"}} — {"x":{"arr":["one"]},"top":{"arr":["one","four"]}} x { l = [1] }\ny : ${x} { l += 2 }\nz : ${y} { l += 3 }\ndiverges {"x":{"l":[1]},"y":{"l":[1,2]},"z":{"l":[1,2,3]}} {"x":{"l":[1]},"y":{"l":[2]},"z":{"l":[3]}} — — — {"x":{"l":[1]},"y":{"l":2},"z":{"l":3}} — {"x":{"l":[1]},"y":{"l":[1,2]},"z":{"l":[1,2,3]}} x { a { b { c = [1] } } }\ntop : ${x} { a { b { c += 2 } } }\ndiverges {"x":{"a":{"b":{"c":[1]}}},"top":{"a":{"b":{"c":[1,2]}}}} {"top":{"a":{"b":{"c":[2]}}},"x":{"a":{"b":{"c":[1]}}}} — — — {"x":{"a":{"b":{"c":[1]}}},"top":{"a":{"b":{"c":2}}}} — {"x":{"a":{"b":{"c":[1]}}},"top":{"a":{"b":{"c":[1,2]}}}} x = {}\ny = ${x} { l = [1, 2] }\ny.l += 3\ndiverges {"x":{},"y":{"l":[1,2,3]}} {"x":{},"y":{"l":[3]}} — — — {"x":{},"y":{"l":3}} — {"x":{},"y":{"l":[1,2,3]}} wrapper = [${does-not-exist}]\nreplacement = {}\nwrapper = ${replacement}\nunsupported {"wrapper":{},"replacement":{}} rejected {"wrapper":{},"replacement":{}} {"wrapper":{},"replacement":{}} rejected {"wrapper":{},"replacement":{}} rejected {"wrapper":{},"replacement":{}} w = [${does-not-exist}]\nr = { x = 1 }\nw = ${r}\nzzz = ${w.x}\nunsupported {"w":{"x":1},"r":{"x":1},"zzz":1} rejected {"w":{"x":1},"r":{"x":1},"zzz":1} {"w":{"x":1},"r":{"x":1},"zzz":1} rejected {"w":{"x":1},"r":{"x":1},"zzz":1} rejected {"w":{"x":1},"r":{"x":1},"zzz":1} d { x = [0] }\nc : ${d} { x : ${c.x} [1, 2] }\nunsupported {"d":{"x":[0]},"c":{"x":[0,1,2]}} rejected — — — {"d":{"x":[0]},"c":{"x":[0,1,2]}} — {"d":{"x":[0]},"c":{"x":[0,1,2]}} d { x = [0] }\ne { z = 5 }\nc : ${d} ${e} { x : ${c.x} [1], w : ${e.z} }\nunsupported {"d":{"x":[0]},"e":{"z":5},"c":{"x":[0,1],"z":5,"w":5}} rejected — — — {"d":{"x":[0]},"e":{"z":5},"c":{"x":[0,1,1],"z":5,"w":5}} — {"d":{"x":[0]},"e":{"z":5},"c":{"x":[0,1],"z":5,"w":5}} mixin { hello = world }\na = ${mixin} { y = ${a.hello} }\nunsupported {"mixin":{"hello":"world"},"a":{"hello":"world","y":"world"}} rejected — — — {"mixin":{"hello":"world"},"a":{"hello":"world","y":"world"}} — {"mixin":{"hello":"world"},"a":{"hello":"world","y":"world"}} x { n = 1 }\ntop = ${x} { n = 2, seen = ${top.n} }\nunsupported {"x":{"n":1},"top":{"n":2,"seen":2}} rejected — — — {"x":{"n":1},"top":{"n":2,"seen":2}} — {"x":{"n":1},"top":{"n":2,"seen":2}} base = {}\nchild = ${base} { foo = ${missing} }\nchild.foo = x\nunsupported {"base":{},"child":{"foo":"x"}} rejected — — — {"base":{},"child":{"foo":"x"}} — {"base":{},"child":{"foo":"x"}} a = { b: 1 } x\nlenient rejects {"a":{"b":1}} rejected rejected rejected rejected rejected rejected a = [ 1, 2 ] x\nlenient rejects {"a":[1,2]} rejected rejected rejected rejected rejected rejected x = [1]suffix\nlenient rejects {"x":[1]} rejected rejected rejected rejected rejected rejected x = {a:1}suffix\nlenient rejects {"x":{"a":1}} rejected rejected rejected rejected rejected rejected x = [1]suffix[2]\nlenient rejects {"x":[1,2]} rejected rejected rejected rejected rejected rejected a = ${?x}42\ndiverges {"a":"42"} {"a":42} {"a":42} {"a":"42"} {"a":42} {"a":42} {"a":42} {"a":"42"} a = 01\nlenient rejects {"a":1} {"a":"01"} {"a":1} {"a":1} {"a":1} {"a":01} rejected a = -.33\nlenient rejects {"a":-0.33} {"a":"-.33"} {"a":-0.33} {"a":-0.33} {"a":-0.33} {"a":-.33} rejected a = -.33e+1\nlenient rejects {"a":-3.3} rejected {"a":-3.3} {"a":-3.3} {"a":-3.3} {"a":-.33e+1} rejected a = 1.\nlenient rejects {"a":1} {"a":"1."} {"a":"1."} {"a":1.0} {"a":"1."} {"a":1.} rejected a = -1.e3\nlenient rejects {"a":-1000} {"a":"-1.e3"} {"a":-1000.0} {"a":-1000.0} {"a":"-1.e3"} {"a":-1.e3} rejected a = -0033\nlenient rejects {"a":-33} {"a":"-0033"} {"a":-33} {"a":-33} {"a":-33} {"a":-0033} rejected a = -foo\nlenient rejects {"a":"-foo"} {"a":"-foo"} {"a":"-foo"} {"a":"-foo"} {"a":"-foo"} {"a":"-foo"} rejected a = 1e\nlenient rejects {"a":"1e"} {"a":"1e"} {"a":"1e"} {"a":"1e"} {"a":"1e"} {"a":"1e"} rejected a = 1.2.3\nlenient rejects {"a":"1.2.3"} — — — {"a":"1.2.3"} — rejected input the spec requires Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon x = { p = 1 }\nn = { a = {}\n a = ${x} } ${y}\ny = { q = 2 }\nunsupported {"x":{"p":1},"n":{"a":{"p":1},"q":2},"y":{"q":2}} rejected {"x":{"p":1},"n":{"a":{"p":1},"q":2},"y":{"q":2}} {"x":{"p":1},"n":{"a":{"p":1},"q":2},"y":{"q":2}} {"x":{"p":1},"n":{"a":{"p":1},"q":2},"y":{"q":2}} {"x":{"p":1},"n":{"a":{"p":1},"q":2},"y":{"q":2}} {"x":{"p":1},"n":{"a":{"p":1},"q":2},"y":{"q":2}} {"x":{"p":1},"n":{"a":{"p":1},"q":2},"y":{"q":2}} a = { q = 1 }\na = { p = {}\n p = ${x} } ${a}\nx = { r = 2 }\nunsupported {"a":{"p":{"r":2},"q":1},"x":{"r":2}} rejected {"a":{"q":1,"p":{"r":2}},"x":{"r":2}} {"a":{"p":{"r":2},"q":1},"x":{"r":2}} {"a":{"q":1,"p":{"r":2}},"x":{"r":2}} {"a":{"q":1,"p":{"r":2}},"x":{"r":2}} {"a":{"q":1,"p":{"r":2}},"x":{"r":2}} {"a":{"q":1,"p":{"r":2}},"x":{"r":2}} a = {}\nb = ${a} { c = 1, c = ${?missing} }\nb = ${a} { c = 2, c = ${?missing} }\nunsupported {"a":{},"b":{"c":2}} rejected — — — {"a":{},"b":{"c":null}} — {"a":{},"b":{"c":2}} t = { x = 1 }\na = {}\nb = ${a} { c = reset, c = ${t} }\nunsupported {"t":{"x":1},"a":{},"b":{"c":{"x":1}}} rejected — — — {"t":{"x":1},"a":{},"b":{"c":{"x":1}}} — {"t":{"x":1},"a":{},"b":{"c":{"x":1}}} tail = [2]\nfront = [ { foo = 1, foo = ${?bar} } ] ${tail}\nunsupported {"tail":[2],"front":[{"foo":1},2]} rejected — — — {"tail":[2],"front":[{"foo":1},2]} — {"tail":[2],"front":[{"foo":1},2]} x = [1]\ny = [2]\na = ${x}${y}\nlenient rejects {"a":[1,2],"x":[1],"y":[2]} rejected rejected rejected rejected rejected rejected x = [1]\na = ${?absent} ${x}\nunsupported {"x":[1],"a":[1]} rejected {"x":[1],"a":[1]} {"x":[1],"a":[1]} {"x":[1],"a":[1]} {"x":[1],"a":[1]} {"x":[1],"a":[1]} {"x":[1],"a":[1]} v = { x = 1 }\nr = null\nr = ${v}\na = { helper = 0 }\na = ${r}\ndiverges {"v":{"x":1},"r":{"x":1},"a":{"helper":0,"x":1}} {"a":{"x":1},"r":{"x":1},"v":{"x":1}} {"v":{"x":1},"r":{"x":1},"a":{"helper":0,"x":1}} {"v":{"x":1},"r":{"x":1},"a":{"x":1}} {"v":{"x":1},"r":{"x":1},"a":{"helper":0,"x":1}} {"v":{"x":1},"r":{"x":1},"a":{"helper":0,"x":1}} {"v":{"x":1},"r":{"x":1},"a":{"helper":0,"x":1}} {"v":{"x":1},"r":{"x":1},"a":{"helper":0,"x":1}} v = { nested = { old = 2 }, nested = null, nested = { x = 1 } }\na = { nested = { helper = 0 } }\na = ${v}\ndiverges {"v":{"nested":{"x":1}},"a":{"nested":{"helper":0,"x":1}}} {"a":{"nested":{"x":1}},"v":{"nested":{"x":1}}} {"v":{"nested":{"x":1}},"a":{"nested":{"helper":0,"x":1}}} {"v":{"nested":{"x":1}},"a":{"nested":{"x":1}}} {"v":{"nested":{"x":1}},"a":{"nested":{"helper":0,"x":1}}} {"v":{"nested":{"x":1}},"a":{"nested":{"helper":0,"x":1}}} {"v":{"nested":{"x":1}},"a":{"nested":{"helper":0,"x":1}}} {"v":{"nested":{"x":1}},"a":{"nested":{"helper":0,"x":1}}} v = { nested = null, nested = ${payload}, sibling = ${payload} }\npayload = { x = 1 }\na = { nested = { helper = 0 } }\na = ${v}\no24bbd = ${a.nested}\ndiverges {"v":{"nested":{"x":1},"sibling":{"x":1}},"payload":{"x":1},"a":{"nested":{"helper":0,"x":1},"sibling":{"x":1}},"o24bbd":{"helper":0,"x":1}} {"a":{"nested":{"x":1},"sibling":{"x":1}},"o24bbd":{"x":1},"payload":{"x":1},"v":{"nested":{"x":1},"sibling":{"x":1}}} {"v":{"nested":{"x":1},"sibling":{"x":1}},"payload":{"x":1},"a":{"nested":{"helper":0,"x":1},"sibling":{"x":1}},"o24bbd":{"helper":0,"x":1}} {"v":{"nested":{"x":1},"sibling":{"x":1}},"payload":{"x":1},"a":{"nested":{"helper":0,"x":1},"sibling":{"x":1}},"o24bbd":{"helper":0,"x":1}} {"v":{"nested":{"x":1},"sibling":{"x":1}},"payload":{"x":1},"a":{"nested":{"helper":0,"x":1},"sibling":{"x":1}},"o24bbd":{"helper":0,"x":1}} {"v":{"nested":{"x":1},"sibling":{"x":1}},"payload":{"x":1},"a":{"nested":{"helper":0,"x":1},"sibling":{"x":1}},"o24bbd":{"helper":0,"x":1}} {"v":{"nested":{"x":1},"sibling":{"x":1}},"payload":{"x":1},"a":{"nested":{"helper":0,"x":1},"sibling":{"x":1}},"o24bbd":{"helper":0,"x":1}} {"v":{"nested":{"x":1},"sibling":{"x":1}},"payload":{"x":1},"a":{"nested":{"helper":0,"x":1},"sibling":{"x":1}},"o24bbd":{"helper":0,"x":1}} v = null\nv = { x = ${pending} }\npending = 1\na = { helper = 0 }\na = ${v}\ndiverges {"v":{"x":1},"pending":1,"a":{"helper":0,"x":1}} {"a":{"x":1},"pending":1,"v":{"x":1}} {"v":{"x":1},"pending":1,"a":{"helper":0,"x":1}} {"v":{"x":1},"pending":1,"a":{"x":1}} {"v":{"x":1},"pending":1,"a":{"helper":0,"x":1}} {"v":{"x":1},"pending":1,"a":{"helper":0,"x":1}} {"v":{"x":1},"pending":1,"a":{"helper":0,"x":1}} {"v":{"x":1},"pending":1,"a":{"helper":0,"x":1}} r = null\nr = { y = 1 }\na = { x = ${missing} }\na = ${r}\nlenient rejects {"a":{"y":1},"r":{"y":1}} rejected {"r":{"y":1},"a":{"y":1}} rejected rejected rejected rejected v = { nested = null, nested = ${pending} }\nv = ${pendingTop}\na = { nested = { helper = 0 } }\na = ${v}\npending = { x = 1 }\npendingTop = { y = 2 }\ndiverges {"v":{"nested":{"x":1},"y":2},"a":{"nested":{"helper":0,"x":1},"y":2},"pending":{"x":1},"pendingTop":{"y":2}} {"a":{"nested":{"x":1},"y":2},"pending":{"x":1},"pendingTop":{"y":2},"v":{"nested":{"x":1},"y":2}} {"v":{"nested":{"x":1},"y":2},"a":{"nested":{"helper":0,"x":1},"y":2},"pending":{"x":1},"pendingTop":{"y":2}} {"v":{"y":2},"a":{"y":2},"pending":{"x":1},"pendingTop":{"y":2}} {"v":{"nested":{"x":1},"y":2},"a":{"nested":{"helper":0,"x":1},"y":2},"pending":{"x":1},"pendingTop":{"y":2}} rejected {"v":{"nested":{"x":1},"y":2},"a":{"nested":{"helper":0,"x":1},"y":2},"pending":{"x":1},"pendingTop":{"y":2}} {"v":{"nested":{"x":1},"y":2},"a":{"nested":{"helper":0,"x":1},"y":2},"pending":{"x":1},"pendingTop":{"y":2}} a = { n = null, n = { d = null, d = ${p} } }\np = { x = 1 }\no24bbd = ${a.n.d}\nunsupported {"a":{"n":{"d":{"x":1}}},"p":{"x":1},"o24bbd":{"x":1}} rejected {"a":{"n":{"d":{"x":1}}},"p":{"x":1},"o24bbd":{"x":1}} {"a":{"n":{"d":{"x":1}}},"p":{"x":1},"o24bbd":{"x":1}} {"a":{"n":{"d":{"x":1}}},"p":{"x":1},"o24bbd":{"x":1}} {"a":{"n":{"d":{"x":1}}},"p":{"x":1},"o24bbd":{"x":1}} {"a":{"n":{"d":{"x":1}}},"p":{"x":1},"o24bbd":{"x":1}} {"a":{"n":{"d":{"x":1}}},"p":{"x":1},"o24bbd":{"x":1}} p = ${a.m}\na = [ 1 ]\na = { m = ${x} }\nx = 1\nunsupported {"p":1,"a":{"m":1},"x":1} rejected {"p":1,"a":{"m":1},"x":1} {"p":1,"a":{"m":1},"x":1} {"p":1,"a":{"m":1},"x":1} {"p":1,"a":{"m":1},"x":1} {"p":1,"a":{"m":1},"x":1} {"p":1,"a":{"m":1},"x":1} 006-json-extension-means-json-syntax/ (directory)lenient rejects {"a":"b"} {"a":"b"} {"a":"b"} {"a":"b"} {"a":"b"} {"a":"b"} rejected 007-properties-extension-means-properties-syntax/ (directory)unsupported {"a":"b=c","n":"1"} rejected rejected {"a":"b=c","n":1} {"a":"b=c","n":"1"} rejected {"a":"b=c","n":"1"} {"a":"b=c","n":"1"} 023-json-leading-zero-illegal/ (directory)lenient rejects {"a":1} {} {} {"a":1} {} {"a":01} rejected 024-unknown-extension-is-not-a-basename/ (directory)diverges {"a":1} {} {"a":1} {"a":1} {} {"a":1} {"a":1} {"a":1} 011-include-inside-a-list-with-a-substitution/ (directory)unsupported {"a":[{"x":1}],"y":1} rejected {"a":[{"x":1}],"y":1} {"a":[{"x":1}],"y":1} {"a":[{"x":1}],"y":1} {"a":[{"x":1}],"y":1} {"a":[{"x":1}],"y":1} {"a":[{"x":1}],"y":1} 009-undefined-optional-at-fixed-up-path-falls-back/ (directory)unsupported {"common":{"x":{},"a":0},"x":{"y":0}} rejected {"common":{"x":{},"a":0},"x":{"y":0}} {"common":{"x":{},"a":0},"x":{"y":0}} {"common":{"x":{}},"x":{"y":0}} {"common":{"x":{},"a":0},"x":{"y":0}} {"common":{"x":{}},"x":{"y":0}} {"common":{"x":{},"a":0},"x":{"y":0}} 011-optional-reference-falls-back-too/ (directory)diverges {"common":{"x":{},"a":0},"x":{"y":0}} {"common":{"x":{}},"x":{"y":0}} {"common":{"x":{},"a":0},"x":{"y":0}} {"common":{"x":{},"a":0},"x":{"y":0}} {"common":{"x":{}},"x":{"y":0}} {"common":{"x":{},"a":0},"x":{"y":0}} {"common":{"x":{}},"x":{"y":0}} {"common":{"x":{},"a":0},"x":{"y":0}} 014-unrelated-key-does-not-change-the-result/ (directory)unsupported {"common":{"x":{},"a":0},"x":{"y":0},"zzz":{}} rejected {"common":{"x":{},"a":0},"x":{"y":0},"zzz":{}} {"common":{"x":{},"a":0},"x":{"y":0},"zzz":{}} {"common":{"x":{}},"x":{"y":0},"zzz":{}} {"common":{"x":{},"a":0},"x":{"y":0},"zzz":{}} {"common":{"x":{}},"x":{"y":0},"zzz":{}} {"common":{"x":{},"a":0},"x":{"y":0},"zzz":{}} [1, 2, 3]\nunsupported [1,2,3] rejected [1,2,3] [1,2,3] rejected [1,2,3] rejected [1,2,3] []\nunsupported [] rejected [] [] rejected [] rejected [] # a leading comment\n[1, 2]\nunsupported [1,2] rejected [1,2] [1,2] rejected [1,2] rejected [1,2] \n[1, 2]\nunsupported [1,2] rejected [1,2] [1,2] rejected [1,2] rejected [1,2] include.foo : 42\nlenient rejects {"include":{"foo":42}} rejected {"include":{"foo":42}} rejected {"include":{"foo":42}} rejected rejected a = [ { b += 1 } ]\nunsupported {"a":[{"b":[1]}]} rejected {"a":[{"b":[1]}]} {"a":[{"b":1}]} {"a":[{"b":[1]}]} {"a":[{"b":1}]} {"a":[{"b":[1]}]} {"a":[{"b":[1]}]} x = { p = 1 }\nl += { a = {}\n a = ${x} }\nunsupported {"x":{"p":1},"l":[{"a":{"p":1}}]} rejected {"x":{"p":1},"l":[{"a":{"p":1}}]} {"x":{"p":1},"l":{"a":{"p":1}}} {"x":{"p":1},"l":[{"a":{"p":1}}]} {"x":{"p":1},"l":{"a":{"p":1}}} {"x":{"p":1},"l":[{"a":{"p":1}}]} {"x":{"p":1},"l":[{"a":{"p":1}}]} x { arr = [one] }\ntop : ${x} { arr += four }\ndiverges {"x":{"arr":["one"]},"top":{"arr":["one","four"]}} {"top":{"arr":["four"]},"x":{"arr":["one"]}} — — — {"x":{"arr":["one"]},"top":{"arr":" four"}} — {"x":{"arr":["one"]},"top":{"arr":["one","four"]}} x { l = [1] }\ny : ${x} { l += 2 }\nz : ${y} { l += 3 }\ndiverges {"x":{"l":[1]},"y":{"l":[1,2]},"z":{"l":[1,2,3]}} {"x":{"l":[1]},"y":{"l":[2]},"z":{"l":[3]}} — — — {"x":{"l":[1]},"y":{"l":2},"z":{"l":3}} — {"x":{"l":[1]},"y":{"l":[1,2]},"z":{"l":[1,2,3]}} x { a { b { c = [1] } } }\ntop : ${x} { a { b { c += 2 } } }\ndiverges {"x":{"a":{"b":{"c":[1]}}},"top":{"a":{"b":{"c":[1,2]}}}} {"top":{"a":{"b":{"c":[2]}}},"x":{"a":{"b":{"c":[1]}}}} — — — {"x":{"a":{"b":{"c":[1]}}},"top":{"a":{"b":{"c":2}}}} — {"x":{"a":{"b":{"c":[1]}}},"top":{"a":{"b":{"c":[1,2]}}}} x = {}\ny = ${x} { l = [1, 2] }\ny.l += 3\ndiverges {"x":{},"y":{"l":[1,2,3]}} {"x":{},"y":{"l":[3]}} — — — {"x":{},"y":{"l":3}} — {"x":{},"y":{"l":[1,2,3]}} wrapper = [${does-not-exist}]\nreplacement = {}\nwrapper = ${replacement}\nunsupported {"wrapper":{},"replacement":{}} rejected {"wrapper":{},"replacement":{}} {"wrapper":{},"replacement":{}} rejected {"wrapper":{},"replacement":{}} rejected {"wrapper":{},"replacement":{}} w = [${does-not-exist}]\nr = { x = 1 }\nw = ${r}\nzzz = ${w.x}\nunsupported {"w":{"x":1},"r":{"x":1},"zzz":1} rejected {"w":{"x":1},"r":{"x":1},"zzz":1} {"w":{"x":1},"r":{"x":1},"zzz":1} rejected {"w":{"x":1},"r":{"x":1},"zzz":1} rejected {"w":{"x":1},"r":{"x":1},"zzz":1} d { x = [0] }\nc : ${d} { x : ${c.x} [1, 2] }\nunsupported {"d":{"x":[0]},"c":{"x":[0,1,2]}} rejected — — — {"d":{"x":[0]},"c":{"x":[0,1,2]}} — {"d":{"x":[0]},"c":{"x":[0,1,2]}} d { x = [0] }\ne { z = 5 }\nc : ${d} ${e} { x : ${c.x} [1], w : ${e.z} }\nunsupported {"d":{"x":[0]},"e":{"z":5},"c":{"x":[0,1],"z":5,"w":5}} rejected — — — {"d":{"x":[0]},"e":{"z":5},"c":{"x":[0,1,1],"z":5,"w":5}} — {"d":{"x":[0]},"e":{"z":5},"c":{"x":[0,1],"z":5,"w":5}} mixin { hello = world }\na = ${mixin} { y = ${a.hello} }\nunsupported {"mixin":{"hello":"world"},"a":{"hello":"world","y":"world"}} rejected — — — {"mixin":{"hello":"world"},"a":{"hello":"world","y":"world"}} — {"mixin":{"hello":"world"},"a":{"hello":"world","y":"world"}} x { n = 1 }\ntop = ${x} { n = 2, seen = ${top.n} }\nunsupported {"x":{"n":1},"top":{"n":2,"seen":2}} rejected — — — {"x":{"n":1},"top":{"n":2,"seen":2}} — {"x":{"n":1},"top":{"n":2,"seen":2}} base = {}\nchild = ${base} { foo = ${missing} }\nchild.foo = x\nunsupported {"base":{},"child":{"foo":"x"}} rejected — — — {"base":{},"child":{"foo":"x"}} — {"base":{},"child":{"foo":"x"}} a = { b: 1 } x\nlenient rejects {"a":{"b":1}} rejected rejected rejected rejected rejected rejected a = [ 1, 2 ] x\nlenient rejects {"a":[1,2]} rejected rejected rejected rejected rejected rejected x = [1]suffix\nlenient rejects {"x":[1]} rejected rejected rejected rejected rejected rejected x = {a:1}suffix\nlenient rejects {"x":{"a":1}} rejected rejected rejected rejected rejected rejected x = [1]suffix[2]\nlenient rejects {"x":[1,2]} rejected rejected rejected rejected rejected rejected a = ${?x}42\ndiverges {"a":"42"} {"a":42} {"a":42} {"a":"42"} {"a":42} {"a":42} {"a":42} {"a":"42"} a = 01\nlenient rejects {"a":1} {"a":"01"} {"a":1} {"a":1} {"a":1} {"a":01} rejected a = -.33\nlenient rejects {"a":-0.33} {"a":"-.33"} {"a":-0.33} {"a":-0.33} {"a":-0.33} {"a":-.33} rejected a = -.33e+1\nlenient rejects {"a":-3.3} rejected {"a":-3.3} {"a":-3.3} {"a":-3.3} {"a":-.33e+1} rejected a = 1.\nlenient rejects {"a":1} {"a":"1."} {"a":"1."} {"a":1.0} {"a":"1."} {"a":1.} rejected a = -1.e3\nlenient rejects {"a":-1000} {"a":"-1.e3"} {"a":-1000.0} {"a":-1000.0} {"a":"-1.e3"} {"a":-1.e3} rejected a = -0033\nlenient rejects {"a":-33} {"a":"-0033"} {"a":-33} {"a":-33} {"a":-33} {"a":-0033} rejected a = 9223372036854775808\nb = -9223372036854775809\nc = 123456789012345678901234567890\ndiverges {"a":9223372036854775808,"b":-9223372036854775809,"c":123456789012345678901234567890} {"a":"9223372036854775808","b":"-9223372036854775809","c":"123456789012345678901234567890"} — — — {"a":9223372036854775808,"b":-9223372036854775809,"c":123456789012345678901234567890} — {"a":9223372036854775808,"b":-9223372036854775809,"c":123456789012345678901234567890} a = \"\\u+041\"\nlenient rejects {"a":"A"} — — — {"a":"\\u+041"} — rejected a = \"\\u٠٠٤١\"\nlenient rejects {"a":"A"} — — — {"a":"\\u\u00d9\u00a0\u00d9\u00a0\u00d9\u00a4\u00d9\u00a1"} — rejected a = -foo\nlenient rejects {"a":"-foo"} {"a":"-foo"} {"a":"-foo"} {"a":"-foo"} {"a":"-foo"} {"a":"-foo"} rejected a = 1e\nlenient rejects {"a":"1e"} {"a":"1e"} {"a":"1e"} {"a":"1e"} {"a":"1e"} {"a":"1e"} rejected a = 1.2.3\nlenient rejects {"a":"1.2.3"} — — — {"a":"1.2.3"} — rejected Where the two bars disagree
By section of the specification
One fold per heading of HOCON.md.
- array-and-object-concatenation19 casesJava 17/19 · hocon2 19/19 · pulse-pyhocon 19/19 · py.hocon 19/19 · pyhocon 19/19 · rs.hocon 19/19 · zig-hocon 19/19
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-arrays-concatenate pass pass pass pass pass pass pass arrays can be concatenated with arrays when only non-newline whitespace separates them 002-objects-concatenate pass pass pass pass pass pass pass objects can be concatenated with objects when only non-newline whitespace separates them 003-mixing-array-and-object-is-error pass pass pass pass pass pass pass it is an error to concatenate an array with an object 004-substitution-counts-as-array-for-concatenation pass pass pass pass pass pass pass for purposes of concatenation a substitution that resolves to an array also means an array, so ${x} [ 1, 2 ] with x an array concatenates into one array 005-substitution-counts-as-object-for-concatenation pass pass pass pass pass pass pass for purposes of concatenation a substitution that resolves to an object also means an object, so ${x} { c : 1 } with x an object merges into one object 006-newline-between-values-prevents-concatenation pass pass pass pass pass pass pass newlines between a substitution and an array prevent concatenation, so inside an array they stay two separate elements instead of one concatenated array 007-object-concatenation-merges-and-second-overrides pass pass pass pass pass pass pass for objects, concatenation means merging, and the second object overrides the first on overlapping keys 008-array-cannot-be-field-key pass pass pass pass pass pass pass arrays cannot be field keys; nested inside an object so the rejection comes from the key position, not from the document root 009-object-cannot-be-field-key pass pass pass pass pass pass pass objects cannot be field keys; nested inside an object so the rejection comes from the key position, not from the document root 010-worked-example-object-inheritance pass pass pass pass pass pass pass the spec's worked example: object concatenation with a substitution is a common way to express inheritance 011-worked-example-array-path-append pass pass pass pass pass pass pass the spec's worked example: array concatenation with a substitution is a common way to add to paths 012-worked-example-self-referential-array-concat pass pass pass pass pass pass pass the spec's worked example: a later array definition can concatenate a substitution referring to its own earlier value with a new array 013-newlines-within-value-still-allow-concatenation pass pass pass pass pass pass pass newlines may occur within an array without preventing it from concatenating with a second array on the same line as its closing bracket 014-substitution-object-with-literal-array-is-error pass pass pass pass pass pass pass a substitution that resolves to an object means an object for concatenation, and concatenating an object with an array is an error 015-substitution-array-with-literal-object-is-error pass pass pass pass pass pass pass a substitution that resolves to an array means an array for concatenation, and concatenating an array with an object is an error 016-mixing-object-then-array-is-error pass pass pass pass pass pass pass it is an error to concatenate an object with an array in this order too, not only array then object 017-newline-between-arrays-in-field-value-prevents-concatenation pass pass pass pass pass pass pass a newline between two arrays in a field value prevents concatenation, so the second array begins a new field, and an array cannot be a field key 018-object-with-inner-merge-concatenated-with-substitution fail pass pass pass pass pass pass an object whose field a is itself a merge of an object and a substitution concatenates with a substituted object like any other: the two merge. typesafe/config fails with an internal error (BugOrBroken) because the concatenation is not registered as the merge's parent; the same objects written as literals concatenate finejava: unsupported 019-self-referential-object-concatenation-with-inner-merge fail pass pass pass pass pass pass ${a} in a's own value refers to the earlier a, an object, so the concatenation merges {p: ...} with {q: 1}; the inner p is itself a merge of an object and a substitution. typesafe/config fails with an internal error (BugOrBroken) when the merge inside a concatenation piece resolves, as in 018java: unsupported arrays-without-commas-or-newlines7 casesJava 7/7 · hocon2 7/7 · pulse-pyhocon 6/7 · py.hocon 6/7 · pyhocon 7/7 · rs.hocon 6/7 · zig-hocon 7/7
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-newlines-instead-of-commas pass pass pass pass pass pass pass arrays allow newlines to separate elements instead of commas 002-whitespace-instead-of-comma-concatenates-into-one-element pass pass pass pass pass pass pass the spec's worked example: non-newline whitespace instead of a comma produces concatenation, so this array has one element, the string 1 2 3 4, not four integers 003-newline-separates-two-arrays-into-elements pass pass pass pass pass pass pass the spec's worked example: a newline between two bracketed arrays makes them two separate elements 004-same-line-whitespace-concatenates-two-arrays-into-one-element pass pass pass pass pass pass pass the spec's worked example: whitespace on the same line between two bracketed arrays concatenates them into a single element, the array [1, 2, 3, 4] 005-unquoted-string-concatenation-with-substitutions-in-array pass pass pass pass pass pass pass the spec's worked example: unquoted words and substitutions separated by whitespace concatenate into one string element, and only the comma separates elements 006-two-elements-each-a-substitution-concatenation pass pass pass pass pass pass pass the spec's worked example: whitespace between two substitutions concatenates them into one element, and the comma separates the two concatenations 007-whitespace-never-separates-fields pass pass fail fail pass fail pass non-newline whitespace is never a field separator, so two field definitions cannot be written on one line separated only by spaces commas13 casesJava 13/13 · hocon2 13/13 · pulse-pyhocon 5/13 · py.hocon 12/13 · pyhocon 6/13 · rs.hocon 12/13 · zig-hocon 13/13
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-array-trailing-comma pass pass pass pass pass pass pass the last element in an array may be followed by a single comma, which is ignored: [1,2,3,] and [1,2,3] are the same array 002-array-newline-no-comma pass pass pass pass pass pass pass array elements need not have a comma between them as long as an ASCII newline separates them: [1\n2\n3] and [1,2,3] are the same array 003-array-double-trailing-comma-invalid pass pass fail pass fail pass pass [1,2,3,,] is explicitly called invalid because it has two trailing commas 004-array-initial-comma-invalid pass pass fail pass fail pass pass [,1,2,3] is explicitly called invalid because it has an initial comma 005-array-double-comma-invalid pass pass fail pass fail pass pass [1,,2,3] is explicitly called invalid because it has two commas in a row 006-object-trailing-comma pass pass pass pass pass pass pass these same comma rules apply to fields in objects, so a single trailing comma after the last field is ignored 007-object-newline-no-comma pass pass pass pass pass pass pass fields in objects need not have a comma between them as long as an ASCII newline separates them, same as array elements 008-object-double-comma-invalid pass pass fail pass fail pass pass these same comma rules apply to fields in objects, so two commas in a row between fields is invalid just as in an array 009-fields-on-one-line-need-a-comma pass pass fail fail pass fail pass two fields need a comma or a newline between them; on one line with neither, 1 b becomes a value concatenation and the second = is a stray token 011-trailing-comma-at-implicit-root pass pass pass pass pass pass pass the implicit root object follows the object comma rules, so its last field may carry a single trailing comma 012-leading-comma-at-implicit-root-illegal pass pass fail pass fail pass pass an initial comma is invalid in an object, the implicit root included 013-double-trailing-comma-in-object-illegal pass pass fail pass fail pass pass the array comma rules apply to object fields: two trailing commas are invalid 014-leading-comma-in-object-illegal pass pass fail pass fail pass pass the array comma rules apply to object fields: an initial comma is invalid comments14 casesJava 14/14 · hocon2 14/14 · pulse-pyhocon 14/14 · py.hocon 14/14 · pyhocon 14/14 · rs.hocon 14/14 · zig-hocon 14/14
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-hash-comment pass pass pass pass pass pass pass anything between # and the next newline is a comment and ignored 002-double-slash-comment pass pass pass pass pass pass pass anything between // and the next newline is a comment and ignored 003-whole-line-comment pass pass pass pass pass pass pass a comment may occupy an entire line by itself, ignored just like a trailing one 004-comment-stops-at-newline pass pass pass pass pass pass pass a comment runs only to the next newline, so the following line is parsed normally 005-hash-inside-quoted-string-is-not-a-comment pass pass pass pass pass pass pass a # inside a quoted string is not treated as starting a comment 006-double-slash-inside-quoted-string-is-not-a-comment pass pass pass pass pass pass pass a // inside a quoted string is not treated as starting a comment 007-comment-between-separator-and-value pass pass pass pass pass pass pass a comment runs to the end of its line and is then over; the value may follow on the next line just as it may after a bare = 008-comment-then-blank-line-then-field pass pass pass pass pass pass pass a comment separated from the following field by a blank line is ignored like any other comment 009-comment-inside-object pass pass pass pass pass pass pass comments and blank lines inside an object body are ignored 010-comments-inside-array pass pass pass pass pass pass pass comments and blank lines between array elements are ignored, and do not join or separate elements 011-comment-before-values-of-every-type pass pass pass pass pass pass pass a comment before a field is ignored whatever type its value has 012-comment-before-explicit-root-object pass pass pass pass pass pass pass a comment and a blank line before an explicit root object are skipped before the implicit-braces rule looks at the first token; the spec's wording ("does not begin with") is loose here and this row records the consensus reading, as omit-root-braces/009 does 013-line-comment-after-array pass pass pass pass pass pass pass a // comment after an array is not text in a concatenation; the value is just the array 014-hash-comment-after-array-without-space pass pass pass pass pass pass pass a # comment directly after ] is a comment, not text in a concatenation concatenation-whitespace-and-substitutions9 casesJava 7/9 · hocon2 9/9 · pulse-pyhocon 8/9 · py.hocon 9/9 · pyhocon 7/9 · rs.hocon 9/9 · zig-hocon 9/9
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-unquoted-whitespace-between-substitutions-is-significant-for-strings pass pass pass pass pass pass pass when the substitutions turn out to be strings, the unquoted whitespace between them is significant and survives into the concatenated string 002-quoted-whitespace-between-substitutions-is-error pass pass pass pass pass pass pass between substitutions that resolve to objects, unquoted whitespace is ignored but quoted whitespace should be an error 003-unquoted-whitespace-between-object-and-list-substitutions-is-ignored pass pass pass pass pass pass pass unquoted whitespace between substitutions that resolve to objects or lists must be ignored, so they merge and concatenate as if adjacent 004-tab-between-list-substitutions-is-ignored pass pass pass pass pass pass pass whitespace between two list substitutions is ignored, tabs included, and the lists concatenate 005-nonbreaking-space-between-list-substitutions-is-ignored pass pass pass pass fail pass pass U+00A0 is a Zs space and so whitespace in HOCON; between two list substitutions it is ignored (lightbend/config#862 review) 006-bom-between-list-substitutions-is-ignored pass pass fail pass fail pass pass the BOM must be treated as whitespace, so between two list substitutions it is ignored 007-zero-width-space-between-list-substitutions-is-error fail pass pass pass pass pass pass U+200B is format (Cf), not a space separator, so it is text, and text between two list substitutions is invalid. typesafe/config drops it and concatenates the lists; lightbend/config#862 rejects itjava: lenient 008-list-then-undefined-optional-is-the-list pass pass pass pass pass pass pass an undefined optional substitution in a concatenation with an array becomes an empty array 009-undefined-optional-then-list-is-the-list fail pass pass pass pass pass pass the mirror of 008: an undefined optional substitution before an array becomes an empty array too. typesafe/config keeps the whitespace before the list as text and rejects it, while accepting 008 (noted on lightbend/config#862)java: unsupported config-and-file-merging4 casesJava 4/4 · hocon2 4/4 · pulse-pyhocon 4/4 · py.hocon 4/4 · pyhocon 2/4 · rs.hocon 4/4 · zig-hocon 4/4
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 002-file-merge-is-recursive-and-later-file-wins pass pass pass pass pass pass pass merging objects loaded from different files is exactly the same behavior as merging duplicate fields in the same file: object-valued fields present in both files merge recursively and a non-object field takes the value from the later file 003-non-object-between-files-hides-earlier-object pass pass pass pass fail pass pass the spec's own example: an intermediate non-object value hides earlier object values because merging is done in pairs, so when 42 is paired with { y : 2 } it simply wins and loses all information about what it overrode, and the two objects are never merged 004-adjacent-objects-across-files-merge pass pass pass pass fail pass pass the spec's re-ordered example: with the non-object 42 moved to the lowest priority the two objects are adjacent, so they merge to { a : { x : 1, y : 2 } } 005-null-clears-included-object pass pass pass pass pass pass pass setting an object to null and then setting it back to a new object clears it and starts over, which is how to get rid of default fallback values from another file that you don't want duplicate-keys-and-object-merging28 casesJava 20/28 · hocon2 28/28 · pulse-pyhocon 21/28 · py.hocon 27/28 · pyhocon 24/28 · rs.hocon 27/28 · zig-hocon 28/28
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-later-scalar-wins pass pass pass pass pass pass pass for a non-object value the later assignment simply replaces the earlier one 002-objects-merge pass pass pass pass pass pass pass two objects under the same key merge key-by-key, they do not replace 003-scalar-breaks-the-merge pass pass pass pass fail pass pass merging is always done two values at a time: the scalar replaces the first object (non-object always wins), then the second object replaces the scalar (no merging, object is the new value), so the two objects never see each other 004-merge-scalar-field-later-wins pass pass pass pass pass pass pass for a non-object-valued field present in both merged objects, the field from the second object is used 005-merge-nested-object-recursive pass pass pass pass pass pass pass for an object-valued field present in both objects, the object values are recursively merged by the same rules 006-null-prevents-merge pass pass pass pass pass pass pass the spec's own example: an intermediate null is a non-object, so it replaces the first object and is in turn replaced by the second object with no merging, which prevents the two objects from ever seeing each other 007-merge-keeps-fields-from-either-side pass pass pass pass pass pass pass fields present in only one of the two objects are added to the merged object as they are, whether the first or the second side has them and whether the value is a scalar or an object with nothing to merge against 008-arrays-are-not-objects pass pass pass pass pass pass pass only two object values merge; an array is not an object, so a later array replaces an earlier array (no concatenation) and replaces an earlier object (no merge) 009-later-null-overrides pass pass pass pass pass pass pass null is a value like any other, so a later null overrides an earlier scalar or object: the key stays, set to null, rather than being removed or the assignment skipped 010-null-in-referenced-key-history-does-not-block-merge fail pass fail pass pass pass pass the null at r only stops merging at r; ${r} is the object {x:1}, and a later object merges with the earlier a, so helper stays. typesafe/config carries r's null through the substitution and drops helper (lightbend/config#864)java: diverges 011-nested-null-in-referenced-object-does-not-block-merge fail pass fail pass pass pass pass the same rule one level down: the null in v.nested's history belongs to v.nested, so a.nested still merges with the substituted {x:1} (lightbend/config#864)java: diverges 012-reference-to-merged-field-sees-the-same-value fail pass pass pass pass pass pass a substitution sees the resolved value of the field it names, so o24bbd equals a.nested, which keeps helper. The key name is chosen to sort before a in typesafe/config's root HashMap, the order in which the first version of lightbend/config#864 answered differently for the twojava: diverges 013-top-level-null-before-object-with-substitution fail pass fail pass pass pass pass the null belongs to v's history even when v's object is still unresolved, so a merges and keeps helper (lightbend/config#864)java: diverges 014-own-null-still-blocks-merge-with-referenced-object pass pass pass pass pass pass pass the receiver's own intermediate null still breaks the merge: a is {helper:0}, then null, then the substituted object, so helper is gone 015-earlier-receiver-value-is-evaluated-once-merged fail pass fail pass pass pass pass ${r} is an object, so it merges with the earlier a and the ${missing} inside it is evaluated: an error. typesafe/config 1.4.5 agreed; 1.4.9 drops the earlier a and yields {y:1}, and lightbend/config#864 restores the errorjava: lenient 016-null-deeper-in-a-merge-stack-stays-at-its-key fail pass fail pass fail pass pass v's value is a merge whose top is an object and whose null sits further down, under nested; that null is still v.nested's merge instruction, so a.nested keeps helper (lightbend/config#864 review, round 3)java: diverges 017-lookup-into-nested-null-merges fail pass pass pass pass pass pass two nested nulls each break only their own key's merge, and a lookup of a.n.d sees the final {x:1}; typesafe/config throws BugOrBroken in this key order (reported on lightbend/config#864, same on main)java: unsupported 018-object-then-empty-object pass pass pass pass pass pass pass two objects merge, so the empty later object keeps x; replacement would have given {} 019-duplicate-keys-merge-inside-a-list-element pass pass pass pass pass pass pass an object that is a list element follows the same duplicate-key rules: the later value, here resolved from a substitution, wins 020-objects-and-substitutions-alternate pass pass fail fail pass fail pass four values for a, two of them substitutions that resolve to objects: all four merge in order 021-later-object-replaces-earlier-concatenation pass pass pass pass fail pass pass a string concatenation is a non-object value, so the later object replaces it rather than merging with it 022-scalar-then-empty-object-then-object pass pass pass pass fail pass pass the number cuts the merge off from anything before it; the empty object after it and the object after that still merge, and the empty one contributes nothing 023-empty-object-then-substitution pass pass pass pass pass pass pass an empty object followed by a substitution that resolves to an object merges into that object; the empty layer contributes nothing 024-merge-stack-nested-inside-a-merge-stack pass pass fail pass pass pass pass the object layer of a's merge contains a field b that is itself a merge of a substitution and an object; both levels merge 025-lookup-into-object-that-replaced-a-list fail pass pass pass pass pass pass the object replaces the earlier list, so a.m exists and p is 1 whatever order the keys are resolved in. typesafe/config fails when the reader p is resolved before a (it iterates p before a); the same file with the reader named zzz resolvesjava: unsupported 026-missing-key-in-object-that-replaced-a-list pass pass pass pass pass pass pass the same shape with a key the object does not have: the list below it is not consulted, a.b is undefined and the optional p is not created 027-nested-object-then-scalar-inside-merge pass pass pass pass pass pass pass inside a merge the same rules apply per field: b is an object in the first and a number in the second, so the later value replaces it 028-null-in-second-object-wins pass pass pass pass pass pass pass null is a non-object value, so the second object's null is used for b rather than dropped duration-format9 casesJava 9/9 · hocon2 9/9 · pulse-pyhocon 9/9 · py.hocon 9/9 · pyhocon 2/9 · rs.hocon 9/9 · zig-hocon 9/9
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-bare-number-is-milliseconds pass pass pass pass pass pass pass bare numbers are taken to be in milliseconds already 002-uppercase-unit-is-illegal pass pass pass pass pass pass pass the supported unit strings for duration are case-sensitive and must be lowercase, so an uppercase unit is not one of the supported strings 003-nanoseconds-spellings pass pass pass pass fail pass pass exactly ns, nano, nanos, nanosecond, nanoseconds are the supported unit strings for nanoseconds 004-microseconds-spellings pass pass pass pass fail pass pass exactly us, micro, micros, microsecond, microseconds are the supported unit strings for microseconds 005-milliseconds-spellings pass pass pass pass fail pass pass exactly ms, milli, millis, millisecond, milliseconds are the supported unit strings for milliseconds 006-seconds-spellings pass pass pass pass fail pass pass exactly s, second, seconds are the supported unit strings for seconds 007-minutes-spellings pass pass pass pass fail pass pass exactly m, minute, minutes are the supported unit strings for minutes 008-hours-spellings pass pass pass pass fail pass pass exactly h, hour, hours are the supported unit strings for hours 009-days-spellings pass pass pass pass fail pass pass exactly d, day, days are the supported unit strings for days in the duration format include-file-formats23 casesJava 19/23 · hocon2 3/23 · pulse-pyhocon 3/23 · py.hocon 13/23 · pyhocon 3/23 · rs.hocon 14/23 · zig-hocon 23/23
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-basename-loads-every-extension pass fail fail pass fail pass pass a name without an extension is a basename: all of base.properties, base.json and base.conf exist, so all three are loaded and merged, properties first and HOCON last, so the .conf value wins where they overlap 002-basename-with-only-conf pass fail fail pass fail pass pass a basename with only base.conf present loads just that file 003-basename-with-only-properties pass fail fail pass fail pass pass a basename with only base.properties present loads it as Java properties, whose values are all strings 004-basename-with-no-file-is-ignored pass pass pass pass pass pass pass a basename that exists with no extension at all is a missing include, and a missing include is ignored 005-required-basename-with-no-file-is-error pass pass pass pass pass pass pass required() makes the missing basename an error 006-json-extension-means-json-syntax fail fail fail fail fail fail pass a file named .json is in JSON format, and an unquoted value is not JSON. typesafe/config parses a .json named outright with the HOCON parser, so the file is accepted; the same file found through a basename is rejectedjava: lenient 007-properties-extension-means-properties-syntax fail fail fail pass fail pass pass a file named .properties is in Java properties format, where the first = separates key from value and values are strings. typesafe/config parses a .properties named outright with the HOCON parser and fails on the second =; the same file found through a basename is read as propertiesjava: unsupported 008-properties-keys-are-paths pass fail fail pass fail pass pass in a properties file a dotted key is a path, so a.b and a.c build an object at a; the spec's Java properties mapping says the object must always win when a key is both a value and a parent, whatever the order; values are strings 010-json-file-with-every-value-type pass fail fail pass fail pass pass a .json file found through a basename is parsed as JSON and contributes its object 011-json-rejects-unquoted-values pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; an unquoted value is a HOCON extension, not JSON 012-json-rejects-substitutions pass fail fail pass fail pass pass the .json file is found through a basename, so it is parsed as JSON; ${} is a HOCON extension, not JSON 013-json-rejects-trailing-comma pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; JSON has no trailing commas 014-json-rejects-duplicate-keys pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; the spec's own footing is that duplicate keys are invalid JSON (L194-195), so the second a is an error rather than an override 015-json-array-needs-commas pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; JSON separates array elements with commas only; whitespace does not concatenate or separate 016-json-rejects-empty-document pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; an empty document is not JSON, although it is an empty HOCON object 017-json-rejects-scalar-root pass fail fail pass fail pass pass the .json file is found through a basename, so it is parsed as JSON; a JSON document included here must be an object; a bare number is neither an object nor valid as an included root 019-json-key-must-be-a-string pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; a JSON key is a quoted string; a number cannot be a key 020-json-newline-is-not-a-separator pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; in JSON a newline between fields does not stand in for the comma 022-json-dotted-key-is-one-key pass fail fail pass fail pass pass the .json file is found through a basename, so it is parsed as JSON; a JSON key is a plain string, not a path expression, so a.b stays a single key 023-json-leading-zero-illegal fail fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; JSON forbids a leading zero on a number. typesafe/config reads 01 as 1, the same leniency it shows in HOCONjava: lenient 024-unknown-extension-is-not-a-basename fail pass pass fail pass pass pass x.txt has an extension, so it is not a basename to probe; it is a file adjacent to the including one and of the same type. The spec does not define what counts as an extension, and this row reads any dotted suffix as one. typesafe/config probes every name that does not end in .conf, .json or .properties as a basename and finds nothingjava: diverges 025-json-is-parsed-after-properties pass fail fail pass fail pass pass base.properties and base.json both exist; JSON is parsed next-to-last, after properties, so its value for a wins 026-conf-is-parsed-after-json pass fail fail pass fail pass pass base.json and base.conf both exist; HOCON is parsed last, after JSON, so its value for a wins include-locating5 casesJava 5/5 · hocon2 4/5 · pulse-pyhocon 4/5 · py.hocon 5/5 · pyhocon 4/5 · rs.hocon 5/5 · zig-hocon 5/5
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-include-string-that-is-a-url-is-a-url pass pass pass pass pass pass pass a quoted include name with a known URL scheme is a URL, not a file adjacent to this one. With nothing behind the URL the include is ignored, so a parser that read the string as a filename would also get {}; the row records the rule, it cannot distinguish the two readings 002-nested-include-is-relative-to-the-including-file pass pass pass pass pass pass pass inner.conf includes x.conf, which is found beside inner.conf, not beside main.conf and not in the process's working directory 003-plain-name-is-a-file-beside-the-including-one pass pass pass pass pass pass pass x.conf is not a URL, so it names a file adjacent to main.conf 004-absolute-path-stays-absolute pass pass pass pass pass pass pass an absolute path is loaded as such, so the file of the same name beside main.conf is not it; the absolute file does not exist and the include is ignored 005-file-argument-is-not-relative-to-the-including-file pass fail fail pass fail pass pass a file() argument is not interpreted relative to the including file, so the marker beside sub/inner.conf is not loaded; nothing of that name exists elsewhere and the include is ignored include-merging15 casesJava 14/15 · hocon2 15/15 · pulse-pyhocon 11/15 · py.hocon 12/15 · pyhocon 15/15 · rs.hocon 12/15 · zig-hocon 15/15
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-included-keys-merge-in-place pass pass pass pass pass pass pass the keys from the included object are conceptually substituted for the include statement in the including file, so the included field lands between the fields around it and merges by the usual duplicate-key rules 002-included-conf-array-root-is-error pass pass pass pass pass pass pass an included file must contain an object, not an array; if it contains an array as the root value it is invalid and an error should be generated 003-included-json-array-root-is-error pass pass pass pass pass pass pass both JSON and HOCON allow arrays as root values in a document, but an included file must contain an object, so a JSON file with an array root is an error when included 004-include-inside-nested-object pass pass pass pass pass pass pass the keys from the included root object are conceptually substituted for the include statement in the including file, so an include inside a nested object contributes its keys to that object 005-included-scalar-overrides-earlier-key pass pass pass pass pass pass pass if a key in the included object occurred prior to the include statement in the including object, the included key's value overrides the earlier value, exactly as with duplicate keys found in a single file 006-included-object-merges-with-earlier-object pass pass pass pass pass pass pass if a key in the included object occurred prior to the include statement and both values are objects, the included value merges with the earlier value, exactly as with duplicate keys found in a single file 007-included-scalar-replaces-earlier-object pass pass pass pass pass pass pass an included key's value overrides the earlier value exactly as with duplicate keys in a single file, so an included non-object replaces an earlier object rather than merging with it 008-later-key-overrides-included-key pass pass pass pass pass pass pass if the including file repeats a key from an earlier-included object, the including file's value overrides the one from the included file 009-later-object-merges-with-included-object pass pass pass pass pass pass pass if the including file repeats a key from an earlier-included object and both values are objects, the including file's value merges with the one from the included file 010-include-inside-an-object-inside-a-list pass pass pass pass pass pass pass an include statement may stand wherever a field may, including in an object that is a list element 011-include-inside-a-list-with-a-substitution fail pass pass pass pass pass pass the included file may contain substitutions like any other; typesafe/config refuses an include nested in a list when the included file is not fully resolvedjava: unsupported 012-included-merge-stack-over-earlier-object pass pass fail fail pass fail pass the included file leaves a as an object merged with a substitution; that whole stack merges with the object the including file set earlier 013-included-merge-stack-over-earlier-substitution pass pass fail fail pass fail pass the earlier value is itself a substitution; once everything resolves to objects, all three layers merge 014-included-merge-stack-over-earlier-scalar pass pass fail pass pass pass pass the earlier value is a number, so the included object stack replaces it and merges only within itself 015-included-object-on-top-of-substitution-over-earlier-substitution pass pass fail fail pass fail pass the same as 013 with the included stack the other way up, an object over a substitution; the result does not depend on which layer is on top include-missing-and-required6 casesJava 6/6 · hocon2 6/6 · pulse-pyhocon 6/6 · py.hocon 6/6 · pyhocon 6/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-missing-include-ignored pass pass pass pass pass pass pass by default, if an included file does not exist the include statement is silently ignored, so the keys around it parse as if the line were not there 002-missing-include-is-empty-object pass pass pass pass pass pass pass a missing include is treated as if the included file contained only an empty object, so an object whose only content is that include stays an empty object 003-missing-file-include-ignored pass pass pass pass pass pass pass the silent-ignore default applies to an explicit file() include as well: a file that does not exist contributes nothing and parsing continues 004-required-missing-is-error pass pass pass pass pass pass pass when the included resource name is wrapped with required(), file parsing fails with an error if the resource cannot be resolved 005-required-file-missing-is-error pass pass pass pass pass pass pass required() may also wrap the file() form, and a required file that cannot be resolved is a parse error rather than an empty object 006-required-present-is-included pass pass pass pass pass pass pass required() only makes the include mandatory; a resource that does resolve is included exactly as an unwrapped include would be include-substitution19 casesJava 16/19 · hocon2 15/19 · pulse-pyhocon 11/19 · py.hocon 14/19 · pyhocon 12/19 · rs.hocon 14/19 · zig-hocon 19/19
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-substitution-relativized-to-include-scope pass pass fail pass fail pass pass a substitution in an included file must be fixed up to be relative to the app's configuration root, so ${x} in a file included at key a becomes ${a.x} 002-relativized-path-sees-later-redefinition pass pass fail pass fail pass pass substitution happens after parsing the whole configuration, so the ${x} fixed up to ${a.x} evaluates to the root's later redefinition 42 rather than the included file's 10 003-original-path-tried-as-fallback pass pass pass pass pass pass pass it is not enough to only look up the fixed-up path; when a.x is undefined the original path x is looked up relative to the root of the including configuration 004-relativized-path-tried-before-original pass fail fail pass fail pass pass substitutions in included files are looked up first relative to the root of the included file and only second relative to the root of the including configuration, so a.x wins over x 005-neither-path-defined-is-error pass pass pass pass pass pass pass only the fixed-up path a.x and the original path x are looked up; with neither defined the substitution is undefined and the configuration is invalid 006-optional-substitution-with-neither-path-is-omitted pass pass pass pass pass pass pass an optional substitution in an included file is looked up at the fixed-up path a.x and at the original path x; with neither defined the field is omitted rather than an error 007-nested-include-relativized-through-both-levels pass pass fail pass fail pass pass the fix-up is relative to the app's configuration root, so ${x} in a file included at b inside a file included at a becomes ${a.b.x} 008-original-path-falls-back-to-environment pass fail pass pass pass pass pass an included file may intend to refer to the application's root config, for example to get a value from the environment; the original path CONFORMANCE_HOME is looked up and falls back to the environment variable 009-undefined-optional-at-fixed-up-path-falls-back fail pass pass fail pass fail pass an undefined ${?nope} means common.x.y is never created, so ${x.y} is not found at the fixed-up path and is looked up at the original path x.y. typesafe/config treats the vanished value as a miss that ends the lookup -- except in some root key orders (see 014); lightbend/config#863 falls backjava: unsupported 010-undefined-optional-at-fixed-up-path-with-no-original pass pass pass fail pass fail pass with neither common.x.y nor x.y defined, the required ${x.y} in the included file is unresolved 011-optional-reference-falls-back-too fail pass pass fail pass fail pass the optional form of 009 falls back the same way; typesafe/config leaves a out instead (lightbend/config#863)java: diverges 012-earlier-value-at-fixed-up-path-wins pass fail fail pass fail pass pass an undefined optional that would override keeps the previous value, so common.x.y is 9 and found at the fixed-up path; the original path is not consulted 013-missing-leaf-under-existing-object-falls-back pass pass pass pass pass pass pass the metamorphic twin of 009 without the optional: common.x exists, common.x.y does not, so x.y is looked up at the original path 014-unrelated-key-does-not-change-the-result fail pass pass fail pass fail pass an unrelated key that reads common.x must not change what common.a resolves to; in typesafe/config this input fails while the same input with the key named o24bbd resolves to 0 (lightbend/config#863)java: unsupported 015-undefined-at-both-paths-is-error pass pass fail fail pass fail pass when the original path is itself an undefined optional, neither lookup finds a value and the required ${x.y} is an error 016-substitution-inside-a-concatenation-is-fixed-up pass pass pass pass pass pass pass a substitution that is one piece of a concatenation in the included file is fixed up like a bare one: p.x is not set, so the original path x is used 017-substitution-inside-a-list-is-fixed-up pass pass pass pass pass pass pass a substitution that is a list element in the included file is fixed up as well 018-substitution-in-a-merge-stack-is-fixed-up pass pass fail pass fail pass pass an object merged with a substitution in the included file keeps working after the fix-up, whichever of the two is on top: q is found at p.q 019-null-at-fixed-up-path-is-found pass fail fail pass fail pass pass a.x is present, set to null, so the lookup at the fixed-up path succeeds and the original path x is not consulted include-syntax15 casesJava 15/15 · hocon2 13/15 · pulse-pyhocon 12/15 · py.hocon 13/15 · pyhocon 13/15 · rs.hocon 14/15 · zig-hocon 15/15
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-include-later-in-key-is-literal pass pass pass pass pass pass pass unquoted include has no special meaning if it is not the start of a key's path expression; it may appear later in the key, so { foo include : 42 } is equivalent to { "foo include" : 42 } 002-include-as-object-value-is-string pass pass pass pass pass pass pass unquoted include has no special meaning if it is not the start of a key's path expression; as an object value it is the string "include" 003-include-as-array-element-is-string pass pass pass pass pass pass pass unquoted include has no special meaning if it is not the start of a key's path expression; as an array element it is the string "include". the spec's example is the root array [ include ]; it is wrapped in a field here so the row does not depend on array-root support 004-no-concatenation-on-include-argument pass pass fail pass pass pass pass value concatenation is NOT performed on the argument to include; the argument must be a single quoted string, so a second quoted string after the resource name is an error whether or not the first resource exists 005-no-substitution-in-include-argument pass pass pass pass pass pass pass no substitutions are allowed in the argument to include, which must be a single quoted string; ${x} after include is an error even though x is defined, so an implementation that resolves the argument first still fails this row 006-include-argument-must-be-quoted pass pass pass pass pass pass pass if an unquoted include at the start of a key is followed by anything other than a single quoted string or the url()/file()/classpath() syntax, it is invalid and an error should be generated; the argument may not be an unquoted string 007-quoted-include-is-ordinary-key pass pass pass pass pass pass pass only unquoted include is special; quoting it gives a key that starts with the word include, so { "include" : 42 } is an ordinary field named include 008-file-argument-must-be-quoted pass pass pass pass pass pass pass the argument inside file() must be a single quoted string; an unquoted name is a syntax error 009-file-missing-close-parenthesis pass pass pass fail pass pass pass file("...") must be closed; the line ends before the parenthesis 010-required-missing-close-parenthesis pass pass pass fail pass fail pass required(...) must be closed too 011-url-argument-must-be-a-url pass pass pass pass pass pass pass the string inside url() is always an absolute URL, with a scheme; x.conf has none, so it is an error, as lightbend/config#887 settled 013-newlines-between-include-and-name pass pass fail pass fail pass pass any amount of whitespace, newlines included, may stand between include and the resource name 014-whitespace-inside-parentheses pass fail pass pass pass pass pass whitespace inside the parentheses, outside the quotes, is allowed 015-include-followed-by-separator-is-not-a-key pass pass fail pass fail pass pass an unquoted include at the start of a key is never a key; followed by a separator instead of a quoted name it is an include with an invalid argument 016-comma-separates-include-from-next-field pass fail pass pass pass pass pass an include stands where a field would, so the usual comma separates it from the field that follows key-value-separator6 casesJava 6/6 · hocon2 6/6 · pulse-pyhocon 6/6 · py.hocon 6/6 · pyhocon 6/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-equals-as-separator pass pass pass pass pass pass pass the = character can be used anywhere JSON allows :, to separate keys from values 002-omitted-separator-before-object pass pass pass pass pass pass pass if a key is followed by {, the : or = may be omitted; the spec's own example is "foo" {} meaning "foo" : {} 003-omitted-separator-unquoted-key pass pass pass pass pass pass pass the separator-omission rule before { applies to unquoted keys too, not just the quoted example in the spec 004-value-on-the-line-after-the-separator pass pass pass pass pass pass pass = stands wherever JSON allows :, and JSON allows any whitespace, newlines included, between the separator and the value 005-separator-cannot-be-omitted-before-array pass pass pass pass pass pass pass the separator may be omitted only before {; a key followed directly by [ is invalid 006-newline-between-key-and-brace pass pass pass pass pass pass pass the separator may be omitted before {, and a newline before the { is whitespace like any other between a key and its value, as JSON allows one before : list-values-from-environment-variables6 casesJava 6/6 · hocon2 1/6 · pulse-pyhocon 3/6 · py.hocon 6/6 · pyhocon 3/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-list-from-indexed-variables pass fail fail pass fail pass pass the [] suffix collects CONFORMANCE_LIST_0, CONFORMANCE_LIST_1, ... until an index is missing, into a list of strings 002-optional-list-with-no-elements-is-undefined pass fail pass pass pass pass pass with no CONFORMANCE_LIST_0 set, an optional ${?X[]} is undefined and the field is not created 003-required-list-with-no-elements-is-error pass pass pass pass pass pass pass with no CONFORMANCE_LIST_0 set, a required ${X[]} is an error 004-collection-stops-at-the-first-gap pass fail fail pass fail pass pass indices are collected from 0 until one is missing, so with _0 and _2 set the list holds only the first 005-elements-are-strings pass fail fail pass fail pass pass an element taken from the environment is a string, so 1 stays "1" 006-config-keys-are-not-collected pass fail pass pass pass pass pass the [] suffix collects environment variables only, so the config key a_0 is not gathered and the optional substitution is undefined multi-line-strings9 casesJava 9/9 · hocon2 9/9 · pulse-pyhocon 6/9 · py.hocon 9/9 · pyhocon 9/9 · rs.hocon 9/9 · zig-hocon 9/9
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-basic-triple-quote pass pass pass pass pass pass pass the three-character sequence """ starts a multi-line string whose unicode characters are used unmodified to create the string value 002-newlines-and-whitespace-preserved pass pass pass pass pass pass pass newlines and whitespace inside a multi-line string receive no special treatment and are kept in the value 003-unicode-escape-not-interpreted pass pass pass pass pass pass pass unlike JSON quoted strings, a triple-quoted string does not interpret escapes, so the six characters \u0041 stay as written rather than becoming A 004-extra-quote-becomes-part-of-string pass pass pass pass pass pass pass any sequence of at least three quotes ends the multi-line string and any extra quotes are part of the string, so HOCON works like Scala's four-character string foo" rather than Python's syntax error 005-five-closing-quotes-two-extra pass pass fail pass pass pass pass a closing run of five quotes still only needs three to end the string, so both extra quotes are appended to the string value 006-unterminated-is-illegal pass pass pass pass pass pass pass a multi-line string opened with a three-character quote sequence that never reaches a closing """ is not a legal value 007-fewer-than-three-quotes-does-not-close pass pass fail pass pass pass pass only a run of at least three quote characters ends the multi-line string, so the single embedded quote pairs around hi do not terminate it early 008-backslash-kept-literally pass pass fail pass pass pass pass a backslash sequence inside a triple-quoted string is used unmodified, not as an escape 009-six-quotes-are-an-empty-string pass pass pass pass pass pass pass the three opening quotes are followed at once by the closing run, so the string is empty numerically-indexed-objects-to-arrays7 casesJava 7/7 · hocon2 3/7 · pulse-pyhocon 3/7 · py.hocon 7/7 · pyhocon 3/7 · rs.hocon 7/7 · zig-hocon 7/7
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-object-with-numeric-keys-stays-object pass pass pass pass pass pass pass the conversion should be done lazily when required to avoid a type error, not eagerly anytime an object has numeric keys, so a bare object with numeric keys stays an object 003-concatenation-converts-numeric-object-to-array pass fail fail pass fail pass pass the conversion should be done in a concatenation when a list is expected and an object with numeric keys is found, so the trailing object concatenates onto the array as elements x, y 004-concatenation-ignores-non-integer-keys pass fail fail pass fail pass pass the conversion should ignore any keys which do not parse as positive integers, so key "foo" is dropped and only "0" contributes to the resulting array 005-empty-object-concatenation-not-converted pass pass pass pass pass pass pass the conversion should not occur if the object is empty, so an empty object cannot satisfy a list-expected concatenation and this is illegal 006-object-without-integer-keys-not-converted pass pass pass pass pass pass pass the conversion should not occur if the object has no keys which parse as positive integers, so this object cannot satisfy a list-expected concatenation and this is illegal 007-sparse-integer-keys-sorted-and-reindexed pass fail fail pass fail pass pass the conversion should sort by the integer value of each key and then build the array; missing indices such as "1" are eliminated rather than left as gaps, so keys "0" and "2" become a two-element array 008-negative-key-ignored-in-conversion pass fail fail pass fail pass pass the conversion ignores any keys which do not parse as positive integers, and "-1" is not a positive integer, so it is dropped and only key "0" contributes omit-root-braces14 casesJava 10/14 · hocon2 12/14 · pulse-pyhocon 14/14 · py.hocon 8/14 · pyhocon 14/14 · rs.hocon 8/14 · zig-hocon 14/14
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-implicit-root-object pass pass pass pass pass pass pass if the file does not begin with [ or {, it is parsed as if enclosed with {} curly braces 002-explicit-root-object-unaffected pass pass pass pass pass pass pass the implicit-wrap rule only applies when the file does not begin with [ or {; a file already beginning with { is parsed as ordinary JSON-style object 003-missing-open-brace-with-close-brace-illegal pass pass pass fail pass fail pass a HOCON file is invalid if it omits the opening { but still has a closing }, since the curly braces must be balanced 004-empty-file pass fail pass pass pass pass pass an empty file does not begin with a square bracket or a curly brace, so it is parsed as if enclosed in {}: the empty object 005-bare-string-root-illegal pass pass pass pass pass pass pass the file does not begin with a bracket or brace, so it is wrapped in {} and becomes a key with no separator and no value 006-array-root-unaffected fail pass pass fail pass fail pass the implicit {} wrap fires only when the file does not begin with a square bracket or curly brace, so a file starting with [ keeps its array root; the include section says so outright -- 'both JSON and HOCON allow arrays as root values in a document' (the reason an included file may not be one). typesafe/config has no array-rooted config at all and rejects the document as LIST rather than object.java: unsupported 008-empty-array-root fail pass pass fail pass fail pass an empty array is still an array root, and the wrap rule looks at the opening bracket, not at whether the document has contentjava: unsupported 009-comment-before-array-root fail pass pass fail pass fail pass the {} wrap fires only for a file that does not begin with a bracket or brace, and a leading comment does not make it one -- both typesafe/config and pyhocon read the first token, not the first byte. the spec's wording is loose here; this row records the consensus readingjava: unsupported 010-blank-line-before-array-root fail pass pass fail pass fail pass leading whitespace does not trigger the {} wrap either, for the same reason as a leading commentjava: unsupported 011-trailing-array-after-root-array pass pass pass pass pass pass pass two arrays side by side concatenate when only non-newline whitespace separates them, which would make this document [1,2,3]; but a document is one array or one object at the root, and nothing in the spec lets a root value be a concatenation of two. typesafe/config, pyhocon and hocon-rs all reject it 012-close-brace-after-value-on-one-line-illegal pass pass pass fail pass fail pass a close brace with no open brace is invalid wherever it stands, on the same line as a value as much as on its own 013-open-brace-never-closed-illegal pass pass pass pass pass pass pass an opening brace with no closing brace is unbalanced 014-comment-only-file pass fail pass pass pass pass pass a file holding only a comment does not begin with a bracket or brace, so it is the empty object 015-bare-number-root-illegal pass pass pass pass pass pass pass the file does not begin with { or [, so it is read as if wrapped in braces, and a bare number there is a key with no value path-expressions20 casesJava 20/20 · hocon2 19/20 · pulse-pyhocon 16/20 · py.hocon 19/20 · pyhocon 13/20 · rs.hocon 19/20 · zig-hocon 20/20
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-quoted-dot-has-no-meaning pass pass pass pass pass pass pass a dot inside a quoted string has no special meaning as a path separator, so a quoted "a.b" key is a single-element path 002-mixed-quoted-and-unquoted-path pass pass fail pass fail pass pass foo.bar."hello.world" is a path with three elements: foo, bar, and hello.world, since unquoted dots are path separators but a quoted dot is not 003-number-then-unquoted-string pass pass pass pass pass pass pass 10.0foo is a number then unquoted string foo, giving the two-element path 10 and 0foo, because a dot inside a number still counts as a path separator 004-unquoted-string-with-dot pass pass pass pass pass pass pass foo10.0 is an unquoted string with a dot in it, giving the two-element path foo10 and 0 005-unquoted-concatenated-with-quoted-number pass pass fail fail fail fail pass foo followed by quoted "10.0" is an unquoted then a quoted string which concatenate, giving a single-element path 006-all-numeric-path pass pass pass pass pass pass pass dots in numbers count as path separators, so 1.2.3 is the three-element path with elements 1, 2, 3 008-value-concatenation-keeps-boolean pass pass pass pass pass pass pass unlike a path expression, an array or element value consisting of the single value true is a value concatenation and retains its character as a boolean 009-empty-path-element-quoted-is-valid pass pass fail pass fail pass pass a path element that is an empty string must be quoted; a."".b is a valid three-element path whose middle element is the empty string 010-consecutive-dots-invalid pass pass pass pass fail pass pass an unquoted empty path element is invalid, so a..b must generate an error 011-path-starting-with-dot-invalid pass pass pass pass fail pass pass a path that starts with a dot is invalid and should generate an error 012-path-ending-with-dot-invalid pass fail pass pass fail pass pass a path that ends with a dot is invalid and should generate an error 015-dotted-path-in-substitution pass pass pass pass pass pass pass path expressions appear in substitutions like ${foo.bar}, where the unquoted dot separates foo and bar into two path elements, so the lookup walks key foo then key bar 016-number-keeps-original-text-in-path pass pass pass pass pass pass pass a number in a path expression must keep its original string representation as it appeared in the file, so 1.10 is the two-element path 1 and 10, not the 1 and 1 a generic number-to-string conversion would give 017-whitespace-concatenation-in-path pass pass pass pass pass pass pass path expressions are syntactically identical to a value concatenation, so the whitespace-separated a b concatenates into one element and the unquoted dot then splits off c, giving the two-element path a b and c 018-quoted-element-in-substitution-path pass pass pass pass pass pass pass path expressions appear in substitutions as well as keys, and inside quoted strings a dot has no special meaning there either, so ${foo."bar.baz"} looks up key foo then the single key bar.baz 019-empty-element-in-substitution-path-invalid pass pass fail pass fail pass pass an unquoted empty path element is invalid in a substitution just as in a key, so ${a..b} must generate an error rather than collapsing to ${a.b} (a.b is defined so a lenient implementation would produce a value and fail) 020-negative-leading-dot-key-is-a-path pass pass pass pass pass pass pass a dot in a key separates path elements whether or not the text around it would be a number, so -.33 names - then 33; numbers in paths keep their original text 021-quoted-dot-in-substitution-path-is-literal pass pass pass pass pass pass pass inside quotes a dot is not a separator, so ${"b.c"} names one field called b.c, which does not exist; it does not reach b's field c 022-keyword-in-substitution-path-is-a-string pass pass pass pass pass pass pass a path expression is always a string, so true in a substitution names the key true rather than a boolean 023-substitution-in-dotted-key-illegal pass pass pass pass pass pass pass a key is a path expression, and a path expression may not contain a substitution, not even as a later element of a dotted key paths-as-keys11 casesJava 10/11 · hocon2 10/11 · pulse-pyhocon 9/11 · py.hocon 11/11 · pyhocon 9/11 · rs.hocon 11/11 · zig-hocon 11/11
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-two-element-path-expands pass pass pass pass pass pass pass a key that is a multi-element path expands to a nested object per element, the last element combined with the value becomes a field in the most-nested object: foo.bar:42 is equivalent to foo{bar:42} 002-three-element-path-expands pass pass pass pass pass pass pass a three-element path key expands through two levels of nesting: foo.bar.baz:42 is equivalent to foo{bar{baz:42}} 003-merge-sibling-paths pass pass pass pass pass pass pass the objects created by expanding path keys are merged in the usual way, so a.x:42, a.y:43 is equivalent to a{x:42,y:43} 004-whitespace-in-key pass pass pass pass pass pass pass because path expressions work like value concatenations, whitespace is allowed in keys: a b c:42 is equivalent to "a b c":42 005-unquoted-true-key-becomes-string pass pass pass pass pass pass pass path expressions are always converted to strings, so the unquoted boolean-looking key true:42 is "true":42 006-unquoted-number-key-becomes-string pass pass pass pass pass pass pass path expressions are always converted to strings, so the unquoted numeric key 3:42 is "3":42 007-decimal-key-splits-on-dot pass pass pass pass pass pass pass a dot in an unquoted key is a path separator even when it looks like a decimal number, so 3.14:42 is "3":{"14":42} 008-include-cannot-begin-key fail pass fail pass fail pass pass the unquoted string include may not begin a path expression in a key: where an object key is expected, include is not read as a key at all and must be followed by a quoted resource name, so include.foo is an error. typesafe/config recognises the keyword only as a bare include token -- include.foo tokenizes as one unquoted string, so it is accepted as the path include.foo, while include = 42 is rejected under the very rule the spec states.java: lenient 009-include-not-at-start-is-ordinary pass fail fail pass fail pass pass only the unquoted string include that begins a path expression in a key is special: include later in a key (foo.include) and a quoted "include" beginning one are ordinary path elements 010-path-key-merges-with-brace-object pass pass pass pass pass pass pass the object created by expanding a path key is merged in the usual way with an object written in braces at the same key, whichever form comes first: a{x:1}, a.y:2 and b.x:1, b{y:2} are both equivalent to {x:1,y:2} 011-whitespace-around-a-dot-stays-in-the-elements pass pass pass pass pass pass pass whitespace inside a key is part of the key, and a dot splits the key into elements, so the elements here are 'a ' and ' b' with their spaces period-format7 casesJava 7/7 · hocon2 7/7 · pulse-pyhocon 7/7 · py.hocon 7/7 · pyhocon 3/7 · rs.hocon 7/7 · zig-hocon 7/7
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-bare-number-is-days pass pass pass pass pass pass pass for getPeriod(), bare numbers are taken to be in days, unlike getDuration() where bare numbers are milliseconds 002-uppercase-unit-is-illegal pass pass pass pass pass pass pass the supported unit strings for period are case-sensitive and must be lowercase, so an uppercase unit is not one of the supported strings 003-days-spellings pass pass pass pass fail pass pass exactly d, day, days are the supported unit strings for days in the period format 004-weeks-spellings pass pass pass pass fail pass pass exactly w, week, weeks are the supported unit strings for weeks 005-months-spellings pass pass pass pass fail pass pass exactly m, mo, month, months are the supported unit strings for months 006-years-spellings pass pass pass pass pass pass pass exactly y, year, years are the supported unit strings for years 007-month-m-ambiguous-with-duration-minutes pass pass pass pass fail pass pass the spec notes that getTemporal() callers should prefer mo over m for months, since m is also the duration unit for minutes and getTemporal() may return either a Duration or a Period plus-equals-field-separator12 casesJava 6/12 · hocon2 6/12 · pulse-pyhocon 0/12 · py.hocon 8/12 · pyhocon 0/12 · rs.hocon 8/12 · zig-hocon 12/12
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-plus-equals-desugars-to-optional-self-ref-array pass pass fail pass fail pass pass a += b transforms into a = ${?a} [b], and because the fallback is optional (${?a} not ${a}) this is legal even as the first mention of a in the file 002-plus-equals-appends-to-existing-array pass pass fail pass fail pass pass += appends an element to a previous array 003-plus-equals-errors-on-non-array-previous-value pass pass fail pass fail pass pass if the previous value was not an array, += results in an error just as the long form a = ${?a} [b] would with a non-array a 004-plus-equals-twice pass pass fail pass fail pass pass each += appends to the value the previous line left, so two of them append two elements 005-plus-equals-in-nested-object-with-substitution pass fail fail pass fail pass pass inside an object, += desugars to a self-reference by the field's full path o.a; the appended element may itself be a substitution 006-plus-equals-inside-a-list-element fail pass fail pass fail pass pass b has no previous value, so b += 1 is b = ${?b} [1] with the optional part undefined: [1]. typesafe/config refuses += inside a list because its desugared path cannot name a list elementjava: unsupported 007-plus-equals-under-a-dotted-key pass fail fail pass fail pass pass the self-reference += desugars to uses the full path of the field, o.p.a, even when the enclosing key was written as the path o.p 008-plus-equals-with-an-object-containing-a-merge fail pass fail pass fail pass pass l += v is l = ${?l} [v]; the appended object holds a merge of an object and a substitution, which resolves inside the new list. typesafe/config fails with an internal error (BugOrBroken) for the same reason as array-and-object-concatenation/018java: unsupported 009-appends-through-object-concatenation fail fail fail fail fail fail pass top.arr += four is top.arr = ${?top.arr} [four], and ${x} { ... } is a merge, so the self-reference looks back at the overridden x.arr, as it does with top : ${x} and top { arr += four } written as two fields; typesafe/config drops the inherited element (lightbend/config#608, fix proposed in #882)java: diverges 010-appends-through-chained-object-concatenations fail fail fail fail fail fail pass each += looks back through its own concatenation, so y.l appends to x.l and z.l to the resolved y.l; typesafe/config keeps only the last element at each stepjava: diverges 011-appends-deep-inside-object-concatenation fail fail fail fail fail fail pass the += is three objects down, top.a.b.c = ${?top.a.b.c} [2], and the merge reaches it: the self-reference looks back at x.a.b.c; typesafe/config drops itjava: diverges 012-appends-to-a-list-defined-in-object-concatenation fail fail fail fail fail fail pass y.l += 3 overrides the y.l that the earlier concatenation defines, so its self-reference looks back at [1, 2]; typesafe/config yields [3] (lightbend/config#375)java: diverges self-referential-examples33 casesJava 26/33 · hocon2 26/33 · pulse-pyhocon 21/33 · py.hocon 23/33 · pyhocon 27/33 · rs.hocon 23/33 · zig-hocon 33/33
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-isolated-self-reference-is-an-error pass pass pass pass pass pass pass in isolation, with no merges involved, a self-referential field is an error because the substitution cannot be resolved 002-self-reference-resolves-to-earlier-merged-value pass pass pass pass pass pass pass when foo:${foo} is merged with an earlier value for foo, the self-reference resolves to that overridden value, so foo ends up { a : 1 } 003-self-reference-before-any-value-is-undefined pass pass fail fail pass fail pass if the self-referential foo:${foo} comes before foo has any value, the reference is undefined, exactly as if it named a path not found in the document, so it is an error even though foo is given a value afterward 004-optional-self-reference-disappears-silently pass pass pass pass pass pass pass because the self-reference error is treated as undefined rather than an intractable cycle, the optional syntax foo:${?foo} makes the field disappear silently instead of erroring 005-substitution-hidden-by-later-non-mergeable-value-is-never-evaluated pass pass pass pass pass pass pass if a substitution is hidden by a later non-object value that could not be merged with it, it is never evaluated and no error is reported, no matter what it would have resolved to; foo ends up 42 006-self-reference-cycle-ignored-when-overridden-by-literal pass pass pass pass pass pass pass the same hiding rule applies to a self-reference cycle: once overridden by a non-mergeable literal, the initial foo:${foo} must simply be ignored, so foo ends up 42 with no error 007-self-reference-in-path-expression-resolves-to-value-below pass pass pass pass pass pass pass a self-reference resolves to the value below it even as part of a path expression, so ${foo.a} refers to { c : 1 } rather than 2, and the final merge is { a : 2, c : 1 } 008-object-may-refer-to-sibling-path-within-itself pass pass pass pass pass pass pass an implementation must allow an object to refer to a path within itself without treating it as a cycle, by resolving only the referenced field rather than recursing the whole enclosing object; bar.baz ends up 42 009-non-cycling-reference-looks-forward-across-merge pass pass pass pass pass pass pass because there is no inherent cycle here, the substitution must look forward including the field's own later merges, so bar.baz ends up 43 once foo is overridden to 43 010-mutually-referring-objects-are-not-self-referential pass pass pass pass pass pass pass mutually-referring objects should work and are not self-referential, so they look forward: bar.a ends up 4 and foo.c ends up 3 011-optional-self-reference-in-concatenation-looks-back pass pass pass pass pass pass pass an optional self-reference in a value concatenation has to look back to an undefined a, so a ends up "foo" rather than "foofoo" 012-mutual-non-self-cycle-is-unresolvable pass pass pass pass pass pass pass the spec gives bar:${foo}, foo:${bar} as an example that is not possible to resolve, since neither lazy evaluation nor looking backward breaks the cycle and neither field is optional 013-multi-step-cycle-is-invalid pass pass pass pass pass pass pass a multi-step loop across three fields (a:${b}, b:${c}, c:${a}) must also be detected as invalid, not just a direct two-field cycle 014-order-dependent-resolution-must-agree-on-one-value pass pass pass pass pass pass pass the order a and b resolve in is undefined: both 1, both 2 or an error are all allowed, but a and b must agree (a=1,b=2 is not permitted) 015-list-hidden-by-substituted-object-is-never-evaluated fail pass pass fail pass fail pass a list merges with nothing, so any later value hides it and it is never evaluated; that the hiding object arrives through ${replacement} changes nothing, since the substitution is required and so either resolves or fails on its own. typesafe/config evaluates the hidden list (lightbend/config#865)java: unsupported 016-object-hidden-by-substituted-scalar-is-never-evaluated pass pass pass pass fail pass pass ${replacement} resolves to 42, a non-object, so the earlier object is hidden and never evaluated, as with the literal in 005 (typesafe/config 1.4.5 evaluated it) 017-object-hidden-by-substituted-concatenation-is-never-evaluated pass pass pass pass fail pass pass a string concatenation is a non-object too, so it hides the earlier object (typesafe/config 1.4.5 evaluated it) 018-object-merged-with-substituted-object-is-evaluated pass pass fail pass pass pass pass a substituted object merges with the earlier object, so its fields are part of the result and ${does-not-exist} is an error 019-list-hidden-by-literal-scalar-is-never-evaluated pass pass pass pass pass pass pass the literal counterpart of 015: a literal number could not be merged with the list, so the list is never evaluated and there is no error 020-object-hidden-by-literal-list-is-never-evaluated pass pass pass pass pass pass pass a literal list is a non-object value, so it hides the earlier object and ${does-not-exist} is never evaluated 021-hidden-list-does-not-depend-on-key-order fail pass pass fail pass fail pass a reader of w.x must not change whether the hidden list is evaluated; only objects merge, so the object hides the list whatever else refers to it. typesafe/config fails here, yet resolves the same file with the reader named o24bbd (lightbend/config#865)java: unsupported 022-undefined-optional-does-not-hide pass pass fail pass pass pass pass an undefined optional that would override leaves the previous value in place, so the list is the result and ${does-not-exist} is evaluated: an error 023-append-does-not-hide pass pass fail pass fail pass pass w += 1 is w = ${?w} [1], which reads the earlier list, so ${does-not-exist} is evaluated: an error 024-substitution-target-is-fetched-lazily pass pass pass pass pass pass pass resolving ${b.c} fetches only b.c; resolving all of b first would reach ${a} and report a cycle that is not there 025-optional-two-field-cycle-is-undefined pass pass fail pass fail pass pass a genuine cycle is an error for required substitutions; with the optional syntax it is a missing value, so neither field is created 026-memoized-by-instance-not-by-path pass pass pass pass pass pass pass the text ${a} appears twice and resolves differently: to x in a's own self-reference and to the final xy in b; memoizing by path instead of by instance would give b the wrong value 027-self-reference-inside-object-concatenation fail fail fail fail pass fail pass c.x is self-referential, and ${d} { ... } merges the object over d, so ${c.x} looks back at the overridden d.x, as with c : ${d} and c { x : ${c.x} [1, 2] } written as two fields; typesafe/config reports a cyclejava: unsupported 028-self-reference-through-two-concatenated-substitutions fail fail fail fail fail fail pass three objects merged in one value, the last overriding both: ${c.x} looks back at what ${d} ${e} gives c.x, and ${e.z} is an ordinary forward reference; typesafe/config reports a cyclejava: unsupported 029-concatenated-object-refers-to-its-mixin fail fail fail fail pass fail pass an object with a substitution inside it is not self-referential, so ${a.hello} looks forward and finds the field the merged ${mixin} gives a; typesafe/config reports a cycle (lightbend/config#586)java: unsupported 030-concatenated-object-sees-its-own-field fail fail fail fail pass fail pass ${top.n} is not self-referential, so it looks forward, including at the field being defined: the merged top.n, 2, not x's 1; typesafe/config reports a cyclejava: unsupported 031-field-hidden-by-a-substituted-object pass fail fail fail pass fail pass the object ${x} gives merges onto the earlier p, and its a = 42 hides the earlier a, a non-object over a substitution, so ${y} is never evaluated (lightbend/config#838) 032-field-hidden-inside-object-concatenation fail fail fail fail pass fail pass ${base} { ... } is a merge, and child.foo = x merges onto it and hides its foo, so ${missing} is never evaluated, as with child = ${base} and child { foo = ${missing} } written as two fields; typesafe/config evaluates it (lightbend/config#812)java: unsupported 033-object-below-a-substitution-looks-forward pass fail fail fail fail fail pass ${bar.foo} is not self-referential, so it looks forward at the final bar.foo, which the object ${r} gives on top: 43, as when the later bar is written out self-referential-substitutions5 casesJava 5/5 · hocon2 5/5 · pulse-pyhocon 5/5 · py.hocon 5/5 · pyhocon 5/5 · rs.hocon 5/5 · zig-hocon 5/5
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-path-builds-on-older-value pass pass pass pass pass pass pass a field may look up its own older value before being overridden with a new value based on it, letting path:${path}":d" extend the earlier path:"a:b:c" 002-self-referential-concatenation pass pass pass pass pass pass pass a value concatenation containing a substitution to its own field is self-referential, so ${a} looks back to the earlier x and the result is xbc 003-object-containing-self-reference-is-not-self-referential pass pass pass pass pass pass pass an object with a substitution inside it is not considered self-referential for this purpose, so a:{b:${a}} is an unbreakable cycle that must generate an error 004-array-containing-self-reference-is-not-self-referential pass pass pass pass pass pass pass an array with a substitution inside it is not considered self-referential for this purpose, so a:[${a}] is an unbreakable cycle that must generate an error 006-self-referential-array-concatenation pass pass pass pass pass pass pass the spec lists path:${path} [ /usr/bin ] among the examples of self-referential fields: a value concatenation of a substitution with an array literal is still self-referential size-in-bytes-format5 casesJava 5/5 · hocon2 5/5 · pulse-pyhocon 5/5 · py.hocon 5/5 · pyhocon 4/5 · rs.hocon 5/5 · zig-hocon 5/5
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-bare-number-is-bytes pass pass pass pass pass pass pass bare numbers are taken to be in bytes already 002-single-byte-spellings pass pass pass pass pass pass pass for single bytes, exactly B, b, byte, bytes are supported 003-powers-of-ten-suffixes pass pass pass pass pass pass pass every power-of-10 suffix and its long forms name the same unit family; one table, one rule 004-powers-of-two-suffixes pass pass pass pass fail pass pass every power-of-2 suffix and its long forms name the same unit family; one table, one rule. The E and e forms are quoted because an unquoted 5E is a number token that is not a number (see unquoted-strings/022) 019-single-letter-abbreviation-means-powers-of-two pass pass pass pass pass pass pass the single-letter abbreviations (like K) are ambiguous between powers of two and ten, and this spec follows the java -Xmx / GNU-tools precedent of mapping them to powers of two string-value-concatenation18 casesJava 13/18 · hocon2 18/18 · pulse-pyhocon 15/18 · py.hocon 18/18 · pyhocon 15/18 · rs.hocon 18/18 · zig-hocon 18/18
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-substitution-in-string-concatenation pass pass pass pass pass pass pass string value concatenation supports substitutions inside the concatenated string 002-only-simple-values-participate pass pass pass pass pass pass pass it is invalid for arrays or objects to appear in a string value concatenation, since only simple values participate 003-leading-and-trailing-whitespace-trimmed pass pass pass pass pass pass pass whitespace before the first and after the last simple value is discarded; only the whitespace between the values is kept 004-concatenation-never-spans-newline pass pass pass pass pass pass pass string value concatenations never span a newline, so a bare word on the following line is not appended to the value above it 005-array-element-string-concatenation pass pass pass pass pass pass pass a string value concatenation may appear in any place a string may appear, including array elements 006-boolean-becomes-string-in-concatenation pass pass fail pass fail pass pass true and false become the strings "true" and "false" when they participate in a concatenation 007-null-becomes-string-in-concatenation pass pass fail pass fail pass pass null becomes the string "null" when it participates in a concatenation 008-number-kept-as-written-in-concatenation pass pass pass pass pass pass pass numbers should be kept as they were originally written in the file when they participate in a concatenation, not renormalized 009-single-boolean-not-converted-to-string pass pass pass pass pass pass pass a single value is never converted to a string; true by itself must be parsed as a boolean-typed value, not a string 010-object-then-scalar-drops-the-scalar fail pass pass pass pass pass pass it is invalid for arrays or objects to appear in a string value concatenation; typesafe/config accepts this one silently and discards the trailing x, losing data with no diagnostic, while erroring on the same pair in the other orderjava: lenient 011-array-then-scalar-drops-the-scalar fail pass pass pass pass pass pass the same rule for arrays; typesafe/config again accepts and discards the trailing x, where it errors when the scalar comes firstjava: lenient 012-scalar-then-object-is-an-error pass pass pass pass pass pass pass the same rule with the scalar first, which typesafe/config does reject -- the half of the rule it enforces 014-array-then-unquoted-text-without-space fail pass pass pass pass pass pass an array may not appear in a string value concatenation, with or without whitespace between them; typesafe/config silently drops suffix (lightbend/config#862)java: lenient 015-object-then-unquoted-text-without-space fail pass pass pass pass pass pass the same rule for an object followed directly by text; typesafe/config silently drops suffix (lightbend/config#862)java: lenient 016-text-between-arrays-is-not-dropped fail pass pass pass pass pass pass text between two arrays makes a concatenation of array, string and array, which is invalid; typesafe/config drops the text and concatenates the arrays (lightbend/config#862)java: lenient 020-64-bit-number-kept-as-written pass pass pass pass pass pass pass a number in a concatenation is stringified as it appeared in the file, whatever its size 021-false-becomes-string-in-concatenation pass pass fail pass fail pass pass false becomes the string "false" in a concatenation, as true does 022-quoted-and-unquoted-strings-concatenate pass pass pass pass pass pass pass a quoted string is itself in a concatenation, and the whitespace between it and the unquoted string beside it is kept substitution-fallback-to-environment8 casesJava 8/8 · hocon2 8/8 · pulse-pyhocon 7/8 · py.hocon 8/8 · pyhocon 7/8 · rs.hocon 8/8 · zig-hocon 8/8
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-env-fallback-for-undefined-path pass pass pass pass pass pass pass a substitution whose path is not defined in the configuration falls back to an environment variable of the same name 002-required-substitution-resolved-from-env pass pass pass pass pass pass pass a required ${x} substitution that is not present in the configuration tree is searched for in the environment and resolves to the variable's value instead of being an error 003-config-value-wins-over-env pass pass pass pass pass pass pass the environment is only searched when the substitution is not present within the configuration tree, so a value set in the configuration wins over an environment variable of the same name 004-null-in-config-blocks-env-lookup pass pass fail pass fail pass pass setting a key to null in the configuration explicitly blocks looking that substitution up in the environment, so ${CONFORMANCE_X} resolves to null even though the variable is set 005-config-lookup-is-case-sensitive pass pass pass pass pass pass pass the lookup is case-sensitive all the way until the env variable fallback is reached, so a lowercase key does not shadow an uppercase substitution and the environment variable is used 006-empty-env-var-is-empty-string pass pass pass pass pass pass pass an env variable set to the empty string is kept as such, so a required substitution resolves to "" rather than being undefined 007-empty-env-var-creates-optional-field pass pass pass pass pass pass pass an env variable set to the empty string is set to empty string rather than undefined, so an optional ${?x} substitution creates the field with "" instead of dropping it 008-env-vars-always-become-strings pass pass pass pass pass pass pass environment variables always become a string value, so values that would parse as a number, a boolean or null in HOCON stay the strings "42", "true" and "null" substitutions37 casesJava 36/37 · hocon2 31/37 · pulse-pyhocon 32/37 · py.hocon 36/37 · pyhocon 28/37 · rs.hocon 36/37 · zig-hocon 37/37
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-basic-substitution pass pass pass pass pass pass pass the syntax ${pathexpression} refers to another part of the configuration tree 002-optional-substitution-syntax pass pass pass pass pass pass pass ${?pathexpression} is the optional substitution syntax; when the path is defined it resolves exactly like ${pathexpression}, keeping the value and its type 003-optional-substitution-whitespace-illegal pass pass pass pass pass pass pass the ? in ${?pathexpression} must not have whitespace before it; the three characters ${? must be exactly grouped together 004-substitution-not-parsed-in-quoted-string pass pass pass pass pass pass pass substitutions are not parsed inside quoted strings, so this stays the literal text 005-substitution-value-concatenation-unquoted pass pass pass pass pass pass pass worked example: to get a string containing a substitution, use value concatenation with the substitution in the unquoted portion 006-substitution-value-concatenation-quoted-tail pass pass pass pass pass pass pass worked example: quoting the non-substitution portion also forms a value concatenation with the substitution 007-substitution-path-absolute pass pass pass pass pass pass pass substitutions are resolved by looking up the path from the root configuration object, absolute rather than relative, so ${a} inside b finds the root a and not the sibling b.a 008-substitution-forward-reference pass pass pass pass pass pass pass substitution processing happens as the last parsing step, so a substitution can look forward in the configuration 009-substitution-latest-duplicate-key pass pass pass pass pass pass pass if a key has been specified more than once, a substitution referring to it always evaluates to its latest-assigned value 010-substitution-latest-duplicate-key-object-merge pass pass pass pass pass pass pass a substitution to a key set more than once evaluates to the merged object, not an earlier snapshot 011-undefined-required-substitution-is-error pass pass pass pass pass pass pass an undefined substitution with the ${foo} syntax is invalid and should generate an error 012-undefined-optional-substitution-field-not-created pass pass pass pass pass pass pass if an undefined ${?foo} substitution is the value of an object field, the field should not be created 013-undefined-optional-substitution-keeps-previous-value pass pass fail pass pass pass pass if an undefined ${?foo} would override a previously-set value for the same field, the previous value remains 014-undefined-optional-substitution-array-element-not-added pass pass pass pass pass pass pass if an undefined ${?foo} substitution is an array element, the element should not be added 015-undefined-optional-substitution-concatenation-string-empty pass pass pass pass pass pass pass an undefined ${?foo} in a value concatenation with another string becomes an empty string 016-undefined-optional-substitution-concatenation-object-empty pass pass pass pass pass pass pass an undefined ${?foo} in a value concatenation with an object becomes an empty object 017-undefined-optional-substitution-concatenation-array-empty pass pass pass pass pass pass pass an undefined ${?foo} in a value concatenation with an array becomes an empty array 018-two-optional-substitutions-both-undefined-field-not-created pass pass pass pass pass pass pass foo : ${?bar}${?baz} avoids creating the field only if both bar and baz are undefined 019-two-optional-substitutions-one-defined-field-created pass pass pass pass pass pass pass foo : ${?bar}${?baz} creates the field once at least one of bar or baz is defined 020-substitution-not-allowed-in-key pass pass pass pass pass pass pass substitutions are only allowed in field values and array elements, not in keys 021-substitution-not-allowed-nested-in-path-expression pass pass pass pass pass pass pass substitutions are not allowed nested inside other substitutions (path expressions) 022-substitution-preserves-type-array pass pass pass pass pass pass pass a substitution is replaced with any value type; if it is the only part of a value, the type (here an array) is preserved 024-substitution-concatenation-forms-string pass pass pass pass pass pass pass when a substitution is not the only part of a value, it is value-concatenated to form a string rather than keeping the substituted type 025-substitution-preserves-type-string-booleans-null pass pass fail pass fail pass pass a substitution is replaced with any value type and, when it is the only part of a value, the type is preserved: here string, true, false and null stay what they were rather than becoming strings 026-unterminated-substitution-is-error pass pass pass pass pass pass pass the syntax is ${pathexpression}: a substitution opened with ${ and never closed with } is not that syntax, and $ cannot start an unquoted string either, so it must be rejected 028-optional-substitution-to-null-value-is-defined pass pass fail pass fail pass pass a substitution is undefined only if it does not match any value present in the configuration, and null is among the value types a substitution is replaced with, so ${?a} with a = null is defined: the field is created with null rather than omitted 030-quoted-key-is-not-a-substitution pass pass pass pass pass pass pass substitutions are not parsed inside quoted strings, and a quoted key is a quoted string: "${a}" is the literal key ${a}, not a substitution in a key (which would be illegal) 031-whitespace-inside-braces-is-trimmed pass fail pass pass fail pass pass the path inside ${} has the syntax of an object key, and whitespace before the first and after the last element of a key is discarded, so ${ b } is ${b} 032-empty-substitution-illegal pass pass pass pass pass pass pass ${} contains no path expression at all, so it is neither a substitution nor anything else 033-path-through-a-non-object-is-undefined pass fail pass pass pass pass pass a is a number, so a.x names nothing; a required substitution to an undefined path is an error 034-optional-path-through-a-non-object-is-omitted pass fail pass pass fail pass pass the same path with the optional form: undefined, so the field is not created 035-quoted-path-element-with-space pass pass pass pass pass pass pass a substitution path has the syntax of an object key, so a quoted element may contain a space and names the field written the same way 036-empty-quoted-path-element pass pass fail pass fail pass pass an empty path element is legal when quoted, in a substitution as in a key 037-path-into-a-list-element-is-undefined pass fail pass pass fail pass pass a path names object fields only, so b.0 does not reach into the list b even though it has an element at index 0; the optional substitution is undefined and a is not created. The list is still unresolved when the lookup happens, which is what makes an implementation look inside it 038-whitespace-after-question-mark pass fail pass pass fail pass pass only whitespace before the ? is forbidden; after it the path follows and its leading whitespace is discarded as in any key 039-undefined-optional-with-number-is-a-string fail fail pass fail fail fail pass ${?x}42 is a concatenation of two parts, so the value is a string: the undefined optional becomes empty and the number keeps its written form. The spec states the empty-string rule for a concatenation 'with another string', so this row extends it to a number; typesafe/config instead drops the undefined part and keeps the lone number as a numberjava: diverges 040-null-blocks-environment-lookup pass pass fail pass fail pass pass the configuration sets CONFORMANCE_NULLED to null, so the substitution resolves to that null and the environment variable of the same name is never consulted unchanged-from-json27 casesJava 21/27 · hocon2 21/27 · pulse-pyhocon 13/27 · py.hocon 20/27 · pyhocon 13/27 · rs.hocon 18/27 · zig-hocon 27/27
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-quoted-string-json-format pass pass fail pass fail pass pass quoted strings are in the same format as JSON strings, so JSON escapes like \n, \t, \", \\ and \u unicode escapes are honored 002-string-value-type pass pass pass pass pass pass pass string is one of the possible value types 003-number-value-type pass pass pass pass pass pass pass number is one of the possible value types 004-object-value-type pass pass pass pass pass pass pass object is one of the possible value types 005-array-value-type pass pass pass pass pass pass pass array is one of the possible value types 006-boolean-value-type pass pass pass pass pass pass pass boolean is one of the possible value types 007-null-value-type pass pass pass pass pass pass pass null is one of the possible value types 008-number-format-decimal-and-exponent pass pass pass pass pass pass pass allowed number formats match JSON, including negative numbers, decimals, and exponent notation 009-number-leading-zero-illegal fail fail fail fail fail fail pass allowed number formats match JSON, and JSON forbids a zero followed by further digits; the initial number character plus the valid-in-JSON number characters after it must be parsed as a number value, so the whole token 01 has to be a number and is not one, with no fallback offered. typesafe/config parses it leniently as the number 1.java: lenient 010-negative-number-without-integer-part-illegal fail fail fail fail fail fail pass allowed number formats match JSON, and JSON requires a digit before the decimal point, so -.33 is a number token that is not a valid number. typesafe/config releases up to 1.4.9 parse it as -0.33; upstream main since lightbend/config#861 (unreleased) reads it as the unquoted string "-.33", like .33 -- still accepted, still not the error the hyphen rule asks forjava: lenient 011-negative-exponent-number-without-integer-part-illegal fail pass fail fail fail fail pass the same JSON rule with an exponent: -.33e+1 has no integer part. typesafe/config releases up to 1.4.9 parse it as -3.3; upstream main since lightbend/config#861 (unreleased) rejects it, on the reserved +java: lenient 012-number-with-trailing-dot-illegal fail fail fail fail fail fail pass JSON requires a digit after the decimal point; 1. is a number token that is not a valid number. typesafe/config reads it as 1, and lightbend/config#861 leaves it so on purposejava: lenient 013-trailing-dot-before-exponent-illegal fail fail fail fail fail fail pass the same rule with an exponent after the dot; typesafe/config reads it as -1000 (named out of scope in lightbend/config#861)java: lenient 014-negative-leading-zeros-illegal fail fail fail fail fail fail pass JSON forbids leading zeros after the minus sign as well; typesafe/config reads it as -33 (named out of scope in lightbend/config#861)java: lenient 015-remaining-json-escapes pass pass fail pass fail pass pass the JSON escapes \/ \b \f and \r are valid in a quoted string and mean slash, backspace, form feed and carriage return 016-unknown-escape-illegal pass pass fail pass fail pass pass only the JSON escape sequences exist; \q is not one of them, so the string is invalid 017-non-hex-unicode-escape-illegal pass pass fail pass fail pass pass \u must be followed by exactly four hex digits; zzzz are not hex digits 018-unterminated-quoted-string-illegal pass pass pass pass pass pass pass a quoted string that is still open at the end of the file is invalid 019-backslash-at-end-of-file-illegal pass pass pass pass pass pass pass a backslash with nothing after it is not an escape and the string is still open at the end of the file 020-unicode-escape-cut-off-by-end-of-file-illegal pass pass pass pass pass pass pass the file ends after two of the four hex digits a \u escape requires 021-raw-newline-in-quoted-string-illegal pass pass fail pass pass pass pass JSON forbids unescaped control characters in strings, and a raw newline is one; a quoted string cannot span lines 022-raw-tab-in-quoted-string-illegal pass pass fail fail fail fail pass a raw tab is a control character too, so it must be written as \t inside quotes 023-64-bit-integer pass pass pass pass pass pass pass a number beyond 32 bits is still a number and keeps its exact value 024-raw-utf8-in-quoted-string pass pass pass pass fail pass pass the file is UTF-8, so a multi-byte sequence is one character 025-nan-is-not-a-number pass fail pass pass pass pass pass NaN is not a JSON number, so the bare token is an unquoted string 026-surrogate-pair-escape pass pass fail pass fail fail pass JSON writes a character outside the BMP as two \u escapes forming a surrogate pair, which decode to one character 027-unpaired-surrogate-escape-is-kept pass pass fail pass fail fail pass quoted strings are JSON strings, and JSON's grammar accepts any \uXXXX escape, an unpaired surrogate included, so the string holds that one code unit units-format6 casesJava 6/6 · hocon2 6/6 · pulse-pyhocon 6/6 · py.hocon 6/6 · pyhocon 5/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-number-value-is-default-unit pass pass pass pass pass pass pass if the value is a number, it is taken to be a number in the default unit 002-quoted-string-with-no-unit-uses-default-unit pass pass pass pass pass pass pass a string value with no unit name should be interpreted with the default unit, as if it were a number 003-string-with-unit-name-specifies-interpretation pass pass pass pass fail pass pass a string value with a unit name has that name specify the value's interpretation 004-whitespace-optional-around-number-and-unit pass pass pass pass pass pass pass the units-format grammar is optional whitespace, a number, optional whitespace, an optional unit name, optional whitespace 005-unit-name-must-be-letters-only pass pass pass pass pass pass pass the unit name consists only of letters (Unicode L* categories, Java isLetter()), so a unit name with a trailing digit is not a legal units-format string 006-unit-before-number-is-illegal pass pass pass pass pass pass pass the units-format grammar puts the number before the unit name, not after, so a unit-then-number string is not a legal units-format value unquoted-strings25 casesJava 22/25 · hocon2 21/25 · pulse-pyhocon 11/25 · py.hocon 21/25 · pyhocon 16/25 · rs.hocon 21/25 · zig-hocon 25/25
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-simple-unquoted-string pass pass pass pass pass pass pass a sequence of characters outside a quoted string is a string value when it contains none of the forbidden characters 002-quoted-alternative-for-forbidden-char pass pass pass pass pass pass pass a quoted string may always be used as an alternative to write a character that is not permitted in an unquoted string, such as '@' 003-unquoted-cannot-contain-at-sign pass pass fail pass pass pass pass '@' is one of the listed forbidden characters, so it cannot appear inside an unquoted string 004-double-slash-starts-comment pass pass fail pass fail pass pass the two-character string '//' starts a comment, ending the unquoted string before it even mid-token 005-single-slash-allowed pass pass pass pass pass pass pass only the two-character string '//' is special for comments; a lone '/' is not a forbidden character and stays part of the unquoted string 006-truefoo-parses-as-true-then-foo pass fail pass pass pass pass pass worked example: truefoo parses as the boolean token true followed by the unquoted string foo, because its initial characters parse as true 007-footrue-is-one-unquoted-string pass pass pass pass pass pass pass worked example: footrue does not begin with true, false, null, or a number, so it parses as a single unquoted string 008-number-then-suffix-string pass pass pass pass pass pass pass worked example: 10.0bar is the number 10.0 followed by the unquoted string bar, since 10.0 parses as a number at the start 009-digits-not-at-start-stay-string pass pass pass pass pass pass pass worked example: bar10.0 does not begin with a digit, so the whole thing is the single unquoted string bar10.0, not a number 010-embedded-null-not-recognized pass pass pass pass pass pass pass embedded (non-initial) null, true, false, and numbers are not recognized as such: they are just part of the string, since only the leading characters are checked 011-unquoted-cannot-contain-backslash pass pass fail pass fail pass pass backslash is a forbidden character, and unquoted strings support no escaping at all, so a literal backslash requires a quoted string instead 012-unquoted-cannot-start-with-hyphen fail fail fail fail fail fail pass an unquoted string may not begin with a hyphen, and the initial number character plus any valid-in-JSON number characters that follow must be parsed as a number value -- here that is the lone '-', which is not a number. typesafe/config falls back to unquoted text and yields the string '-foo', the same leniency it shows for '1e'.java: lenient 013-negative-number-then-string pass pass pass pass pass pass pass a hyphen followed by digits begins number parsing (-1), after which foo continues as a separate unquoted string token, mirroring the 10.0bar example for negative numbers 014-hash-starts-comment-not-forbidden-char-in-string pass pass fail pass pass pass pass '#' is forbidden inside an unquoted string because it already has meaning in HOCON: it starts a comment 015-reserved-forbidden-char-no-current-meaning pass pass fail pass pass pass pass some forbidden characters, such as '!', have no meaning in HOCON today and are reserved as keywords for future extensions to the spec 016-control-char-allowed-unquoted pass pass pass pass pass pass pass unquoted strings place no restriction on control characters, other than the forbidden characters listed 017-control-char-forbidden-in-quoted pass pass fail fail fail fail pass quoted JSON strings may not contain control characters, per the JSON spec, unlike unquoted strings 018-leading-dot-is-not-a-number pass pass fail pass fail pass pass only 0-9 and - begin a number, so .33 is an ordinary unquoted string -- the reading lightbend/config#861 extends to -.33 019-dollar-must-start-a-substitution pass pass fail pass pass pass pass $ is a forbidden character in an unquoted string, and outside quotes it may only begin ${; $foo is neither 020-plus-must-be-followed-by-equals pass pass fail pass pass pass pass + is a forbidden character in an unquoted string and outside quotes only forms the += separator; a + with nothing after it, here at the end of the file, is invalid 021-plus-inside-a-number-illegal pass pass fail pass fail pass pass 1+2 begins like a number and is not one; + is a forbidden character, so the token cannot fall back to an unquoted string either 022-digit-then-exponent-letter-illegal fail fail fail fail fail fail pass 1e begins with a digit, so the run of number characters 1e must be parsed as a number, and it is not one; typesafe/config falls back to the unquoted string "1e", the same leniency it shows for -foojava: lenient 023-unquoted-string-ends-at-end-of-file pass pass pass pass pass pass pass an unquoted string runs to a forbidden character or whitespace, and the end of the file ends it as well; no final newline is needed 024-escape-sequence-in-unquoted-string-illegal pass pass fail pass fail pass pass unquoted strings support no escaping, so \u00e9 is not decoded, and the backslash it needs is forbidden in an unquoted string; the quoted "caf\u00e9" is how to write it 025-digit-initial-invalid-number fail fail fail fail fail fail pass an unquoted string may not begin with the digits 0-9, and the initial number character plus any valid-in-JSON number characters that follow it must be parsed as a number value -- 1.2.3 is that run, and it is not a number, so the input is rejected rather than read as a string (the everyday shape is an address like 127.0.0.1); typesafe/config and pyhocon both fall back to the unquoted string "1.2.3"java: lenient value-concatenation2 casesJava 2/2 · hocon2 2/2 · pulse-pyhocon 2/2 · py.hocon 2/2 · pyhocon 2/2 · rs.hocon 2/2 · zig-hocon 2/2
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-simple-values-concatenate-to-string pass pass pass pass pass pass pass if all the values are simple values they are concatenated into a string 004-string-concatenation-allowed-in-field-keys pass pass pass pass pass pass pass string value concatenation is allowed in field keys, in addition to field values and array elements whitespace13 casesJava 13/13 · hocon2 12/13 · pulse-pyhocon 3/13 · py.hocon 10/13 · pyhocon 4/13 · rs.hocon 10/13 · zig-hocon 13/13
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-nonbreaking-space-as-whitespace pass pass fail pass fail pass pass a nonbreaking space (0x00A0) is a Unicode Zs separator and must be treated as whitespace 002-bom-at-start-is-whitespace pass pass fail pass fail pass pass the BOM (0xFEFF) must be treated as whitespace, not as part of the key 003-unicode-line-paragraph-separator pass pass fail fail pass fail pass the line and paragraph separators U+2028 and U+2029 are whitespace but not newlines, so they do not separate two fields: 1 b concatenates and the second = is a stray token 004-vertical-tab-form-feed-whitespace pass pass fail pass fail pass pass vertical tab (0x000B) and form feed (0x000C) are whitespace characters 005-ascii-separator-control-chars pass pass pass fail pass fail pass the ASCII separator controls 0x1C-0x1F are whitespace but not newlines, so they do not separate two fields 006-special-nonbreaking-spaces pass pass fail pass fail pass pass the spec names figure space (0x2007) and narrow no-break space (0x202F) explicitly among the nonbreaking spaces treated as whitespace 007-tab-is-whitespace pass pass pass pass pass pass pass a tab is whitespace around a key and a separator 008-crlf-line-endings pass pass pass pass pass pass pass a carriage return is whitespace and the line feed beside it is the newline, so CRLF line endings separate fields; if the CR were text the first value would be "1\r" 009-lone-carriage-return-is-not-a-newline pass fail fail fail fail fail pass a lone carriage return is whitespace but not a newline, so 1 b concatenates and the second = is a stray token 010-line-separator-is-whitespace pass pass fail pass fail pass pass U+2028 around the separator is whitespace, not part of the key or the value 011-ascii-separator-controls-are-whitespace pass pass fail pass fail pass pass the file, group, record and unit separators 0x1C-0x1F are whitespace around keys and separators 012-ideographic-space-is-whitespace pass pass fail pass fail pass pass the ideographic space U+3000 is a Zs space separator and so whitespace, like every member of the category 013-bom-inside-the-document-is-whitespace pass pass fail pass fail pass pass the BOM is whitespace wherever it appears, not only as a file-start marker array-and-object-concatenation22 casesJava 17/22 · hocon2 19/22 · pulse-pyhocon 19/22 · py.hocon 19/22 · pyhocon 21/22 · rs.hocon 19/22 · zig-hocon 22/22
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-arrays-concatenate pass pass pass pass pass pass pass arrays can be concatenated with arrays when only non-newline whitespace separates them 002-objects-concatenate pass pass pass pass pass pass pass objects can be concatenated with objects when only non-newline whitespace separates them 003-mixing-array-and-object-is-error pass pass pass pass pass pass pass it is an error to concatenate an array with an object 004-substitution-counts-as-array-for-concatenation pass pass pass pass pass pass pass for purposes of concatenation a substitution that resolves to an array also means an array, so ${x} [ 1, 2 ] with x an array concatenates into one array 005-substitution-counts-as-object-for-concatenation pass pass pass pass pass pass pass for purposes of concatenation a substitution that resolves to an object also means an object, so ${x} { c : 1 } with x an object merges into one object 006-newline-between-values-prevents-concatenation pass pass pass pass pass pass pass newlines between a substitution and an array prevent concatenation, so inside an array they stay two separate elements instead of one concatenated array 007-object-concatenation-merges-and-second-overrides pass pass pass pass pass pass pass for objects, concatenation means merging, and the second object overrides the first on overlapping keys 008-array-cannot-be-field-key pass pass pass pass pass pass pass arrays cannot be field keys; nested inside an object so the rejection comes from the key position, not from the document root 009-object-cannot-be-field-key pass pass pass pass pass pass pass objects cannot be field keys; nested inside an object so the rejection comes from the key position, not from the document root 010-worked-example-object-inheritance pass pass pass pass pass pass pass the spec's worked example: object concatenation with a substitution is a common way to express inheritance 011-worked-example-array-path-append pass pass pass pass pass pass pass the spec's worked example: array concatenation with a substitution is a common way to add to paths 012-worked-example-self-referential-array-concat pass pass pass pass pass pass pass the spec's worked example: a later array definition can concatenate a substitution referring to its own earlier value with a new array 013-newlines-within-value-still-allow-concatenation pass pass pass pass pass pass pass newlines may occur within an array without preventing it from concatenating with a second array on the same line as its closing bracket 014-substitution-object-with-literal-array-is-error pass pass pass pass pass pass pass a substitution that resolves to an object means an object for concatenation, and concatenating an object with an array is an error 015-substitution-array-with-literal-object-is-error pass pass pass pass pass pass pass a substitution that resolves to an array means an array for concatenation, and concatenating an array with an object is an error 016-mixing-object-then-array-is-error pass pass pass pass pass pass pass it is an error to concatenate an object with an array in this order too, not only array then object 017-newline-between-arrays-in-field-value-prevents-concatenation pass pass pass pass pass pass pass a newline between two arrays in a field value prevents concatenation, so the second array begins a new field, and an array cannot be a field key 018-object-with-inner-merge-concatenated-with-substitution fail pass pass pass pass pass pass an object whose field a is itself a merge of an object and a substitution concatenates with a substituted object like any other: the two merge. typesafe/config fails with an internal error (BugOrBroken) because the concatenation is not registered as the merge's parent; the same objects written as literals concatenate finejava: unsupported 019-self-referential-object-concatenation-with-inner-merge fail pass pass pass pass pass pass ${a} in a's own value refers to the earlier a, an object, so the concatenation merges {p: ...} with {q: 1}; the inner p is itself a merge of an object and a substitution. typesafe/config fails with an internal error (BugOrBroken) when the merge inside a concatenation piece resolves, as in 018java: unsupported 020-optional-override-inside-object-concatenation fail fail fail fail fail fail pass inside each ${a} { ... } the undefined ${?missing} leaves the c before it, and the later b merges over the earlier one: c is 2; typesafe/config fails with an internal error (lightbend/config#647, #725, #754)java: unsupported 021-substitution-over-duplicate-key-inside-object-concatenation fail fail fail fail pass fail pass the later c, an object, overrides the string before it inside the object merged onto ${a}; typesafe/config fails with an internal error (lightbend/config#356)java: unsupported 022-duplicate-key-in-list-element-before-list-substitution fail fail fail fail pass fail pass ${tail} is a list, so it concatenates with the list before it, whose object keeps foo = 1 under the undefined ${?bar}; typesafe/config fails with an internal error (lightbend/config#751)java: unsupported arrays-without-commas-or-newlines7 casesJava 7/7 · hocon2 7/7 · pulse-pyhocon 6/7 · py.hocon 6/7 · pyhocon 7/7 · rs.hocon 6/7 · zig-hocon 7/7
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-newlines-instead-of-commas pass pass pass pass pass pass pass arrays allow newlines to separate elements instead of commas 002-whitespace-instead-of-comma-concatenates-into-one-element pass pass pass pass pass pass pass the spec's worked example: non-newline whitespace instead of a comma produces concatenation, so this array has one element, the string 1 2 3 4, not four integers 003-newline-separates-two-arrays-into-elements pass pass pass pass pass pass pass the spec's worked example: a newline between two bracketed arrays makes them two separate elements 004-same-line-whitespace-concatenates-two-arrays-into-one-element pass pass pass pass pass pass pass the spec's worked example: whitespace on the same line between two bracketed arrays concatenates them into a single element, the array [1, 2, 3, 4] 005-unquoted-string-concatenation-with-substitutions-in-array pass pass pass pass pass pass pass the spec's worked example: unquoted words and substitutions separated by whitespace concatenate into one string element, and only the comma separates elements 006-two-elements-each-a-substitution-concatenation pass pass pass pass pass pass pass the spec's worked example: whitespace between two substitutions concatenates them into one element, and the comma separates the two concatenations 007-whitespace-never-separates-fields pass pass fail fail pass fail pass non-newline whitespace is never a field separator, so two field definitions cannot be written on one line separated only by spaces commas13 casesJava 13/13 · hocon2 13/13 · pulse-pyhocon 5/13 · py.hocon 12/13 · pyhocon 6/13 · rs.hocon 12/13 · zig-hocon 13/13
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-array-trailing-comma pass pass pass pass pass pass pass the last element in an array may be followed by a single comma, which is ignored: [1,2,3,] and [1,2,3] are the same array 002-array-newline-no-comma pass pass pass pass pass pass pass array elements need not have a comma between them as long as an ASCII newline separates them: [1\n2\n3] and [1,2,3] are the same array 003-array-double-trailing-comma-invalid pass pass fail pass fail pass pass [1,2,3,,] is explicitly called invalid because it has two trailing commas 004-array-initial-comma-invalid pass pass fail pass fail pass pass [,1,2,3] is explicitly called invalid because it has an initial comma 005-array-double-comma-invalid pass pass fail pass fail pass pass [1,,2,3] is explicitly called invalid because it has two commas in a row 006-object-trailing-comma pass pass pass pass pass pass pass these same comma rules apply to fields in objects, so a single trailing comma after the last field is ignored 007-object-newline-no-comma pass pass pass pass pass pass pass fields in objects need not have a comma between them as long as an ASCII newline separates them, same as array elements 008-object-double-comma-invalid pass pass fail pass fail pass pass these same comma rules apply to fields in objects, so two commas in a row between fields is invalid just as in an array 009-fields-on-one-line-need-a-comma pass pass fail fail pass fail pass two fields need a comma or a newline between them; on one line with neither, 1 b becomes a value concatenation and the second = is a stray token 011-trailing-comma-at-implicit-root pass pass pass pass pass pass pass the implicit root object follows the object comma rules, so its last field may carry a single trailing comma 012-leading-comma-at-implicit-root-illegal pass pass fail pass fail pass pass an initial comma is invalid in an object, the implicit root included 013-double-trailing-comma-in-object-illegal pass pass fail pass fail pass pass the array comma rules apply to object fields: two trailing commas are invalid 014-leading-comma-in-object-illegal pass pass fail pass fail pass pass the array comma rules apply to object fields: an initial comma is invalid comments14 casesJava 14/14 · hocon2 14/14 · pulse-pyhocon 14/14 · py.hocon 14/14 · pyhocon 14/14 · rs.hocon 14/14 · zig-hocon 14/14
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-hash-comment pass pass pass pass pass pass pass anything between # and the next newline is a comment and ignored 002-double-slash-comment pass pass pass pass pass pass pass anything between // and the next newline is a comment and ignored 003-whole-line-comment pass pass pass pass pass pass pass a comment may occupy an entire line by itself, ignored just like a trailing one 004-comment-stops-at-newline pass pass pass pass pass pass pass a comment runs only to the next newline, so the following line is parsed normally 005-hash-inside-quoted-string-is-not-a-comment pass pass pass pass pass pass pass a # inside a quoted string is not treated as starting a comment 006-double-slash-inside-quoted-string-is-not-a-comment pass pass pass pass pass pass pass a // inside a quoted string is not treated as starting a comment 007-comment-between-separator-and-value pass pass pass pass pass pass pass a comment runs to the end of its line and is then over; the value may follow on the next line just as it may after a bare = 008-comment-then-blank-line-then-field pass pass pass pass pass pass pass a comment separated from the following field by a blank line is ignored like any other comment 009-comment-inside-object pass pass pass pass pass pass pass comments and blank lines inside an object body are ignored 010-comments-inside-array pass pass pass pass pass pass pass comments and blank lines between array elements are ignored, and do not join or separate elements 011-comment-before-values-of-every-type pass pass pass pass pass pass pass a comment before a field is ignored whatever type its value has 012-comment-before-explicit-root-object pass pass pass pass pass pass pass a comment and a blank line before an explicit root object are skipped before the implicit-braces rule looks at the first token; the spec's wording ("does not begin with") is loose here and this row records the consensus reading, as omit-root-braces/009 does 013-line-comment-after-array pass pass pass pass pass pass pass a // comment after an array is not text in a concatenation; the value is just the array 014-hash-comment-after-array-without-space pass pass pass pass pass pass pass a # comment directly after ] is a comment, not text in a concatenation concatenation-whitespace-and-substitutions9 casesJava 7/9 · hocon2 9/9 · pulse-pyhocon 8/9 · py.hocon 9/9 · pyhocon 7/9 · rs.hocon 9/9 · zig-hocon 9/9
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-unquoted-whitespace-between-substitutions-is-significant-for-strings pass pass pass pass pass pass pass when the substitutions turn out to be strings, the unquoted whitespace between them is significant and survives into the concatenated string 002-quoted-whitespace-between-substitutions-is-error pass pass pass pass pass pass pass between substitutions that resolve to objects, unquoted whitespace is ignored but quoted whitespace should be an error 003-unquoted-whitespace-between-object-and-list-substitutions-is-ignored pass pass pass pass pass pass pass unquoted whitespace between substitutions that resolve to objects or lists must be ignored, so they merge and concatenate as if adjacent 004-tab-between-list-substitutions-is-ignored pass pass pass pass pass pass pass whitespace between two list substitutions is ignored, tabs included, and the lists concatenate 005-nonbreaking-space-between-list-substitutions-is-ignored pass pass pass pass fail pass pass U+00A0 is a Zs space and so whitespace in HOCON; between two list substitutions it is ignored (lightbend/config#862 review) 006-bom-between-list-substitutions-is-ignored pass pass fail pass fail pass pass the BOM must be treated as whitespace, so between two list substitutions it is ignored 007-zero-width-space-between-list-substitutions-is-error fail pass pass pass pass pass pass U+200B is format (Cf), not a space separator, so it is text, and text between two list substitutions is invalid. typesafe/config drops it and concatenates the lists; lightbend/config#862 rejects itjava: lenient 008-list-then-undefined-optional-is-the-list pass pass pass pass pass pass pass an undefined optional substitution in a concatenation with an array becomes an empty array 009-undefined-optional-then-list-is-the-list fail pass pass pass pass pass pass the mirror of 008: an undefined optional substitution before an array becomes an empty array too. typesafe/config keeps the whitespace before the list as text and rejects it, while accepting 008 (noted on lightbend/config#862)java: unsupported config-and-file-merging4 casesJava 4/4 · hocon2 4/4 · pulse-pyhocon 4/4 · py.hocon 4/4 · pyhocon 2/4 · rs.hocon 4/4 · zig-hocon 4/4
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 002-file-merge-is-recursive-and-later-file-wins pass pass pass pass pass pass pass merging objects loaded from different files is exactly the same behavior as merging duplicate fields in the same file: object-valued fields present in both files merge recursively and a non-object field takes the value from the later file 003-non-object-between-files-hides-earlier-object pass pass pass pass fail pass pass the spec's own example: an intermediate non-object value hides earlier object values because merging is done in pairs, so when 42 is paired with { y : 2 } it simply wins and loses all information about what it overrode, and the two objects are never merged 004-adjacent-objects-across-files-merge pass pass pass pass fail pass pass the spec's re-ordered example: with the non-object 42 moved to the lowest priority the two objects are adjacent, so they merge to { a : { x : 1, y : 2 } } 005-null-clears-included-object pass pass pass pass pass pass pass setting an object to null and then setting it back to a new object clears it and starts over, which is how to get rid of default fallback values from another file that you don't want duplicate-keys-and-object-merging28 casesJava 20/28 · hocon2 28/28 · pulse-pyhocon 21/28 · py.hocon 27/28 · pyhocon 24/28 · rs.hocon 27/28 · zig-hocon 28/28
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-later-scalar-wins pass pass pass pass pass pass pass for a non-object value the later assignment simply replaces the earlier one 002-objects-merge pass pass pass pass pass pass pass two objects under the same key merge key-by-key, they do not replace 003-scalar-breaks-the-merge pass pass pass pass fail pass pass merging is always done two values at a time: the scalar replaces the first object (non-object always wins), then the second object replaces the scalar (no merging, object is the new value), so the two objects never see each other 004-merge-scalar-field-later-wins pass pass pass pass pass pass pass for a non-object-valued field present in both merged objects, the field from the second object is used 005-merge-nested-object-recursive pass pass pass pass pass pass pass for an object-valued field present in both objects, the object values are recursively merged by the same rules 006-null-prevents-merge pass pass pass pass pass pass pass the spec's own example: an intermediate null is a non-object, so it replaces the first object and is in turn replaced by the second object with no merging, which prevents the two objects from ever seeing each other 007-merge-keeps-fields-from-either-side pass pass pass pass pass pass pass fields present in only one of the two objects are added to the merged object as they are, whether the first or the second side has them and whether the value is a scalar or an object with nothing to merge against 008-arrays-are-not-objects pass pass pass pass pass pass pass only two object values merge; an array is not an object, so a later array replaces an earlier array (no concatenation) and replaces an earlier object (no merge) 009-later-null-overrides pass pass pass pass pass pass pass null is a value like any other, so a later null overrides an earlier scalar or object: the key stays, set to null, rather than being removed or the assignment skipped 010-null-in-referenced-key-history-does-not-block-merge fail pass fail pass pass pass pass the null at r only stops merging at r; ${r} is the object {x:1}, and a later object merges with the earlier a, so helper stays. typesafe/config carries r's null through the substitution and drops helper (lightbend/config#864)java: diverges 011-nested-null-in-referenced-object-does-not-block-merge fail pass fail pass pass pass pass the same rule one level down: the null in v.nested's history belongs to v.nested, so a.nested still merges with the substituted {x:1} (lightbend/config#864)java: diverges 012-reference-to-merged-field-sees-the-same-value fail pass pass pass pass pass pass a substitution sees the resolved value of the field it names, so o24bbd equals a.nested, which keeps helper. The key name is chosen to sort before a in typesafe/config's root HashMap, the order in which the first version of lightbend/config#864 answered differently for the twojava: diverges 013-top-level-null-before-object-with-substitution fail pass fail pass pass pass pass the null belongs to v's history even when v's object is still unresolved, so a merges and keeps helper (lightbend/config#864)java: diverges 014-own-null-still-blocks-merge-with-referenced-object pass pass pass pass pass pass pass the receiver's own intermediate null still breaks the merge: a is {helper:0}, then null, then the substituted object, so helper is gone 015-earlier-receiver-value-is-evaluated-once-merged fail pass fail pass pass pass pass ${r} is an object, so it merges with the earlier a and the ${missing} inside it is evaluated: an error. typesafe/config 1.4.5 agreed; 1.4.9 drops the earlier a and yields {y:1}, and lightbend/config#864 restores the errorjava: lenient 016-null-deeper-in-a-merge-stack-stays-at-its-key fail pass fail pass fail pass pass v's value is a merge whose top is an object and whose null sits further down, under nested; that null is still v.nested's merge instruction, so a.nested keeps helper (lightbend/config#864 review, round 3)java: diverges 017-lookup-into-nested-null-merges fail pass pass pass pass pass pass two nested nulls each break only their own key's merge, and a lookup of a.n.d sees the final {x:1}; typesafe/config throws BugOrBroken in this key order (reported on lightbend/config#864, same on main)java: unsupported 018-object-then-empty-object pass pass pass pass pass pass pass two objects merge, so the empty later object keeps x; replacement would have given {} 019-duplicate-keys-merge-inside-a-list-element pass pass pass pass pass pass pass an object that is a list element follows the same duplicate-key rules: the later value, here resolved from a substitution, wins 020-objects-and-substitutions-alternate pass pass fail fail pass fail pass four values for a, two of them substitutions that resolve to objects: all four merge in order 021-later-object-replaces-earlier-concatenation pass pass pass pass fail pass pass a string concatenation is a non-object value, so the later object replaces it rather than merging with it 022-scalar-then-empty-object-then-object pass pass pass pass fail pass pass the number cuts the merge off from anything before it; the empty object after it and the object after that still merge, and the empty one contributes nothing 023-empty-object-then-substitution pass pass pass pass pass pass pass an empty object followed by a substitution that resolves to an object merges into that object; the empty layer contributes nothing 024-merge-stack-nested-inside-a-merge-stack pass pass fail pass pass pass pass the object layer of a's merge contains a field b that is itself a merge of a substitution and an object; both levels merge 025-lookup-into-object-that-replaced-a-list fail pass pass pass pass pass pass the object replaces the earlier list, so a.m exists and p is 1 whatever order the keys are resolved in. typesafe/config fails when the reader p is resolved before a (it iterates p before a); the same file with the reader named zzz resolvesjava: unsupported 026-missing-key-in-object-that-replaced-a-list pass pass pass pass pass pass pass the same shape with a key the object does not have: the list below it is not consulted, a.b is undefined and the optional p is not created 027-nested-object-then-scalar-inside-merge pass pass pass pass pass pass pass inside a merge the same rules apply per field: b is an object in the first and a number in the second, so the later value replaces it 028-null-in-second-object-wins pass pass pass pass pass pass pass null is a non-object value, so the second object's null is used for b rather than dropped duration-format9 casesJava 9/9 · hocon2 9/9 · pulse-pyhocon 9/9 · py.hocon 9/9 · pyhocon 2/9 · rs.hocon 9/9 · zig-hocon 9/9
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-bare-number-is-milliseconds pass pass pass pass pass pass pass bare numbers are taken to be in milliseconds already 002-uppercase-unit-is-illegal pass pass pass pass pass pass pass the supported unit strings for duration are case-sensitive and must be lowercase, so an uppercase unit is not one of the supported strings 003-nanoseconds-spellings pass pass pass pass fail pass pass exactly ns, nano, nanos, nanosecond, nanoseconds are the supported unit strings for nanoseconds 004-microseconds-spellings pass pass pass pass fail pass pass exactly us, micro, micros, microsecond, microseconds are the supported unit strings for microseconds 005-milliseconds-spellings pass pass pass pass fail pass pass exactly ms, milli, millis, millisecond, milliseconds are the supported unit strings for milliseconds 006-seconds-spellings pass pass pass pass fail pass pass exactly s, second, seconds are the supported unit strings for seconds 007-minutes-spellings pass pass pass pass fail pass pass exactly m, minute, minutes are the supported unit strings for minutes 008-hours-spellings pass pass pass pass fail pass pass exactly h, hour, hours are the supported unit strings for hours 009-days-spellings pass pass pass pass fail pass pass exactly d, day, days are the supported unit strings for days in the duration format include-file-formats23 casesJava 19/23 · hocon2 3/23 · pulse-pyhocon 3/23 · py.hocon 13/23 · pyhocon 3/23 · rs.hocon 14/23 · zig-hocon 23/23
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-basename-loads-every-extension pass fail fail pass fail pass pass a name without an extension is a basename: all of base.properties, base.json and base.conf exist, so all three are loaded and merged, properties first and HOCON last, so the .conf value wins where they overlap 002-basename-with-only-conf pass fail fail pass fail pass pass a basename with only base.conf present loads just that file 003-basename-with-only-properties pass fail fail pass fail pass pass a basename with only base.properties present loads it as Java properties, whose values are all strings 004-basename-with-no-file-is-ignored pass pass pass pass pass pass pass a basename that exists with no extension at all is a missing include, and a missing include is ignored 005-required-basename-with-no-file-is-error pass pass pass pass pass pass pass required() makes the missing basename an error 006-json-extension-means-json-syntax fail fail fail fail fail fail pass a file named .json is in JSON format, and an unquoted value is not JSON. typesafe/config parses a .json named outright with the HOCON parser, so the file is accepted; the same file found through a basename is rejectedjava: lenient 007-properties-extension-means-properties-syntax fail fail fail pass fail pass pass a file named .properties is in Java properties format, where the first = separates key from value and values are strings. typesafe/config parses a .properties named outright with the HOCON parser and fails on the second =; the same file found through a basename is read as propertiesjava: unsupported 008-properties-keys-are-paths pass fail fail pass fail pass pass in a properties file a dotted key is a path, so a.b and a.c build an object at a; the spec's Java properties mapping says the object must always win when a key is both a value and a parent, whatever the order; values are strings 010-json-file-with-every-value-type pass fail fail pass fail pass pass a .json file found through a basename is parsed as JSON and contributes its object 011-json-rejects-unquoted-values pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; an unquoted value is a HOCON extension, not JSON 012-json-rejects-substitutions pass fail fail pass fail pass pass the .json file is found through a basename, so it is parsed as JSON; ${} is a HOCON extension, not JSON 013-json-rejects-trailing-comma pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; JSON has no trailing commas 014-json-rejects-duplicate-keys pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; the spec's own footing is that duplicate keys are invalid JSON (L194-195), so the second a is an error rather than an override 015-json-array-needs-commas pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; JSON separates array elements with commas only; whitespace does not concatenate or separate 016-json-rejects-empty-document pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; an empty document is not JSON, although it is an empty HOCON object 017-json-rejects-scalar-root pass fail fail pass fail pass pass the .json file is found through a basename, so it is parsed as JSON; a JSON document included here must be an object; a bare number is neither an object nor valid as an included root 019-json-key-must-be-a-string pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; a JSON key is a quoted string; a number cannot be a key 020-json-newline-is-not-a-separator pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; in JSON a newline between fields does not stand in for the comma 022-json-dotted-key-is-one-key pass fail fail pass fail pass pass the .json file is found through a basename, so it is parsed as JSON; a JSON key is a plain string, not a path expression, so a.b stays a single key 023-json-leading-zero-illegal fail fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; JSON forbids a leading zero on a number. typesafe/config reads 01 as 1, the same leniency it shows in HOCONjava: lenient 024-unknown-extension-is-not-a-basename fail pass pass fail pass pass pass x.txt has an extension, so it is not a basename to probe; it is a file adjacent to the including one and of the same type. The spec does not define what counts as an extension, and this row reads any dotted suffix as one. typesafe/config probes every name that does not end in .conf, .json or .properties as a basename and finds nothingjava: diverges 025-json-is-parsed-after-properties pass fail fail pass fail pass pass base.properties and base.json both exist; JSON is parsed next-to-last, after properties, so its value for a wins 026-conf-is-parsed-after-json pass fail fail pass fail pass pass base.json and base.conf both exist; HOCON is parsed last, after JSON, so its value for a wins include-locating5 casesJava 5/5 · hocon2 4/5 · pulse-pyhocon 4/5 · py.hocon 5/5 · pyhocon 4/5 · rs.hocon 5/5 · zig-hocon 5/5
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-include-string-that-is-a-url-is-a-url pass pass pass pass pass pass pass a quoted include name with a known URL scheme is a URL, not a file adjacent to this one. With nothing behind the URL the include is ignored, so a parser that read the string as a filename would also get {}; the row records the rule, it cannot distinguish the two readings 002-nested-include-is-relative-to-the-including-file pass pass pass pass pass pass pass inner.conf includes x.conf, which is found beside inner.conf, not beside main.conf and not in the process's working directory 003-plain-name-is-a-file-beside-the-including-one pass pass pass pass pass pass pass x.conf is not a URL, so it names a file adjacent to main.conf 004-absolute-path-stays-absolute pass pass pass pass pass pass pass an absolute path is loaded as such, so the file of the same name beside main.conf is not it; the absolute file does not exist and the include is ignored 005-file-argument-is-not-relative-to-the-including-file pass fail fail pass fail pass pass a file() argument is not interpreted relative to the including file, so the marker beside sub/inner.conf is not loaded; nothing of that name exists elsewhere and the include is ignored include-merging16 casesJava 15/16 · hocon2 15/16 · pulse-pyhocon 11/16 · py.hocon 12/16 · pyhocon 15/16 · rs.hocon 12/16 · zig-hocon 16/16
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-included-keys-merge-in-place pass pass pass pass pass pass pass the keys from the included object are conceptually substituted for the include statement in the including file, so the included field lands between the fields around it and merges by the usual duplicate-key rules 002-included-conf-array-root-is-error pass pass pass pass pass pass pass an included file must contain an object, not an array; if it contains an array as the root value it is invalid and an error should be generated 003-included-json-array-root-is-error pass pass pass pass pass pass pass both JSON and HOCON allow arrays as root values in a document, but an included file must contain an object, so a JSON file with an array root is an error when included 004-include-inside-nested-object pass pass pass pass pass pass pass the keys from the included root object are conceptually substituted for the include statement in the including file, so an include inside a nested object contributes its keys to that object 005-included-scalar-overrides-earlier-key pass pass pass pass pass pass pass if a key in the included object occurred prior to the include statement in the including object, the included key's value overrides the earlier value, exactly as with duplicate keys found in a single file 006-included-object-merges-with-earlier-object pass pass pass pass pass pass pass if a key in the included object occurred prior to the include statement and both values are objects, the included value merges with the earlier value, exactly as with duplicate keys found in a single file 007-included-scalar-replaces-earlier-object pass pass pass pass pass pass pass an included key's value overrides the earlier value exactly as with duplicate keys in a single file, so an included non-object replaces an earlier object rather than merging with it 008-later-key-overrides-included-key pass pass pass pass pass pass pass if the including file repeats a key from an earlier-included object, the including file's value overrides the one from the included file 009-later-object-merges-with-included-object pass pass pass pass pass pass pass if the including file repeats a key from an earlier-included object and both values are objects, the including file's value merges with the one from the included file 010-include-inside-an-object-inside-a-list pass pass pass pass pass pass pass an include statement may stand wherever a field may, including in an object that is a list element 011-include-inside-a-list-with-a-substitution fail pass pass pass pass pass pass the included file may contain substitutions like any other; typesafe/config refuses an include nested in a list when the included file is not fully resolvedjava: unsupported 012-included-merge-stack-over-earlier-object pass pass fail fail pass fail pass the included file leaves a as an object merged with a substitution; that whole stack merges with the object the including file set earlier 013-included-merge-stack-over-earlier-substitution pass pass fail fail pass fail pass the earlier value is itself a substitution; once everything resolves to objects, all three layers merge 014-included-merge-stack-over-earlier-scalar pass pass fail pass pass pass pass the earlier value is a number, so the included object stack replaces it and merges only within itself 015-included-object-on-top-of-substitution-over-earlier-substitution pass pass fail fail pass fail pass the same as 013 with the included stack the other way up, an object over a substitution; the result does not depend on which layer is on top 016-plus-equals-across-include-keeps-order pass fail fail fail fail fail pass the included keys stand where the include is, so its two += come first and the file's two append after them (lightbend/config#426) include-missing-and-required6 casesJava 6/6 · hocon2 6/6 · pulse-pyhocon 6/6 · py.hocon 6/6 · pyhocon 6/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-missing-include-ignored pass pass pass pass pass pass pass by default, if an included file does not exist the include statement is silently ignored, so the keys around it parse as if the line were not there 002-missing-include-is-empty-object pass pass pass pass pass pass pass a missing include is treated as if the included file contained only an empty object, so an object whose only content is that include stays an empty object 003-missing-file-include-ignored pass pass pass pass pass pass pass the silent-ignore default applies to an explicit file() include as well: a file that does not exist contributes nothing and parsing continues 004-required-missing-is-error pass pass pass pass pass pass pass when the included resource name is wrapped with required(), file parsing fails with an error if the resource cannot be resolved 005-required-file-missing-is-error pass pass pass pass pass pass pass required() may also wrap the file() form, and a required file that cannot be resolved is a parse error rather than an empty object 006-required-present-is-included pass pass pass pass pass pass pass required() only makes the include mandatory; a resource that does resolve is included exactly as an unwrapped include would be include-substitution20 casesJava 17/20 · hocon2 15/20 · pulse-pyhocon 11/20 · py.hocon 14/20 · pyhocon 12/20 · rs.hocon 14/20 · zig-hocon 20/20
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-substitution-relativized-to-include-scope pass pass fail pass fail pass pass a substitution in an included file must be fixed up to be relative to the app's configuration root, so ${x} in a file included at key a becomes ${a.x} 002-relativized-path-sees-later-redefinition pass pass fail pass fail pass pass substitution happens after parsing the whole configuration, so the ${x} fixed up to ${a.x} evaluates to the root's later redefinition 42 rather than the included file's 10 003-original-path-tried-as-fallback pass pass pass pass pass pass pass it is not enough to only look up the fixed-up path; when a.x is undefined the original path x is looked up relative to the root of the including configuration 004-relativized-path-tried-before-original pass fail fail pass fail pass pass substitutions in included files are looked up first relative to the root of the included file and only second relative to the root of the including configuration, so a.x wins over x 005-neither-path-defined-is-error pass pass pass pass pass pass pass only the fixed-up path a.x and the original path x are looked up; with neither defined the substitution is undefined and the configuration is invalid 006-optional-substitution-with-neither-path-is-omitted pass pass pass pass pass pass pass an optional substitution in an included file is looked up at the fixed-up path a.x and at the original path x; with neither defined the field is omitted rather than an error 007-nested-include-relativized-through-both-levels pass pass fail pass fail pass pass the fix-up is relative to the app's configuration root, so ${x} in a file included at b inside a file included at a becomes ${a.b.x} 008-original-path-falls-back-to-environment pass fail pass pass pass pass pass an included file may intend to refer to the application's root config, for example to get a value from the environment; the original path CONFORMANCE_HOME is looked up and falls back to the environment variable 009-undefined-optional-at-fixed-up-path-falls-back fail pass pass fail pass fail pass an undefined ${?nope} means common.x.y is never created, so ${x.y} is not found at the fixed-up path and is looked up at the original path x.y. typesafe/config treats the vanished value as a miss that ends the lookup -- except in some root key orders (see 014); lightbend/config#863 falls backjava: unsupported 010-undefined-optional-at-fixed-up-path-with-no-original pass pass pass fail pass fail pass with neither common.x.y nor x.y defined, the required ${x.y} in the included file is unresolved 011-optional-reference-falls-back-too fail pass pass fail pass fail pass the optional form of 009 falls back the same way; typesafe/config leaves a out instead (lightbend/config#863)java: diverges 012-earlier-value-at-fixed-up-path-wins pass fail fail pass fail pass pass an undefined optional that would override keeps the previous value, so common.x.y is 9 and found at the fixed-up path; the original path is not consulted 013-missing-leaf-under-existing-object-falls-back pass pass pass pass pass pass pass the metamorphic twin of 009 without the optional: common.x exists, common.x.y does not, so x.y is looked up at the original path 014-unrelated-key-does-not-change-the-result fail pass pass fail pass fail pass an unrelated key that reads common.x must not change what common.a resolves to; in typesafe/config this input fails while the same input with the key named o24bbd resolves to 0 (lightbend/config#863)java: unsupported 015-undefined-at-both-paths-is-error pass pass fail fail pass fail pass when the original path is itself an undefined optional, neither lookup finds a value and the required ${x.y} is an error 016-substitution-inside-a-concatenation-is-fixed-up pass pass pass pass pass pass pass a substitution that is one piece of a concatenation in the included file is fixed up like a bare one: p.x is not set, so the original path x is used 017-substitution-inside-a-list-is-fixed-up pass pass pass pass pass pass pass a substitution that is a list element in the included file is fixed up as well 018-substitution-in-a-merge-stack-is-fixed-up pass pass fail pass fail pass pass an object merged with a substitution in the included file keeps working after the fix-up, whichever of the two is on top: q is found at p.q 019-null-at-fixed-up-path-is-found pass fail fail pass fail pass pass a.x is present, set to null, so the lookup at the fixed-up path succeeds and the original path x is not consulted 020-include-two-objects-deep-is-fixed-up pass fail fail fail fail fail pass included at a.nested, the file's ${x} is fixed up to ${a.nested.x}, two objects deep in one file (lightbend/config#136) include-syntax15 casesJava 15/15 · hocon2 13/15 · pulse-pyhocon 12/15 · py.hocon 13/15 · pyhocon 13/15 · rs.hocon 14/15 · zig-hocon 15/15
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-include-later-in-key-is-literal pass pass pass pass pass pass pass unquoted include has no special meaning if it is not the start of a key's path expression; it may appear later in the key, so { foo include : 42 } is equivalent to { "foo include" : 42 } 002-include-as-object-value-is-string pass pass pass pass pass pass pass unquoted include has no special meaning if it is not the start of a key's path expression; as an object value it is the string "include" 003-include-as-array-element-is-string pass pass pass pass pass pass pass unquoted include has no special meaning if it is not the start of a key's path expression; as an array element it is the string "include". the spec's example is the root array [ include ]; it is wrapped in a field here so the row does not depend on array-root support 004-no-concatenation-on-include-argument pass pass fail pass pass pass pass value concatenation is NOT performed on the argument to include; the argument must be a single quoted string, so a second quoted string after the resource name is an error whether or not the first resource exists 005-no-substitution-in-include-argument pass pass pass pass pass pass pass no substitutions are allowed in the argument to include, which must be a single quoted string; ${x} after include is an error even though x is defined, so an implementation that resolves the argument first still fails this row 006-include-argument-must-be-quoted pass pass pass pass pass pass pass if an unquoted include at the start of a key is followed by anything other than a single quoted string or the url()/file()/classpath() syntax, it is invalid and an error should be generated; the argument may not be an unquoted string 007-quoted-include-is-ordinary-key pass pass pass pass pass pass pass only unquoted include is special; quoting it gives a key that starts with the word include, so { "include" : 42 } is an ordinary field named include 008-file-argument-must-be-quoted pass pass pass pass pass pass pass the argument inside file() must be a single quoted string; an unquoted name is a syntax error 009-file-missing-close-parenthesis pass pass pass fail pass pass pass file("...") must be closed; the line ends before the parenthesis 010-required-missing-close-parenthesis pass pass pass fail pass fail pass required(...) must be closed too 011-url-argument-must-be-a-url pass pass pass pass pass pass pass the string inside url() is always an absolute URL, with a scheme; x.conf has none, so it is an error, as lightbend/config#887 settled 013-newlines-between-include-and-name pass pass fail pass fail pass pass any amount of whitespace, newlines included, may stand between include and the resource name 014-whitespace-inside-parentheses pass fail pass pass pass pass pass whitespace inside the parentheses, outside the quotes, is allowed 015-include-followed-by-separator-is-not-a-key pass pass fail pass fail pass pass an unquoted include at the start of a key is never a key; followed by a separator instead of a quoted name it is an include with an invalid argument 016-comma-separates-include-from-next-field pass fail pass pass pass pass pass an include stands where a field would, so the usual comma separates it from the field that follows key-value-separator6 casesJava 6/6 · hocon2 6/6 · pulse-pyhocon 6/6 · py.hocon 6/6 · pyhocon 6/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-equals-as-separator pass pass pass pass pass pass pass the = character can be used anywhere JSON allows :, to separate keys from values 002-omitted-separator-before-object pass pass pass pass pass pass pass if a key is followed by {, the : or = may be omitted; the spec's own example is "foo" {} meaning "foo" : {} 003-omitted-separator-unquoted-key pass pass pass pass pass pass pass the separator-omission rule before { applies to unquoted keys too, not just the quoted example in the spec 004-value-on-the-line-after-the-separator pass pass pass pass pass pass pass = stands wherever JSON allows :, and JSON allows any whitespace, newlines included, between the separator and the value 005-separator-cannot-be-omitted-before-array pass pass pass pass pass pass pass the separator may be omitted only before {; a key followed directly by [ is invalid 006-newline-between-key-and-brace pass pass pass pass pass pass pass the separator may be omitted before {, and a newline before the { is whitespace like any other between a key and its value, as JSON allows one before : list-values-from-environment-variables6 casesJava 6/6 · hocon2 1/6 · pulse-pyhocon 3/6 · py.hocon 6/6 · pyhocon 3/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-list-from-indexed-variables pass fail fail pass fail pass pass the [] suffix collects CONFORMANCE_LIST_0, CONFORMANCE_LIST_1, ... until an index is missing, into a list of strings 002-optional-list-with-no-elements-is-undefined pass fail pass pass pass pass pass with no CONFORMANCE_LIST_0 set, an optional ${?X[]} is undefined and the field is not created 003-required-list-with-no-elements-is-error pass pass pass pass pass pass pass with no CONFORMANCE_LIST_0 set, a required ${X[]} is an error 004-collection-stops-at-the-first-gap pass fail fail pass fail pass pass indices are collected from 0 until one is missing, so with _0 and _2 set the list holds only the first 005-elements-are-strings pass fail fail pass fail pass pass an element taken from the environment is a string, so 1 stays "1" 006-config-keys-are-not-collected pass fail pass pass pass pass pass the [] suffix collects environment variables only, so the config key a_0 is not gathered and the optional substitution is undefined multi-line-strings9 casesJava 9/9 · hocon2 9/9 · pulse-pyhocon 6/9 · py.hocon 9/9 · pyhocon 9/9 · rs.hocon 9/9 · zig-hocon 9/9
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-basic-triple-quote pass pass pass pass pass pass pass the three-character sequence """ starts a multi-line string whose unicode characters are used unmodified to create the string value 002-newlines-and-whitespace-preserved pass pass pass pass pass pass pass newlines and whitespace inside a multi-line string receive no special treatment and are kept in the value 003-unicode-escape-not-interpreted pass pass pass pass pass pass pass unlike JSON quoted strings, a triple-quoted string does not interpret escapes, so the six characters \u0041 stay as written rather than becoming A 004-extra-quote-becomes-part-of-string pass pass pass pass pass pass pass any sequence of at least three quotes ends the multi-line string and any extra quotes are part of the string, so HOCON works like Scala's four-character string foo" rather than Python's syntax error 005-five-closing-quotes-two-extra pass pass fail pass pass pass pass a closing run of five quotes still only needs three to end the string, so both extra quotes are appended to the string value 006-unterminated-is-illegal pass pass pass pass pass pass pass a multi-line string opened with a three-character quote sequence that never reaches a closing """ is not a legal value 007-fewer-than-three-quotes-does-not-close pass pass fail pass pass pass pass only a run of at least three quote characters ends the multi-line string, so the single embedded quote pairs around hi do not terminate it early 008-backslash-kept-literally pass pass fail pass pass pass pass a backslash sequence inside a triple-quoted string is used unmodified, not as an escape 009-six-quotes-are-an-empty-string pass pass pass pass pass pass pass the three opening quotes are followed at once by the closing run, so the string is empty numerically-indexed-objects-to-arrays7 casesJava 7/7 · hocon2 3/7 · pulse-pyhocon 3/7 · py.hocon 7/7 · pyhocon 3/7 · rs.hocon 7/7 · zig-hocon 7/7
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-object-with-numeric-keys-stays-object pass pass pass pass pass pass pass the conversion should be done lazily when required to avoid a type error, not eagerly anytime an object has numeric keys, so a bare object with numeric keys stays an object 003-concatenation-converts-numeric-object-to-array pass fail fail pass fail pass pass the conversion should be done in a concatenation when a list is expected and an object with numeric keys is found, so the trailing object concatenates onto the array as elements x, y 004-concatenation-ignores-non-integer-keys pass fail fail pass fail pass pass the conversion should ignore any keys which do not parse as positive integers, so key "foo" is dropped and only "0" contributes to the resulting array 005-empty-object-concatenation-not-converted pass pass pass pass pass pass pass the conversion should not occur if the object is empty, so an empty object cannot satisfy a list-expected concatenation and this is illegal 006-object-without-integer-keys-not-converted pass pass pass pass pass pass pass the conversion should not occur if the object has no keys which parse as positive integers, so this object cannot satisfy a list-expected concatenation and this is illegal 007-sparse-integer-keys-sorted-and-reindexed pass fail fail pass fail pass pass the conversion should sort by the integer value of each key and then build the array; missing indices such as "1" are eliminated rather than left as gaps, so keys "0" and "2" become a two-element array 008-negative-key-ignored-in-conversion pass fail fail pass fail pass pass the conversion ignores any keys which do not parse as positive integers, and "-1" is not a positive integer, so it is dropped and only key "0" contributes omit-root-braces14 casesJava 10/14 · hocon2 12/14 · pulse-pyhocon 14/14 · py.hocon 8/14 · pyhocon 14/14 · rs.hocon 8/14 · zig-hocon 14/14
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-implicit-root-object pass pass pass pass pass pass pass if the file does not begin with [ or {, it is parsed as if enclosed with {} curly braces 002-explicit-root-object-unaffected pass pass pass pass pass pass pass the implicit-wrap rule only applies when the file does not begin with [ or {; a file already beginning with { is parsed as ordinary JSON-style object 003-missing-open-brace-with-close-brace-illegal pass pass pass fail pass fail pass a HOCON file is invalid if it omits the opening { but still has a closing }, since the curly braces must be balanced 004-empty-file pass fail pass pass pass pass pass an empty file does not begin with a square bracket or a curly brace, so it is parsed as if enclosed in {}: the empty object 005-bare-string-root-illegal pass pass pass pass pass pass pass the file does not begin with a bracket or brace, so it is wrapped in {} and becomes a key with no separator and no value 006-array-root-unaffected fail pass pass fail pass fail pass the implicit {} wrap fires only when the file does not begin with a square bracket or curly brace, so a file starting with [ keeps its array root; the include section says so outright -- 'both JSON and HOCON allow arrays as root values in a document' (the reason an included file may not be one). typesafe/config has no array-rooted config at all and rejects the document as LIST rather than object.java: unsupported 008-empty-array-root fail pass pass fail pass fail pass an empty array is still an array root, and the wrap rule looks at the opening bracket, not at whether the document has contentjava: unsupported 009-comment-before-array-root fail pass pass fail pass fail pass the {} wrap fires only for a file that does not begin with a bracket or brace, and a leading comment does not make it one -- both typesafe/config and pyhocon read the first token, not the first byte. the spec's wording is loose here; this row records the consensus readingjava: unsupported 010-blank-line-before-array-root fail pass pass fail pass fail pass leading whitespace does not trigger the {} wrap either, for the same reason as a leading commentjava: unsupported 011-trailing-array-after-root-array pass pass pass pass pass pass pass two arrays side by side concatenate when only non-newline whitespace separates them, which would make this document [1,2,3]; but a document is one array or one object at the root, and nothing in the spec lets a root value be a concatenation of two. typesafe/config, pyhocon and hocon-rs all reject it 012-close-brace-after-value-on-one-line-illegal pass pass pass fail pass fail pass a close brace with no open brace is invalid wherever it stands, on the same line as a value as much as on its own 013-open-brace-never-closed-illegal pass pass pass pass pass pass pass an opening brace with no closing brace is unbalanced 014-comment-only-file pass fail pass pass pass pass pass a file holding only a comment does not begin with a bracket or brace, so it is the empty object 015-bare-number-root-illegal pass pass pass pass pass pass pass the file does not begin with { or [, so it is read as if wrapped in braces, and a bare number there is a key with no value path-expressions20 casesJava 20/20 · hocon2 19/20 · pulse-pyhocon 16/20 · py.hocon 19/20 · pyhocon 13/20 · rs.hocon 19/20 · zig-hocon 20/20
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-quoted-dot-has-no-meaning pass pass pass pass pass pass pass a dot inside a quoted string has no special meaning as a path separator, so a quoted "a.b" key is a single-element path 002-mixed-quoted-and-unquoted-path pass pass fail pass fail pass pass foo.bar."hello.world" is a path with three elements: foo, bar, and hello.world, since unquoted dots are path separators but a quoted dot is not 003-number-then-unquoted-string pass pass pass pass pass pass pass 10.0foo is a number then unquoted string foo, giving the two-element path 10 and 0foo, because a dot inside a number still counts as a path separator 004-unquoted-string-with-dot pass pass pass pass pass pass pass foo10.0 is an unquoted string with a dot in it, giving the two-element path foo10 and 0 005-unquoted-concatenated-with-quoted-number pass pass fail fail fail fail pass foo followed by quoted "10.0" is an unquoted then a quoted string which concatenate, giving a single-element path 006-all-numeric-path pass pass pass pass pass pass pass dots in numbers count as path separators, so 1.2.3 is the three-element path with elements 1, 2, 3 008-value-concatenation-keeps-boolean pass pass pass pass pass pass pass unlike a path expression, an array or element value consisting of the single value true is a value concatenation and retains its character as a boolean 009-empty-path-element-quoted-is-valid pass pass fail pass fail pass pass a path element that is an empty string must be quoted; a."".b is a valid three-element path whose middle element is the empty string 010-consecutive-dots-invalid pass pass pass pass fail pass pass an unquoted empty path element is invalid, so a..b must generate an error 011-path-starting-with-dot-invalid pass pass pass pass fail pass pass a path that starts with a dot is invalid and should generate an error 012-path-ending-with-dot-invalid pass fail pass pass fail pass pass a path that ends with a dot is invalid and should generate an error 015-dotted-path-in-substitution pass pass pass pass pass pass pass path expressions appear in substitutions like ${foo.bar}, where the unquoted dot separates foo and bar into two path elements, so the lookup walks key foo then key bar 016-number-keeps-original-text-in-path pass pass pass pass pass pass pass a number in a path expression must keep its original string representation as it appeared in the file, so 1.10 is the two-element path 1 and 10, not the 1 and 1 a generic number-to-string conversion would give 017-whitespace-concatenation-in-path pass pass pass pass pass pass pass path expressions are syntactically identical to a value concatenation, so the whitespace-separated a b concatenates into one element and the unquoted dot then splits off c, giving the two-element path a b and c 018-quoted-element-in-substitution-path pass pass pass pass pass pass pass path expressions appear in substitutions as well as keys, and inside quoted strings a dot has no special meaning there either, so ${foo."bar.baz"} looks up key foo then the single key bar.baz 019-empty-element-in-substitution-path-invalid pass pass fail pass fail pass pass an unquoted empty path element is invalid in a substitution just as in a key, so ${a..b} must generate an error rather than collapsing to ${a.b} (a.b is defined so a lenient implementation would produce a value and fail) 020-negative-leading-dot-key-is-a-path pass pass pass pass pass pass pass a dot in a key separates path elements whether or not the text around it would be a number, so -.33 names - then 33; numbers in paths keep their original text 021-quoted-dot-in-substitution-path-is-literal pass pass pass pass pass pass pass inside quotes a dot is not a separator, so ${"b.c"} names one field called b.c, which does not exist; it does not reach b's field c 022-keyword-in-substitution-path-is-a-string pass pass pass pass pass pass pass a path expression is always a string, so true in a substitution names the key true rather than a boolean 023-substitution-in-dotted-key-illegal pass pass pass pass pass pass pass a key is a path expression, and a path expression may not contain a substitution, not even as a later element of a dotted key paths-as-keys11 casesJava 10/11 · hocon2 10/11 · pulse-pyhocon 9/11 · py.hocon 11/11 · pyhocon 9/11 · rs.hocon 11/11 · zig-hocon 11/11
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-two-element-path-expands pass pass pass pass pass pass pass a key that is a multi-element path expands to a nested object per element, the last element combined with the value becomes a field in the most-nested object: foo.bar:42 is equivalent to foo{bar:42} 002-three-element-path-expands pass pass pass pass pass pass pass a three-element path key expands through two levels of nesting: foo.bar.baz:42 is equivalent to foo{bar{baz:42}} 003-merge-sibling-paths pass pass pass pass pass pass pass the objects created by expanding path keys are merged in the usual way, so a.x:42, a.y:43 is equivalent to a{x:42,y:43} 004-whitespace-in-key pass pass pass pass pass pass pass because path expressions work like value concatenations, whitespace is allowed in keys: a b c:42 is equivalent to "a b c":42 005-unquoted-true-key-becomes-string pass pass pass pass pass pass pass path expressions are always converted to strings, so the unquoted boolean-looking key true:42 is "true":42 006-unquoted-number-key-becomes-string pass pass pass pass pass pass pass path expressions are always converted to strings, so the unquoted numeric key 3:42 is "3":42 007-decimal-key-splits-on-dot pass pass pass pass pass pass pass a dot in an unquoted key is a path separator even when it looks like a decimal number, so 3.14:42 is "3":{"14":42} 008-include-cannot-begin-key fail pass fail pass fail pass pass the unquoted string include may not begin a path expression in a key: where an object key is expected, include is not read as a key at all and must be followed by a quoted resource name, so include.foo is an error. typesafe/config recognises the keyword only as a bare include token -- include.foo tokenizes as one unquoted string, so it is accepted as the path include.foo, while include = 42 is rejected under the very rule the spec states.java: lenient 009-include-not-at-start-is-ordinary pass fail fail pass fail pass pass only the unquoted string include that begins a path expression in a key is special: include later in a key (foo.include) and a quoted "include" beginning one are ordinary path elements 010-path-key-merges-with-brace-object pass pass pass pass pass pass pass the object created by expanding a path key is merged in the usual way with an object written in braces at the same key, whichever form comes first: a{x:1}, a.y:2 and b.x:1, b{y:2} are both equivalent to {x:1,y:2} 011-whitespace-around-a-dot-stays-in-the-elements pass pass pass pass pass pass pass whitespace inside a key is part of the key, and a dot splits the key into elements, so the elements here are 'a ' and ' b' with their spaces period-format7 casesJava 7/7 · hocon2 7/7 · pulse-pyhocon 7/7 · py.hocon 7/7 · pyhocon 3/7 · rs.hocon 7/7 · zig-hocon 7/7
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-bare-number-is-days pass pass pass pass pass pass pass for getPeriod(), bare numbers are taken to be in days, unlike getDuration() where bare numbers are milliseconds 002-uppercase-unit-is-illegal pass pass pass pass pass pass pass the supported unit strings for period are case-sensitive and must be lowercase, so an uppercase unit is not one of the supported strings 003-days-spellings pass pass pass pass fail pass pass exactly d, day, days are the supported unit strings for days in the period format 004-weeks-spellings pass pass pass pass fail pass pass exactly w, week, weeks are the supported unit strings for weeks 005-months-spellings pass pass pass pass fail pass pass exactly m, mo, month, months are the supported unit strings for months 006-years-spellings pass pass pass pass pass pass pass exactly y, year, years are the supported unit strings for years 007-month-m-ambiguous-with-duration-minutes pass pass pass pass fail pass pass the spec notes that getTemporal() callers should prefer mo over m for months, since m is also the duration unit for minutes and getTemporal() may return either a Duration or a Period plus-equals-field-separator13 casesJava 7/13 · hocon2 6/13 · pulse-pyhocon 0/13 · py.hocon 8/13 · pyhocon 0/13 · rs.hocon 8/13 · zig-hocon 13/13
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-plus-equals-desugars-to-optional-self-ref-array pass pass fail pass fail pass pass a += b transforms into a = ${?a} [b], and because the fallback is optional (${?a} not ${a}) this is legal even as the first mention of a in the file 002-plus-equals-appends-to-existing-array pass pass fail pass fail pass pass += appends an element to a previous array 003-plus-equals-errors-on-non-array-previous-value pass pass fail pass fail pass pass if the previous value was not an array, += results in an error just as the long form a = ${?a} [b] would with a non-array a 004-plus-equals-twice pass pass fail pass fail pass pass each += appends to the value the previous line left, so two of them append two elements 005-plus-equals-in-nested-object-with-substitution pass fail fail pass fail pass pass inside an object, += desugars to a self-reference by the field's full path o.a; the appended element may itself be a substitution 006-plus-equals-inside-a-list-element fail pass fail pass fail pass pass b has no previous value, so b += 1 is b = ${?b} [1] with the optional part undefined: [1]. typesafe/config refuses += inside a list because its desugared path cannot name a list elementjava: unsupported 007-plus-equals-under-a-dotted-key pass fail fail pass fail pass pass the self-reference += desugars to uses the full path of the field, o.p.a, even when the enclosing key was written as the path o.p 008-plus-equals-with-an-object-containing-a-merge fail pass fail pass fail pass pass l += v is l = ${?l} [v]; the appended object holds a merge of an object and a substitution, which resolves inside the new list. typesafe/config fails with an internal error (BugOrBroken) for the same reason as array-and-object-concatenation/018java: unsupported 009-appends-through-object-concatenation fail fail fail fail fail fail pass top.arr += four is top.arr = ${?top.arr} [four], and ${x} { ... } is a merge, so the self-reference looks back at the overridden x.arr, as it does with top : ${x} and top { arr += four } written as two fields; typesafe/config drops the inherited element (lightbend/config#608, fix proposed in #882)java: diverges 010-appends-through-chained-object-concatenations fail fail fail fail fail fail pass each += looks back through its own concatenation, so y.l appends to x.l and z.l to the resolved y.l; typesafe/config keeps only the last element at each stepjava: diverges 011-appends-deep-inside-object-concatenation fail fail fail fail fail fail pass the += is three objects down, top.a.b.c = ${?top.a.b.c} [2], and the merge reaches it: the self-reference looks back at x.a.b.c; typesafe/config drops itjava: diverges 012-appends-to-a-list-defined-in-object-concatenation fail fail fail fail fail fail pass y.l += 3 overrides the y.l that the earlier concatenation defines, so its self-reference looks back at [1, 2]; typesafe/config yields [3] (lightbend/config#375)java: diverges 013-appends-after-a-substituted-object pass fail fail fail fail fail pass c.x += 1 looks back at c.x, which ${d} gives, and the second += at the first: [1, 2], and d is left as it was (lightbend/config#177) self-referential-examples34 casesJava 27/34 · hocon2 26/34 · pulse-pyhocon 21/34 · py.hocon 23/34 · pyhocon 27/34 · rs.hocon 23/34 · zig-hocon 34/34
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-isolated-self-reference-is-an-error pass pass pass pass pass pass pass in isolation, with no merges involved, a self-referential field is an error because the substitution cannot be resolved 002-self-reference-resolves-to-earlier-merged-value pass pass pass pass pass pass pass when foo:${foo} is merged with an earlier value for foo, the self-reference resolves to that overridden value, so foo ends up { a : 1 } 003-self-reference-before-any-value-is-undefined pass pass fail fail pass fail pass if the self-referential foo:${foo} comes before foo has any value, the reference is undefined, exactly as if it named a path not found in the document, so it is an error even though foo is given a value afterward 004-optional-self-reference-disappears-silently pass pass pass pass pass pass pass because the self-reference error is treated as undefined rather than an intractable cycle, the optional syntax foo:${?foo} makes the field disappear silently instead of erroring 005-substitution-hidden-by-later-non-mergeable-value-is-never-evaluated pass pass pass pass pass pass pass if a substitution is hidden by a later non-object value that could not be merged with it, it is never evaluated and no error is reported, no matter what it would have resolved to; foo ends up 42 006-self-reference-cycle-ignored-when-overridden-by-literal pass pass pass pass pass pass pass the same hiding rule applies to a self-reference cycle: once overridden by a non-mergeable literal, the initial foo:${foo} must simply be ignored, so foo ends up 42 with no error 007-self-reference-in-path-expression-resolves-to-value-below pass pass pass pass pass pass pass a self-reference resolves to the value below it even as part of a path expression, so ${foo.a} refers to { c : 1 } rather than 2, and the final merge is { a : 2, c : 1 } 008-object-may-refer-to-sibling-path-within-itself pass pass pass pass pass pass pass an implementation must allow an object to refer to a path within itself without treating it as a cycle, by resolving only the referenced field rather than recursing the whole enclosing object; bar.baz ends up 42 009-non-cycling-reference-looks-forward-across-merge pass pass pass pass pass pass pass because there is no inherent cycle here, the substitution must look forward including the field's own later merges, so bar.baz ends up 43 once foo is overridden to 43 010-mutually-referring-objects-are-not-self-referential pass pass pass pass pass pass pass mutually-referring objects should work and are not self-referential, so they look forward: bar.a ends up 4 and foo.c ends up 3 011-optional-self-reference-in-concatenation-looks-back pass pass pass pass pass pass pass an optional self-reference in a value concatenation has to look back to an undefined a, so a ends up "foo" rather than "foofoo" 012-mutual-non-self-cycle-is-unresolvable pass pass pass pass pass pass pass the spec gives bar:${foo}, foo:${bar} as an example that is not possible to resolve, since neither lazy evaluation nor looking backward breaks the cycle and neither field is optional 013-multi-step-cycle-is-invalid pass pass pass pass pass pass pass a multi-step loop across three fields (a:${b}, b:${c}, c:${a}) must also be detected as invalid, not just a direct two-field cycle 014-order-dependent-resolution-must-agree-on-one-value pass pass pass pass pass pass pass the order a and b resolve in is undefined: both 1, both 2 or an error are all allowed, but a and b must agree (a=1,b=2 is not permitted) 015-list-hidden-by-substituted-object-is-never-evaluated fail pass pass fail pass fail pass a list merges with nothing, so any later value hides it and it is never evaluated; that the hiding object arrives through ${replacement} changes nothing, since the substitution is required and so either resolves or fails on its own. typesafe/config evaluates the hidden list (lightbend/config#865)java: unsupported 016-object-hidden-by-substituted-scalar-is-never-evaluated pass pass pass pass fail pass pass ${replacement} resolves to 42, a non-object, so the earlier object is hidden and never evaluated, as with the literal in 005 (typesafe/config 1.4.5 evaluated it) 017-object-hidden-by-substituted-concatenation-is-never-evaluated pass pass pass pass fail pass pass a string concatenation is a non-object too, so it hides the earlier object (typesafe/config 1.4.5 evaluated it) 018-object-merged-with-substituted-object-is-evaluated pass pass fail pass pass pass pass a substituted object merges with the earlier object, so its fields are part of the result and ${does-not-exist} is an error 019-list-hidden-by-literal-scalar-is-never-evaluated pass pass pass pass pass pass pass the literal counterpart of 015: a literal number could not be merged with the list, so the list is never evaluated and there is no error 020-object-hidden-by-literal-list-is-never-evaluated pass pass pass pass pass pass pass a literal list is a non-object value, so it hides the earlier object and ${does-not-exist} is never evaluated 021-hidden-list-does-not-depend-on-key-order fail pass pass fail pass fail pass a reader of w.x must not change whether the hidden list is evaluated; only objects merge, so the object hides the list whatever else refers to it. typesafe/config fails here, yet resolves the same file with the reader named o24bbd (lightbend/config#865)java: unsupported 022-undefined-optional-does-not-hide pass pass fail pass pass pass pass an undefined optional that would override leaves the previous value in place, so the list is the result and ${does-not-exist} is evaluated: an error 023-append-does-not-hide pass pass fail pass fail pass pass w += 1 is w = ${?w} [1], which reads the earlier list, so ${does-not-exist} is evaluated: an error 024-substitution-target-is-fetched-lazily pass pass pass pass pass pass pass resolving ${b.c} fetches only b.c; resolving all of b first would reach ${a} and report a cycle that is not there 025-optional-two-field-cycle-is-undefined pass pass fail pass fail pass pass a genuine cycle is an error for required substitutions; with the optional syntax it is a missing value, so neither field is created 026-memoized-by-instance-not-by-path pass pass pass pass pass pass pass the text ${a} appears twice and resolves differently: to x in a's own self-reference and to the final xy in b; memoizing by path instead of by instance would give b the wrong value 027-self-reference-inside-object-concatenation fail fail fail fail pass fail pass c.x is self-referential, and ${d} { ... } merges the object over d, so ${c.x} looks back at the overridden d.x, as with c : ${d} and c { x : ${c.x} [1, 2] } written as two fields; typesafe/config reports a cyclejava: unsupported 028-self-reference-through-two-concatenated-substitutions fail fail fail fail fail fail pass three objects merged in one value, the last overriding both: ${c.x} looks back at what ${d} ${e} gives c.x, and ${e.z} is an ordinary forward reference; typesafe/config reports a cyclejava: unsupported 029-concatenated-object-refers-to-its-mixin fail fail fail fail pass fail pass an object with a substitution inside it is not self-referential, so ${a.hello} looks forward and finds the field the merged ${mixin} gives a; typesafe/config reports a cycle (lightbend/config#586)java: unsupported 030-concatenated-object-sees-its-own-field fail fail fail fail pass fail pass ${top.n} is not self-referential, so it looks forward, including at the field being defined: the merged top.n, 2, not x's 1; typesafe/config reports a cyclejava: unsupported 031-field-hidden-by-a-substituted-object pass fail fail fail pass fail pass the object ${x} gives merges onto the earlier p, and its a = 42 hides the earlier a, a non-object over a substitution, so ${y} is never evaluated (lightbend/config#838) 032-field-hidden-inside-object-concatenation fail fail fail fail pass fail pass ${base} { ... } is a merge, and child.foo = x merges onto it and hides its foo, so ${missing} is never evaluated, as with child = ${base} and child { foo = ${missing} } written as two fields; typesafe/config evaluates it (lightbend/config#812)java: unsupported 033-object-below-a-substitution-looks-forward pass fail fail fail fail fail pass ${bar.foo} is not self-referential, so it looks forward at the final bar.foo, which the object ${r} gives on top: 43, as when the later bar is written out 034-object-over-scalar-hidden-by-later-object pass fail fail fail fail fail pass the two objects merge and the later bar, a string, hides ${override-me}, which is never evaluated; the string under them is simply overridden (lightbend/config#599) self-referential-substitutions5 casesJava 5/5 · hocon2 5/5 · pulse-pyhocon 5/5 · py.hocon 5/5 · pyhocon 5/5 · rs.hocon 5/5 · zig-hocon 5/5
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-path-builds-on-older-value pass pass pass pass pass pass pass a field may look up its own older value before being overridden with a new value based on it, letting path:${path}":d" extend the earlier path:"a:b:c" 002-self-referential-concatenation pass pass pass pass pass pass pass a value concatenation containing a substitution to its own field is self-referential, so ${a} looks back to the earlier x and the result is xbc 003-object-containing-self-reference-is-not-self-referential pass pass pass pass pass pass pass an object with a substitution inside it is not considered self-referential for this purpose, so a:{b:${a}} is an unbreakable cycle that must generate an error 004-array-containing-self-reference-is-not-self-referential pass pass pass pass pass pass pass an array with a substitution inside it is not considered self-referential for this purpose, so a:[${a}] is an unbreakable cycle that must generate an error 006-self-referential-array-concatenation pass pass pass pass pass pass pass the spec lists path:${path} [ /usr/bin ] among the examples of self-referential fields: a value concatenation of a substitution with an array literal is still self-referential size-in-bytes-format5 casesJava 5/5 · hocon2 5/5 · pulse-pyhocon 5/5 · py.hocon 5/5 · pyhocon 4/5 · rs.hocon 5/5 · zig-hocon 5/5
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-bare-number-is-bytes pass pass pass pass pass pass pass bare numbers are taken to be in bytes already 002-single-byte-spellings pass pass pass pass pass pass pass for single bytes, exactly B, b, byte, bytes are supported 003-powers-of-ten-suffixes pass pass pass pass pass pass pass every power-of-10 suffix and its long forms name the same unit family; one table, one rule 004-powers-of-two-suffixes pass pass pass pass fail pass pass every power-of-2 suffix and its long forms name the same unit family; one table, one rule. The E and e forms are quoted because an unquoted 5E is a number token that is not a number (see unquoted-strings/022) 019-single-letter-abbreviation-means-powers-of-two pass pass pass pass pass pass pass the single-letter abbreviations (like K) are ambiguous between powers of two and ten, and this spec follows the java -Xmx / GNU-tools precedent of mapping them to powers of two string-value-concatenation19 casesJava 14/19 · hocon2 18/19 · pulse-pyhocon 15/19 · py.hocon 18/19 · pyhocon 15/19 · rs.hocon 18/19 · zig-hocon 19/19
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-substitution-in-string-concatenation pass pass pass pass pass pass pass string value concatenation supports substitutions inside the concatenated string 002-only-simple-values-participate pass pass pass pass pass pass pass it is invalid for arrays or objects to appear in a string value concatenation, since only simple values participate 003-leading-and-trailing-whitespace-trimmed pass pass pass pass pass pass pass whitespace before the first and after the last simple value is discarded; only the whitespace between the values is kept 004-concatenation-never-spans-newline pass pass pass pass pass pass pass string value concatenations never span a newline, so a bare word on the following line is not appended to the value above it 005-array-element-string-concatenation pass pass pass pass pass pass pass a string value concatenation may appear in any place a string may appear, including array elements 006-boolean-becomes-string-in-concatenation pass pass fail pass fail pass pass true and false become the strings "true" and "false" when they participate in a concatenation 007-null-becomes-string-in-concatenation pass pass fail pass fail pass pass null becomes the string "null" when it participates in a concatenation 008-number-kept-as-written-in-concatenation pass pass pass pass pass pass pass numbers should be kept as they were originally written in the file when they participate in a concatenation, not renormalized 009-single-boolean-not-converted-to-string pass pass pass pass pass pass pass a single value is never converted to a string; true by itself must be parsed as a boolean-typed value, not a string 010-object-then-scalar-drops-the-scalar fail pass pass pass pass pass pass it is invalid for arrays or objects to appear in a string value concatenation; typesafe/config accepts this one silently and discards the trailing x, losing data with no diagnostic, while erroring on the same pair in the other orderjava: lenient 011-array-then-scalar-drops-the-scalar fail pass pass pass pass pass pass the same rule for arrays; typesafe/config again accepts and discards the trailing x, where it errors when the scalar comes firstjava: lenient 012-scalar-then-object-is-an-error pass pass pass pass pass pass pass the same rule with the scalar first, which typesafe/config does reject -- the half of the rule it enforces 014-array-then-unquoted-text-without-space fail pass pass pass pass pass pass an array may not appear in a string value concatenation, with or without whitespace between them; typesafe/config silently drops suffix (lightbend/config#862)java: lenient 015-object-then-unquoted-text-without-space fail pass pass pass pass pass pass the same rule for an object followed directly by text; typesafe/config silently drops suffix (lightbend/config#862)java: lenient 016-text-between-arrays-is-not-dropped fail pass pass pass pass pass pass text between two arrays makes a concatenation of array, string and array, which is invalid; typesafe/config drops the text and concatenates the arrays (lightbend/config#862)java: lenient 020-64-bit-number-kept-as-written pass pass pass pass pass pass pass a number in a concatenation is stringified as it appeared in the file, whatever its size 021-false-becomes-string-in-concatenation pass pass fail pass fail pass pass false becomes the string "false" in a concatenation, as true does 022-quoted-and-unquoted-strings-concatenate pass pass pass pass pass pass pass a quoted string is itself in a concatenation, and the whitespace between it and the unquoted string beside it is kept 023-substituted-number-keeps-its-text pass fail fail fail fail fail pass a substitution in a string concatenation becomes text the way a number written there does, as written: 1e5 and 1.50, not 100000.0 and 1.5 substitution-fallback-to-environment8 casesJava 8/8 · hocon2 8/8 · pulse-pyhocon 7/8 · py.hocon 8/8 · pyhocon 7/8 · rs.hocon 8/8 · zig-hocon 8/8
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-env-fallback-for-undefined-path pass pass pass pass pass pass pass a substitution whose path is not defined in the configuration falls back to an environment variable of the same name 002-required-substitution-resolved-from-env pass pass pass pass pass pass pass a required ${x} substitution that is not present in the configuration tree is searched for in the environment and resolves to the variable's value instead of being an error 003-config-value-wins-over-env pass pass pass pass pass pass pass the environment is only searched when the substitution is not present within the configuration tree, so a value set in the configuration wins over an environment variable of the same name 004-null-in-config-blocks-env-lookup pass pass fail pass fail pass pass setting a key to null in the configuration explicitly blocks looking that substitution up in the environment, so ${CONFORMANCE_X} resolves to null even though the variable is set 005-config-lookup-is-case-sensitive pass pass pass pass pass pass pass the lookup is case-sensitive all the way until the env variable fallback is reached, so a lowercase key does not shadow an uppercase substitution and the environment variable is used 006-empty-env-var-is-empty-string pass pass pass pass pass pass pass an env variable set to the empty string is kept as such, so a required substitution resolves to "" rather than being undefined 007-empty-env-var-creates-optional-field pass pass pass pass pass pass pass an env variable set to the empty string is set to empty string rather than undefined, so an optional ${?x} substitution creates the field with "" instead of dropping it 008-env-vars-always-become-strings pass pass pass pass pass pass pass environment variables always become a string value, so values that would parse as a number, a boolean or null in HOCON stay the strings "42", "true" and "null" substitutions38 casesJava 37/38 · hocon2 31/38 · pulse-pyhocon 32/38 · py.hocon 36/38 · pyhocon 29/38 · rs.hocon 36/38 · zig-hocon 38/38
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-basic-substitution pass pass pass pass pass pass pass the syntax ${pathexpression} refers to another part of the configuration tree 002-optional-substitution-syntax pass pass pass pass pass pass pass ${?pathexpression} is the optional substitution syntax; when the path is defined it resolves exactly like ${pathexpression}, keeping the value and its type 003-optional-substitution-whitespace-illegal pass pass pass pass pass pass pass the ? in ${?pathexpression} must not have whitespace before it; the three characters ${? must be exactly grouped together 004-substitution-not-parsed-in-quoted-string pass pass pass pass pass pass pass substitutions are not parsed inside quoted strings, so this stays the literal text 005-substitution-value-concatenation-unquoted pass pass pass pass pass pass pass worked example: to get a string containing a substitution, use value concatenation with the substitution in the unquoted portion 006-substitution-value-concatenation-quoted-tail pass pass pass pass pass pass pass worked example: quoting the non-substitution portion also forms a value concatenation with the substitution 007-substitution-path-absolute pass pass pass pass pass pass pass substitutions are resolved by looking up the path from the root configuration object, absolute rather than relative, so ${a} inside b finds the root a and not the sibling b.a 008-substitution-forward-reference pass pass pass pass pass pass pass substitution processing happens as the last parsing step, so a substitution can look forward in the configuration 009-substitution-latest-duplicate-key pass pass pass pass pass pass pass if a key has been specified more than once, a substitution referring to it always evaluates to its latest-assigned value 010-substitution-latest-duplicate-key-object-merge pass pass pass pass pass pass pass a substitution to a key set more than once evaluates to the merged object, not an earlier snapshot 011-undefined-required-substitution-is-error pass pass pass pass pass pass pass an undefined substitution with the ${foo} syntax is invalid and should generate an error 012-undefined-optional-substitution-field-not-created pass pass pass pass pass pass pass if an undefined ${?foo} substitution is the value of an object field, the field should not be created 013-undefined-optional-substitution-keeps-previous-value pass pass fail pass pass pass pass if an undefined ${?foo} would override a previously-set value for the same field, the previous value remains 014-undefined-optional-substitution-array-element-not-added pass pass pass pass pass pass pass if an undefined ${?foo} substitution is an array element, the element should not be added 015-undefined-optional-substitution-concatenation-string-empty pass pass pass pass pass pass pass an undefined ${?foo} in a value concatenation with another string becomes an empty string 016-undefined-optional-substitution-concatenation-object-empty pass pass pass pass pass pass pass an undefined ${?foo} in a value concatenation with an object becomes an empty object 017-undefined-optional-substitution-concatenation-array-empty pass pass pass pass pass pass pass an undefined ${?foo} in a value concatenation with an array becomes an empty array 018-two-optional-substitutions-both-undefined-field-not-created pass pass pass pass pass pass pass foo : ${?bar}${?baz} avoids creating the field only if both bar and baz are undefined 019-two-optional-substitutions-one-defined-field-created pass pass pass pass pass pass pass foo : ${?bar}${?baz} creates the field once at least one of bar or baz is defined 020-substitution-not-allowed-in-key pass pass pass pass pass pass pass substitutions are only allowed in field values and array elements, not in keys 021-substitution-not-allowed-nested-in-path-expression pass pass pass pass pass pass pass substitutions are not allowed nested inside other substitutions (path expressions) 022-substitution-preserves-type-array pass pass pass pass pass pass pass a substitution is replaced with any value type; if it is the only part of a value, the type (here an array) is preserved 024-substitution-concatenation-forms-string pass pass pass pass pass pass pass when a substitution is not the only part of a value, it is value-concatenated to form a string rather than keeping the substituted type 025-substitution-preserves-type-string-booleans-null pass pass fail pass fail pass pass a substitution is replaced with any value type and, when it is the only part of a value, the type is preserved: here string, true, false and null stay what they were rather than becoming strings 026-unterminated-substitution-is-error pass pass pass pass pass pass pass the syntax is ${pathexpression}: a substitution opened with ${ and never closed with } is not that syntax, and $ cannot start an unquoted string either, so it must be rejected 028-optional-substitution-to-null-value-is-defined pass pass fail pass fail pass pass a substitution is undefined only if it does not match any value present in the configuration, and null is among the value types a substitution is replaced with, so ${?a} with a = null is defined: the field is created with null rather than omitted 030-quoted-key-is-not-a-substitution pass pass pass pass pass pass pass substitutions are not parsed inside quoted strings, and a quoted key is a quoted string: "${a}" is the literal key ${a}, not a substitution in a key (which would be illegal) 031-whitespace-inside-braces-is-trimmed pass fail pass pass fail pass pass the path inside ${} has the syntax of an object key, and whitespace before the first and after the last element of a key is discarded, so ${ b } is ${b} 032-empty-substitution-illegal pass pass pass pass pass pass pass ${} contains no path expression at all, so it is neither a substitution nor anything else 033-path-through-a-non-object-is-undefined pass fail pass pass pass pass pass a is a number, so a.x names nothing; a required substitution to an undefined path is an error 034-optional-path-through-a-non-object-is-omitted pass fail pass pass fail pass pass the same path with the optional form: undefined, so the field is not created 035-quoted-path-element-with-space pass pass pass pass pass pass pass a substitution path has the syntax of an object key, so a quoted element may contain a space and names the field written the same way 036-empty-quoted-path-element pass pass fail pass fail pass pass an empty path element is legal when quoted, in a substitution as in a key 037-path-into-a-list-element-is-undefined pass fail pass pass fail pass pass a path names object fields only, so b.0 does not reach into the list b even though it has an element at index 0; the optional substitution is undefined and a is not created. The list is still unresolved when the lookup happens, which is what makes an implementation look inside it 038-whitespace-after-question-mark pass fail pass pass fail pass pass only whitespace before the ? is forbidden; after it the path follows and its leading whitespace is discarded as in any key 039-undefined-optional-with-number-is-a-string fail fail pass fail fail fail pass ${?x}42 is a concatenation of two parts, so the value is a string: the undefined optional becomes empty and the number keeps its written form. The spec states the empty-string rule for a concatenation 'with another string', so this row extends it to a number; typesafe/config instead drops the undefined part and keeps the lone number as a numberjava: diverges 040-null-blocks-environment-lookup pass pass fail pass fail pass pass the configuration sets CONFORMANCE_NULLED to null, so the substitution resolves to that null and the environment variable of the same name is never consulted 041-undefined-optional-under-a-path-leaves-the-object pass fail fail fail pass fail pass the undefined optional creates no field foo, but the path section.foo still made the object section, which stays empty (lightbend/config#615) unchanged-from-json30 casesJava 21/30 · hocon2 21/30 · pulse-pyhocon 13/30 · py.hocon 20/30 · pyhocon 14/30 · rs.hocon 18/30 · zig-hocon 30/30
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-quoted-string-json-format pass pass fail pass fail pass pass quoted strings are in the same format as JSON strings, so JSON escapes like \n, \t, \", \\ and \u unicode escapes are honored 002-string-value-type pass pass pass pass pass pass pass string is one of the possible value types 003-number-value-type pass pass pass pass pass pass pass number is one of the possible value types 004-object-value-type pass pass pass pass pass pass pass object is one of the possible value types 005-array-value-type pass pass pass pass pass pass pass array is one of the possible value types 006-boolean-value-type pass pass pass pass pass pass pass boolean is one of the possible value types 007-null-value-type pass pass pass pass pass pass pass null is one of the possible value types 008-number-format-decimal-and-exponent pass pass pass pass pass pass pass allowed number formats match JSON, including negative numbers, decimals, and exponent notation 009-number-leading-zero-illegal fail fail fail fail fail fail pass allowed number formats match JSON, and JSON forbids a zero followed by further digits; the initial number character plus the valid-in-JSON number characters after it must be parsed as a number value, so the whole token 01 has to be a number and is not one, with no fallback offered. typesafe/config parses it leniently as the number 1.java: lenient 010-negative-number-without-integer-part-illegal fail fail fail fail fail fail pass allowed number formats match JSON, and JSON requires a digit before the decimal point, so -.33 is a number token that is not a valid number. typesafe/config releases up to 1.4.9 parse it as -0.33; upstream main since lightbend/config#861 (unreleased) reads it as the unquoted string "-.33", like .33 -- still accepted, still not the error the hyphen rule asks forjava: lenient 011-negative-exponent-number-without-integer-part-illegal fail pass fail fail fail fail pass the same JSON rule with an exponent: -.33e+1 has no integer part. typesafe/config releases up to 1.4.9 parse it as -3.3; upstream main since lightbend/config#861 (unreleased) rejects it, on the reserved +java: lenient 012-number-with-trailing-dot-illegal fail fail fail fail fail fail pass JSON requires a digit after the decimal point; 1. is a number token that is not a valid number. typesafe/config reads it as 1, and lightbend/config#861 leaves it so on purposejava: lenient 013-trailing-dot-before-exponent-illegal fail fail fail fail fail fail pass the same rule with an exponent after the dot; typesafe/config reads it as -1000 (named out of scope in lightbend/config#861)java: lenient 014-negative-leading-zeros-illegal fail fail fail fail fail fail pass JSON forbids leading zeros after the minus sign as well; typesafe/config reads it as -33 (named out of scope in lightbend/config#861)java: lenient 015-remaining-json-escapes pass pass fail pass fail pass pass the JSON escapes \/ \b \f and \r are valid in a quoted string and mean slash, backspace, form feed and carriage return 016-unknown-escape-illegal pass pass fail pass fail pass pass only the JSON escape sequences exist; \q is not one of them, so the string is invalid 017-non-hex-unicode-escape-illegal pass pass fail pass fail pass pass \u must be followed by exactly four hex digits; zzzz are not hex digits 018-unterminated-quoted-string-illegal pass pass pass pass pass pass pass a quoted string that is still open at the end of the file is invalid 019-backslash-at-end-of-file-illegal pass pass pass pass pass pass pass a backslash with nothing after it is not an escape and the string is still open at the end of the file 020-unicode-escape-cut-off-by-end-of-file-illegal pass pass pass pass pass pass pass the file ends after two of the four hex digits a \u escape requires 021-raw-newline-in-quoted-string-illegal pass pass fail pass pass pass pass JSON forbids unescaped control characters in strings, and a raw newline is one; a quoted string cannot span lines 022-raw-tab-in-quoted-string-illegal pass pass fail fail fail fail pass a raw tab is a control character too, so it must be written as \t inside quotes 023-64-bit-integer pass pass pass pass pass pass pass a number beyond 32 bits is still a number and keeps its exact value 024-raw-utf8-in-quoted-string pass pass pass pass fail pass pass the file is UTF-8, so a multi-byte sequence is one character 025-nan-is-not-a-number pass fail pass pass pass pass pass NaN is not a JSON number, so the bare token is an unquoted string 026-surrogate-pair-escape pass pass fail pass fail fail pass JSON writes a character outside the BMP as two \u escapes forming a surrogate pair, which decode to one character 027-unpaired-surrogate-escape-is-kept pass pass fail pass fail fail pass quoted strings are JSON strings, and JSON's grammar accepts any \uXXXX escape, an unpaired surrogate included, so the string holds that one code unit 028-integer-beyond-64-bits-is-a-number fail fail fail fail pass fail pass number formats match JSON, which has no size limit, so an integer past 2^63 is still a number; typesafe/config makes it a string (lightbend/config#801, ekrich/sconfig#631)java: diverges 029-unicode-escape-with-a-sign-is-invalid fail fail fail fail fail fail pass a JSON \u escape is exactly four hex digits, and + is not one; typesafe/config reads +041 as hex 41, A (lightbend/config#884, ekrich/sconfig#623)java: lenient 030-unicode-escape-with-non-ascii-digits-is-invalid fail fail fail fail fail fail pass a JSON \u escape is four ASCII hex digits; Arabic-Indic digits are not, though typesafe/config parses them as 0041, A (lightbend/config#884, ekrich/sconfig#623)java: lenient units-format6 casesJava 6/6 · hocon2 6/6 · pulse-pyhocon 6/6 · py.hocon 6/6 · pyhocon 5/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-number-value-is-default-unit pass pass pass pass pass pass pass if the value is a number, it is taken to be a number in the default unit 002-quoted-string-with-no-unit-uses-default-unit pass pass pass pass pass pass pass a string value with no unit name should be interpreted with the default unit, as if it were a number 003-string-with-unit-name-specifies-interpretation pass pass pass pass fail pass pass a string value with a unit name has that name specify the value's interpretation 004-whitespace-optional-around-number-and-unit pass pass pass pass pass pass pass the units-format grammar is optional whitespace, a number, optional whitespace, an optional unit name, optional whitespace 005-unit-name-must-be-letters-only pass pass pass pass pass pass pass the unit name consists only of letters (Unicode L* categories, Java isLetter()), so a unit name with a trailing digit is not a legal units-format string 006-unit-before-number-is-illegal pass pass pass pass pass pass pass the units-format grammar puts the number before the unit name, not after, so a unit-then-number string is not a legal units-format value unquoted-strings25 casesJava 22/25 · hocon2 21/25 · pulse-pyhocon 11/25 · py.hocon 21/25 · pyhocon 16/25 · rs.hocon 21/25 · zig-hocon 25/25
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-simple-unquoted-string pass pass pass pass pass pass pass a sequence of characters outside a quoted string is a string value when it contains none of the forbidden characters 002-quoted-alternative-for-forbidden-char pass pass pass pass pass pass pass a quoted string may always be used as an alternative to write a character that is not permitted in an unquoted string, such as '@' 003-unquoted-cannot-contain-at-sign pass pass fail pass pass pass pass '@' is one of the listed forbidden characters, so it cannot appear inside an unquoted string 004-double-slash-starts-comment pass pass fail pass fail pass pass the two-character string '//' starts a comment, ending the unquoted string before it even mid-token 005-single-slash-allowed pass pass pass pass pass pass pass only the two-character string '//' is special for comments; a lone '/' is not a forbidden character and stays part of the unquoted string 006-truefoo-parses-as-true-then-foo pass fail pass pass pass pass pass worked example: truefoo parses as the boolean token true followed by the unquoted string foo, because its initial characters parse as true 007-footrue-is-one-unquoted-string pass pass pass pass pass pass pass worked example: footrue does not begin with true, false, null, or a number, so it parses as a single unquoted string 008-number-then-suffix-string pass pass pass pass pass pass pass worked example: 10.0bar is the number 10.0 followed by the unquoted string bar, since 10.0 parses as a number at the start 009-digits-not-at-start-stay-string pass pass pass pass pass pass pass worked example: bar10.0 does not begin with a digit, so the whole thing is the single unquoted string bar10.0, not a number 010-embedded-null-not-recognized pass pass pass pass pass pass pass embedded (non-initial) null, true, false, and numbers are not recognized as such: they are just part of the string, since only the leading characters are checked 011-unquoted-cannot-contain-backslash pass pass fail pass fail pass pass backslash is a forbidden character, and unquoted strings support no escaping at all, so a literal backslash requires a quoted string instead 012-unquoted-cannot-start-with-hyphen fail fail fail fail fail fail pass an unquoted string may not begin with a hyphen, and the initial number character plus any valid-in-JSON number characters that follow must be parsed as a number value -- here that is the lone '-', which is not a number. typesafe/config falls back to unquoted text and yields the string '-foo', the same leniency it shows for '1e'.java: lenient 013-negative-number-then-string pass pass pass pass pass pass pass a hyphen followed by digits begins number parsing (-1), after which foo continues as a separate unquoted string token, mirroring the 10.0bar example for negative numbers 014-hash-starts-comment-not-forbidden-char-in-string pass pass fail pass pass pass pass '#' is forbidden inside an unquoted string because it already has meaning in HOCON: it starts a comment 015-reserved-forbidden-char-no-current-meaning pass pass fail pass pass pass pass some forbidden characters, such as '!', have no meaning in HOCON today and are reserved as keywords for future extensions to the spec 016-control-char-allowed-unquoted pass pass pass pass pass pass pass unquoted strings place no restriction on control characters, other than the forbidden characters listed 017-control-char-forbidden-in-quoted pass pass fail fail fail fail pass quoted JSON strings may not contain control characters, per the JSON spec, unlike unquoted strings 018-leading-dot-is-not-a-number pass pass fail pass fail pass pass only 0-9 and - begin a number, so .33 is an ordinary unquoted string -- the reading lightbend/config#861 extends to -.33 019-dollar-must-start-a-substitution pass pass fail pass pass pass pass $ is a forbidden character in an unquoted string, and outside quotes it may only begin ${; $foo is neither 020-plus-must-be-followed-by-equals pass pass fail pass pass pass pass + is a forbidden character in an unquoted string and outside quotes only forms the += separator; a + with nothing after it, here at the end of the file, is invalid 021-plus-inside-a-number-illegal pass pass fail pass fail pass pass 1+2 begins like a number and is not one; + is a forbidden character, so the token cannot fall back to an unquoted string either 022-digit-then-exponent-letter-illegal fail fail fail fail fail fail pass 1e begins with a digit, so the run of number characters 1e must be parsed as a number, and it is not one; typesafe/config falls back to the unquoted string "1e", the same leniency it shows for -foojava: lenient 023-unquoted-string-ends-at-end-of-file pass pass pass pass pass pass pass an unquoted string runs to a forbidden character or whitespace, and the end of the file ends it as well; no final newline is needed 024-escape-sequence-in-unquoted-string-illegal pass pass fail pass fail pass pass unquoted strings support no escaping, so \u00e9 is not decoded, and the backslash it needs is forbidden in an unquoted string; the quoted "caf\u00e9" is how to write it 025-digit-initial-invalid-number fail fail fail fail fail fail pass an unquoted string may not begin with the digits 0-9, and the initial number character plus any valid-in-JSON number characters that follow it must be parsed as a number value -- 1.2.3 is that run, and it is not a number, so the input is rejected rather than read as a string (the everyday shape is an address like 127.0.0.1); typesafe/config and pyhocon both fall back to the unquoted string "1.2.3"java: lenient value-concatenation2 casesJava 2/2 · hocon2 2/2 · pulse-pyhocon 2/2 · py.hocon 2/2 · pyhocon 2/2 · rs.hocon 2/2 · zig-hocon 2/2
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-simple-values-concatenate-to-string pass pass pass pass pass pass pass if all the values are simple values they are concatenated into a string 004-string-concatenation-allowed-in-field-keys pass pass pass pass pass pass pass string value concatenation is allowed in field keys, in addition to field values and array elements whitespace13 casesJava 13/13 · hocon2 12/13 · pulse-pyhocon 3/13 · py.hocon 10/13 · pyhocon 4/13 · rs.hocon 10/13 · zig-hocon 13/13
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-nonbreaking-space-as-whitespace pass pass fail pass fail pass pass a nonbreaking space (0x00A0) is a Unicode Zs separator and must be treated as whitespace 002-bom-at-start-is-whitespace pass pass fail pass fail pass pass the BOM (0xFEFF) must be treated as whitespace, not as part of the key 003-unicode-line-paragraph-separator pass pass fail fail pass fail pass the line and paragraph separators U+2028 and U+2029 are whitespace but not newlines, so they do not separate two fields: 1 b concatenates and the second = is a stray token 004-vertical-tab-form-feed-whitespace pass pass fail pass fail pass pass vertical tab (0x000B) and form feed (0x000C) are whitespace characters 005-ascii-separator-control-chars pass pass pass fail pass fail pass the ASCII separator controls 0x1C-0x1F are whitespace but not newlines, so they do not separate two fields 006-special-nonbreaking-spaces pass pass fail pass fail pass pass the spec names figure space (0x2007) and narrow no-break space (0x202F) explicitly among the nonbreaking spaces treated as whitespace 007-tab-is-whitespace pass pass pass pass pass pass pass a tab is whitespace around a key and a separator 008-crlf-line-endings pass pass pass pass pass pass pass a carriage return is whitespace and the line feed beside it is the newline, so CRLF line endings separate fields; if the CR were text the first value would be "1\r" 009-lone-carriage-return-is-not-a-newline pass fail fail fail fail fail pass a lone carriage return is whitespace but not a newline, so 1 b concatenates and the second = is a stray token 010-line-separator-is-whitespace pass pass fail pass fail pass pass U+2028 around the separator is whitespace, not part of the key or the value 011-ascii-separator-controls-are-whitespace pass pass fail pass fail pass pass the file, group, record and unit separators 0x1C-0x1F are whitespace around keys and separators 012-ideographic-space-is-whitespace pass pass fail pass fail pass pass the ideographic space U+3000 is a Zs space separator and so whitespace, like every member of the category 013-bom-inside-the-document-is-whitespace pass pass fail pass fail pass pass the BOM is whitespace wherever it appears, not only as a file-start marker