diff --git a/README.md b/README.md index 3a9a28d..c3d774f 100644 --- a/README.md +++ b/README.md @@ -211,13 +211,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 | 90% (399/445) | 99% (442/445) | -| hocon2 hocon2 0.6.3 | 86% (384/445) | 80% (357/445) | -| pulse-pyhocon pulse-pyhocon 0.2.1 | 71% (314/445) | 68% (303/445) | -| py.hocon hocon-parser 1.14.2 (Python) | 89% (395/445) | 88% (392/445) | -| pyhocon pyhocon 0.3.63 | 70% (312/445) | 66% (295/445) | -| rs.hocon hocon-parser 1.14.2 (Rust) | 89% (395/445) | 86% (383/445) | -| zig-hocon zig-hocon code@fad796a | 99% (442/445) | 90% (399/445) | +| Java typesafe/config 1.4.9 | 90% (401/445) | 100% (444/445) | +| hocon2 hocon2 0.6.3 | 87% (386/445) | 81% (359/445) | +| pulse-pyhocon pulse-pyhocon 0.2.1 | 71% (316/445) | 69% (305/445) | +| py.hocon hocon-parser 1.14.2 (Python) | 89% (397/445) | 89% (394/445) | +| pyhocon pyhocon 0.3.63 | 71% (314/445) | 67% (297/445) | +| rs.hocon hocon-parser 1.14.2 (Rust) | 89% (397/445) | 87% (385/445) | +| zig-hocon zig-hocon code@73b5fad | 100% (444/445) | 90% (401/445) | 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/README.md b/conformance/README.md index b98c0ae..26e1ffa 100644 --- a/conformance/README.md +++ b/conformance/README.md @@ -81,6 +81,17 @@ A runner hands your adapter the path to `main.conf`; includes resolve beside it. A row where the reference implementation and the spec part ways carries `"java": "diverges" | "unsupported" | "lenient"` and, next to it, `"java_expect"` or `"java_error"` — what typesafe/config does with that same input. Those rows are the reason the suite exists; `maintaining/PROCESS.md` has the table of which kind means what. +Where the spec allows more than one answer, `"also_valid"` lists the others, and an output matching `expect` or any of them passes. `"rejected"` in the list lets an error pass too: + +```json +{ + "spec": "HOCON.md#examples-of-self-referential-substitutions", + "why": "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", + "expect": {"a": 1, "b": 1}, + "also_valid": [{"a": 2, "b": 2}, "rejected"] +} +``` + A row carrying `"review"` is an open question for a human. It is neither a pass nor a failure. `--text` lists such rows apart from the failing ones, and they alone do not make `run.sh` exit 1. Two directories assert features the specification only permits: `include-file-formats` (other formats, basename probing: "implementations may support") and `substitution-fallback-to-environment` together with `list-values-from-environment-variables` ("implementations may search external sources"). An implementation without them is still conformant; it fails those rows knowingly, and `--only` can leave them out. @@ -102,9 +113,9 @@ Drop your own result into `reports/` and the local report picks it up. The published table keeps the best parsers: every Python parser of its own above 70 % against the spec, and the best Rust one. These were measured too and left out: - **hocon-rs 0.2.0** (Rust, mikai233/hocon-rs): 84 % against the spec, behind rs.hocon. -- **hocon 0.9.0** (Rust, mockersf/hocon.rs): 71 % with its strict loader, 58 % with the default one, which never fails; the repository is archived since 2023, and non-ASCII whitespace makes it panic. +- **hocon 0.9.0** (Rust, mockersf/hocon.rs): 72 % with its strict loader, 58 % with the default one, which never fails; the repository is archived since 2023, and non-ASCII whitespace makes it panic. - **hocon_** (Rust, pepa65): a republished fork of hocon 0.9.0, not measured on its own. -- **pyhocon-fix 0.0.4** (Python, 2018) and **dd-pyhocon 0.3.61** (Python, 2023): forks of pyhocon, 71 % and 70 %, within two cases of it; dd-pyhocon hangs on one merge-stack case. +- **pyhocon-fix 0.0.4** (Python, 2018) and **dd-pyhocon 0.3.61** (Python, 2023): forks of pyhocon, both 71 %, within two cases of it; dd-pyhocon hangs on one merge-stack case. - Not parsers of their own: `hocon` 0.3.0, `hocon-validator`, `pytyped-hocon`, `pyhocon-dantic` and `dataconf` wrap pyhocon; `atomr` (Rust/PyO3) builds its `Config` from TOML or a dict and parses no HOCON; chris-martin/hocon-python (2015) is an unfinished port of typesafe/config. ## Reusing this diff --git a/conformance/report.html b/conformance/report.html index 44c1344..ee90447 100644 --- a/conformance/report.html +++ b/conformance/report.html @@ -124,7 +124,7 @@

HOCON Conformance

-
Java
90%
399 of 445 rows
typesafe/config 1.4.9
hocon2
86%
384 of 445 rows
hocon2 0.6.3
pulse-pyhocon
71%
314 of 445 rows
pulse-pyhocon 0.2.1
py.hocon
89%
395 of 445 rows
hocon-parser 1.14.2 (Python)
pyhocon
70%
312 of 445 rows
pyhocon 0.3.63
rs.hocon
89%
395 of 445 rows
hocon-parser 1.14.2 (Rust)
zig-hocon
99%
442 of 445 rows
zig-hocon code@fad796a
+
Java
90%
401 of 445 rows
typesafe/config 1.4.9
hocon2
87%
386 of 445 rows
hocon2 0.6.3
pulse-pyhocon
71%
316 of 445 rows
pulse-pyhocon 0.2.1
py.hocon
89%
397 of 445 rows
hocon-parser 1.14.2 (Python)
pyhocon
71%
314 of 445 rows
pyhocon 0.3.63
rs.hocon
89%
397 of 445 rows
hocon-parser 1.14.2 (Rust)
zig-hocon
100%
444 of 445 rows
zig-hocon code@73b5fad
@@ -151,7 +151,7 @@

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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-arrays-concatenatepasspasspasspasspasspasspassarrays can be concatenated with arrays when only non-newline whitespace separates them
002-objects-concatenatepasspasspasspasspasspasspassobjects can be concatenated with objects when only non-newline whitespace separates them
003-mixing-array-and-object-is-errorpasspasspasspasspasspasspassit is an error to concatenate an array with an object
004-substitution-counts-as-array-for-concatenationpasspasspasspasspasspasspassfor 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-concatenationpasspasspasspasspasspasspassfor 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-concatenationpasspasspasspasspasspasspassnewlines 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-overridespasspasspasspasspasspasspassfor objects, concatenation means merging, and the second object overrides the first on overlapping keys
008-array-cannot-be-field-keypasspasspasspasspasspasspassarrays 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-keypasspasspasspasspasspasspassobjects 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-inheritancepasspasspasspasspasspasspassthe spec's worked example: object concatenation with a substitution is a common way to express inheritance
011-worked-example-array-path-appendpasspasspasspasspasspasspassthe spec's worked example: array concatenation with a substitution is a common way to add to paths
012-worked-example-self-referential-array-concatpasspasspasspasspasspasspassthe 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-concatenationpasspasspasspasspasspasspassnewlines 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-errorpasspasspasspasspasspasspassa 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-errorpasspasspasspasspasspasspassa 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-errorpasspasspasspasspasspasspassit 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-concatenationpasspasspasspasspasspasspassa 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-substitutionfailpasspasspasspasspasspassan 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-mergefailpasspasspasspasspasspass${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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-newlines-instead-of-commaspasspasspasspasspasspasspassarrays allow newlines to separate elements instead of commas
002-whitespace-instead-of-comma-concatenates-into-one-elementpasspasspasspasspasspasspassthe 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-elementspasspasspasspasspasspasspassthe spec's worked example: a newline between two bracketed arrays makes them two separate elements
004-same-line-whitespace-concatenates-two-arrays-into-one-elementpasspasspasspasspasspasspassthe 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-arraypasspasspasspasspasspasspassthe 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-concatenationpasspasspasspasspasspasspassthe spec's worked example: whitespace between two substitutions concatenates them into one element, and the comma separates the two concatenations
007-whitespace-never-separates-fieldspasspassfailfailpassfailpassnon-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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-array-trailing-commapasspasspasspasspasspasspassthe 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-commapasspasspasspasspasspasspassarray 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-invalidpasspassfailpassfailpasspass[1,2,3,,] is explicitly called invalid because it has two trailing commas
004-array-initial-comma-invalidpasspassfailpassfailpasspass[,1,2,3] is explicitly called invalid because it has an initial comma
005-array-double-comma-invalidpasspassfailpassfailpasspass[1,,2,3] is explicitly called invalid because it has two commas in a row
006-object-trailing-commapasspasspasspasspasspasspassthese same comma rules apply to fields in objects, so a single trailing comma after the last field is ignored
007-object-newline-no-commapasspasspasspasspasspasspassfields 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-invalidpasspassfailpassfailpasspassthese 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-commapasspassfailfailpassfailpasstwo 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-rootpasspasspasspasspasspasspassthe implicit root object follows the object comma rules, so its last field may carry a single trailing comma
012-leading-comma-at-implicit-root-illegalpasspassfailpassfailpasspassan initial comma is invalid in an object, the implicit root included
013-double-trailing-comma-in-object-illegalpasspassfailpassfailpasspassthe array comma rules apply to object fields: two trailing commas are invalid
014-leading-comma-in-object-illegalpasspassfailpassfailpasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-hash-commentpasspasspasspasspasspasspassanything between # and the next newline is a comment and ignored
002-double-slash-commentpasspasspasspasspasspasspassanything between // and the next newline is a comment and ignored
003-whole-line-commentpasspasspasspasspasspasspassa comment may occupy an entire line by itself, ignored just like a trailing one
004-comment-stops-at-newlinepasspasspasspasspasspasspassa comment runs only to the next newline, so the following line is parsed normally
005-hash-inside-quoted-string-is-not-a-commentpasspasspasspasspasspasspassa # inside a quoted string is not treated as starting a comment
006-double-slash-inside-quoted-string-is-not-a-commentpasspasspasspasspasspasspassa // inside a quoted string is not treated as starting a comment
007-comment-between-separator-and-valuepasspasspasspasspasspasspassa 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-fieldpasspasspasspasspasspasspassa comment separated from the following field by a blank line is ignored like any other comment
009-comment-inside-objectpasspasspasspasspasspasspasscomments and blank lines inside an object body are ignored
010-comments-inside-arraypasspasspasspasspasspasspasscomments and blank lines between array elements are ignored, and do not join or separate elements
011-comment-before-values-of-every-typepasspasspasspasspasspasspassa comment before a field is ignored whatever type its value has
012-comment-before-explicit-root-objectpasspasspasspasspasspasspassa 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-arraypasspasspasspasspasspasspassa // comment after an array is not text in a concatenation; the value is just the array
014-hash-comment-after-array-without-spacepasspasspasspasspasspasspassa # 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-unquoted-whitespace-between-substitutions-is-significant-for-stringspasspasspasspasspasspasspasswhen 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-errorpasspasspasspasspasspasspassbetween 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-ignoredpasspasspasspasspasspasspassunquoted 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-ignoredpasspasspasspasspasspasspasswhitespace between two list substitutions is ignored, tabs included, and the lists concatenate
005-nonbreaking-space-between-list-substitutions-is-ignoredpasspasspasspassfailpasspassU+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-ignoredpasspassfailpassfailpasspassthe BOM must be treated as whitespace, so between two list substitutions it is ignored
007-zero-width-space-between-list-substitutions-is-errorfailpasspasspasspasspasspassU+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-listpasspasspasspasspasspasspassan undefined optional substitution in a concatenation with an array becomes an empty array
009-undefined-optional-then-list-is-the-listfailpasspasspasspasspasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
002-file-merge-is-recursive-and-later-file-winspasspasspasspasspasspasspassmerging 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-objectpasspasspasspassfailpasspassthe 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-mergepasspasspasspassfailpasspassthe 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-objectpasspasspasspasspasspasspasssetting 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-later-scalar-winspasspasspasspasspasspasspassfor a non-object value the later assignment simply replaces the earlier one
002-objects-mergepasspasspasspasspasspasspasstwo objects under the same key merge key-by-key, they do not replace
003-scalar-breaks-the-mergepasspasspasspassfailpasspassmerging 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-winspasspasspasspasspasspasspassfor a non-object-valued field present in both merged objects, the field from the second object is used
005-merge-nested-object-recursivepasspasspasspasspasspasspassfor an object-valued field present in both objects, the object values are recursively merged by the same rules
006-null-prevents-mergepasspasspasspasspasspasspassthe 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-sidepasspasspasspasspasspasspassfields 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-objectspasspasspasspasspasspasspassonly 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-overridespasspasspasspasspasspasspassnull 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-mergefailpassfailpasspasspasspassthe 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-mergefailpassfailpasspasspasspassthe 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-valuefailpasspasspasspasspasspassa 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-substitutionfailpassfailpasspasspasspassthe 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-objectpasspasspasspasspasspasspassthe 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-mergedfailpassfailpasspasspasspass${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-keyfailpassfailpassfailpasspassv'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-mergesfailpasspasspasspasspasspasstwo 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-objectpasspasspasspasspasspasspasstwo objects merge, so the empty later object keeps x; replacement would have given {}
019-duplicate-keys-merge-inside-a-list-elementpasspasspasspasspasspasspassan 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-alternatepasspassfailfailpassfailpassfour values for a, two of them substitutions that resolve to objects: all four merge in order
021-later-object-replaces-earlier-concatenationpasspasspasspassfailpasspassa string concatenation is a non-object value, so the later object replaces it rather than merging with it
022-scalar-then-empty-object-then-objectpasspasspasspassfailpasspassthe 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-substitutionpasspasspasspasspasspasspassan 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-stackpasspassfailpasspasspasspassthe 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-listfailpasspasspasspasspasspassthe 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-listpasspasspasspasspasspasspassthe 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-mergepasspasspasspasspasspasspassinside 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-winspasspasspasspasspasspasspassnull 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-bare-number-is-millisecondspasspasspasspasspasspasspassbare numbers are taken to be in milliseconds already
002-uppercase-unit-is-illegalpasspasspasspasspasspasspassthe 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-spellingspasspasspasspassfailpasspassexactly ns, nano, nanos, nanosecond, nanoseconds are the supported unit strings for nanoseconds
004-microseconds-spellingspasspasspasspassfailpasspassexactly us, micro, micros, microsecond, microseconds are the supported unit strings for microseconds
005-milliseconds-spellingspasspasspasspassfailpasspassexactly ms, milli, millis, millisecond, milliseconds are the supported unit strings for milliseconds
006-seconds-spellingspasspasspasspassfailpasspassexactly s, second, seconds are the supported unit strings for seconds
007-minutes-spellingspasspasspasspassfailpasspassexactly m, minute, minutes are the supported unit strings for minutes
008-hours-spellingspasspasspasspassfailpasspassexactly h, hour, hours are the supported unit strings for hours
009-days-spellingspasspasspasspassfailpasspassexactly 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-basename-loads-every-extensionpassfailfailpassfailpasspassa 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-confpassfailfailpassfailpasspassa basename with only base.conf present loads just that file
003-basename-with-only-propertiespassfailfailpassfailpasspassa basename with only base.properties present loads it as Java properties, whose values are all strings
004-basename-with-no-file-is-ignoredpasspasspasspasspasspasspassa 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-errorpasspasspasspasspasspasspassrequired() makes the missing basename an error
006-json-extension-means-json-syntaxfailfailfailfailfailfailpassa 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-syntaxfailfailfailpassfailpasspassa 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-pathspassfailfailpassfailpasspassin 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-typepassfailfailpassfailpasspassa .json file found through a basename is parsed as JSON and contributes its object
011-json-rejects-unquoted-valuespassfailfailfailfailfailpassthe .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-substitutionspassfailfailpassfailpasspassthe .json file is found through a basename, so it is parsed as JSON; ${} is a HOCON extension, not JSON
013-json-rejects-trailing-commapassfailfailfailfailfailpassthe .json file is found through a basename, so it is parsed as JSON; JSON has no trailing commas
014-json-rejects-duplicate-keyspassfailfailfailfailfailpassthe .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-commaspassfailfailfailfailfailpassthe .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-documentpassfailfailfailfailfailpassthe .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-rootpassfailfailpassfailpasspassthe .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-stringpassfailfailfailfailfailpassthe .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-separatorpassfailfailfailfailfailpassthe .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-keypassfailfailpassfailpasspassthe .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-illegalfailfailfailfailfailfailpassthe .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-basenamefailpasspassfailpasspasspassx.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-propertiespassfailfailpassfailpasspassbase.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-jsonpassfailfailpassfailpasspassbase.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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-include-string-that-is-a-url-is-a-urlpasspasspasspasspasspasspassa 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-filepasspasspasspasspasspasspassinner.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-onepasspasspasspasspasspasspassx.conf is not a URL, so it names a file adjacent to main.conf
004-absolute-path-stays-absolutepasspasspasspasspasspasspassan 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-filepassfailfailpassfailpasspassa 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-included-keys-merge-in-placepasspasspasspasspasspasspassthe 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-errorpasspasspasspasspasspasspassan 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-errorpasspasspasspasspasspasspassboth 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-objectpasspasspasspasspasspasspassthe 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-keypasspasspasspasspasspasspassif 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-objectpasspasspasspasspasspasspassif 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-objectpasspasspasspasspasspasspassan 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-keypasspasspasspasspasspasspassif 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-objectpasspasspasspasspasspasspassif 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-listpasspasspasspasspasspasspassan include statement may stand wherever a field may, including in an object that is a list element
011-include-inside-a-list-with-a-substitutionfailpasspasspasspasspasspassthe 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-objectpasspassfailfailpassfailpassthe 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-substitutionpasspassfailfailpassfailpassthe earlier value is itself a substitution; once everything resolves to objects, all three layers merge
014-included-merge-stack-over-earlier-scalarpasspassfailpasspasspasspassthe 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-substitutionpasspassfailfailpassfailpassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-missing-include-ignoredpasspasspasspasspasspasspassby 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-objectpasspasspasspasspasspasspassa 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-ignoredpasspasspasspasspasspasspassthe 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-errorpasspasspasspasspasspasspasswhen 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-errorpasspasspasspasspasspasspassrequired() 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-includedpasspasspasspasspasspasspassrequired() 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-substitution-relativized-to-include-scopepasspassfailpassfailpasspassa 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-redefinitionpasspassfailpassfailpasspasssubstitution 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-fallbackpasspasspasspasspasspasspassit 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-originalpassfailfailpassfailpasspasssubstitutions 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-errorpasspasspasspasspasspasspassonly 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-omittedpasspasspasspasspasspasspassan 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-levelspasspassfailpassfailpasspassthe 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-environmentpassfailpasspasspasspasspassan 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-backfailpasspassfailpassfailpassan 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-originalpasspasspassfailpassfailpasswith neither common.x.y nor x.y defined, the required ${x.y} in the included file is unresolved
011-optional-reference-falls-back-toofailpasspassfailpassfailpassthe 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-winspassfailfailpassfailpasspassan 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-backpasspasspasspasspasspasspassthe 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-resultfailpasspassfailpassfailpassan 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-errorpasspassfailfailpassfailpasswhen 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-uppasspasspasspasspasspasspassa 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-uppasspasspasspasspasspasspassa substitution that is a list element in the included file is fixed up as well
018-substitution-in-a-merge-stack-is-fixed-uppasspassfailpassfailpasspassan 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-foundpassfailfailpassfailpasspassa.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 14/15 · hocon2 12/15 · pulse-pyhocon 11/15 · py.hocon 12/15 · pyhocon 12/15 · rs.hocon 13/15 · zig-hocon 14/15
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-include-later-in-key-is-literalpasspasspasspasspasspasspassunquoted 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-stringpasspasspasspasspasspasspassunquoted 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-stringpasspasspasspasspasspasspassunquoted 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-argumentpasspassfailpasspasspasspassvalue 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-argumentpasspasspasspasspasspasspassno 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-quotedpasspasspasspasspasspasspassif 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-keypasspasspasspasspasspasspassonly 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-quotedpasspasspasspasspasspasspassthe argument inside file() must be a single quoted string; an unquoted name is a syntax error
009-file-missing-close-parenthesispasspasspassfailpasspasspassfile("...") must be closed; the line ends before the parenthesis
010-required-missing-close-parenthesispasspasspassfailpassfailpassrequired(...) must be closed too
011-url-argument-must-be-a-urlfailfailfailfailfailfailfailthe string inside url() is interpreted as a URL, and x.conf has no scheme, so it is not one
013-newlines-between-include-and-namepasspassfailpassfailpasspassany amount of whitespace, newlines included, may stand between include and the resource name
014-whitespace-inside-parenthesespassfailpasspasspasspasspasswhitespace inside the parentheses, outside the quotes, is allowed
015-include-followed-by-separator-is-not-a-keypasspassfailpassfailpasspassan 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-fieldpassfailpasspasspasspasspassan include stands where a field would, so the usual comma separates it from the field that follows
key-value-separator6 casesJava 5/6 · hocon2 5/6 · pulse-pyhocon 5/6 · py.hocon 5/6 · pyhocon 5/6 · rs.hocon 5/6 · zig-hocon 5/6
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-equals-as-separatorpasspasspasspasspasspasspassthe = character can be used anywhere JSON allows :, to separate keys from values
002-omitted-separator-before-objectpasspasspasspasspasspasspassif a key is followed by {, the : or = may be omitted; the spec's own example is "foo" {} meaning "foo" : {}
003-omitted-separator-unquoted-keypasspasspasspasspasspasspassthe separator-omission rule before { applies to unquoted keys too, not just the quoted example in the spec
004-value-on-the-line-after-the-separatorpasspasspasspasspasspasspass= stands wherever JSON allows :, and JSON allows any whitespace, newlines included, between the separator and the value
005-separator-cannot-be-omitted-before-arraypasspasspasspasspasspasspassthe separator may be omitted only before {; a key followed directly by [ is invalid
006-newline-between-key-and-bracefailfailfailfailfailfailfailthe separator may be omitted before {, and the spec does not say whether a newline may stand between the key and the brace; typesafe/config and pyhocon accept it, hocon-rs rejects it
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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-list-from-indexed-variablespassfailfailpassfailpasspassthe [] 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-undefinedpassfailpasspasspasspasspasswith no CONFORMANCE_LIST_0 set, an optional ${?X[]} is undefined and the field is not created
003-required-list-with-no-elements-is-errorpasspasspasspasspasspasspasswith no CONFORMANCE_LIST_0 set, a required ${X[]} is an error
004-collection-stops-at-the-first-gappassfailfailpassfailpasspassindices are collected from 0 until one is missing, so with _0 and _2 set the list holds only the first
005-elements-are-stringspassfailfailpassfailpasspassan element taken from the environment is a string, so 1 stays "1"
006-config-keys-are-not-collectedpassfailpasspasspasspasspassthe [] 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-basic-triple-quotepasspasspasspasspasspasspassthe three-character sequence """ starts a multi-line string whose unicode characters are used unmodified to create the string value
002-newlines-and-whitespace-preservedpasspasspasspasspasspasspassnewlines and whitespace inside a multi-line string receive no special treatment and are kept in the value
003-unicode-escape-not-interpretedpasspasspasspasspasspasspassunlike 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-stringpasspasspasspasspasspasspassany 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-extrapasspassfailpasspasspasspassa 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-illegalpasspasspasspasspasspasspassa 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-closepasspassfailpasspasspasspassonly 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-literallypasspassfailpasspasspasspassa backslash sequence inside a triple-quoted string is used unmodified, not as an escape
009-six-quotes-are-an-empty-stringpasspasspasspasspasspasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-object-with-numeric-keys-stays-objectpasspasspasspasspasspasspassthe 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-arraypassfailfailpassfailpasspassthe 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-keyspassfailfailpassfailpasspassthe 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-convertedpasspasspasspasspasspasspassthe 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-convertedpasspasspasspasspasspasspassthe 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-reindexedpassfailfailpassfailpasspassthe 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-conversionpassfailfailpassfailpasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-implicit-root-objectpasspasspasspasspasspasspassif the file does not begin with [ or {, it is parsed as if enclosed with {} curly braces
002-explicit-root-object-unaffectedpasspasspasspasspasspasspassthe 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-illegalpasspasspassfailpassfailpassa HOCON file is invalid if it omits the opening { but still has a closing }, since the curly braces must be balanced
004-empty-filepassfailpasspasspasspasspassan 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-illegalpasspasspasspasspasspasspassthe 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-unaffectedfailpasspassfailpassfailpassthe 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-rootfailpasspassfailpassfailpassan 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-rootfailpasspassfailpassfailpassthe {} 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-rootfailpasspassfailpassfailpassleading whitespace does not trigger the {} wrap either, for the same reason as a leading commentjava: unsupported
011-trailing-array-after-root-arraypasspasspasspasspasspasspasstwo 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-illegalpasspasspassfailpassfailpassa 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-illegalpasspasspasspasspasspasspassan opening brace with no closing brace is unbalanced
014-comment-only-filepassfailpasspasspasspasspassa file holding only a comment does not begin with a bracket or brace, so it is the empty object
015-bare-number-root-illegalpasspasspasspasspasspasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-quoted-dot-has-no-meaningpasspasspasspasspasspasspassa 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-pathpasspassfailpassfailpasspassfoo.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-stringpasspasspasspasspasspasspass10.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-dotpasspasspasspasspasspasspassfoo10.0 is an unquoted string with a dot in it, giving the two-element path foo10 and 0
005-unquoted-concatenated-with-quoted-numberpasspassfailfailfailfailpassfoo followed by quoted "10.0" is an unquoted then a quoted string which concatenate, giving a single-element path
006-all-numeric-pathpasspasspasspasspasspasspassdots in numbers count as path separators, so 1.2.3 is the three-element path with elements 1, 2, 3
008-value-concatenation-keeps-booleanpasspasspasspasspasspasspassunlike 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-validpasspassfailpassfailpasspassa 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-invalidpasspasspasspassfailpasspassan unquoted empty path element is invalid, so a..b must generate an error
011-path-starting-with-dot-invalidpasspasspasspassfailpasspassa path that starts with a dot is invalid and should generate an error
012-path-ending-with-dot-invalidpassfailpasspassfailpasspassa path that ends with a dot is invalid and should generate an error
015-dotted-path-in-substitutionpasspasspasspasspasspasspasspath 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-pathpasspasspasspasspasspasspassa 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-pathpasspasspasspasspasspasspasspath 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-pathpasspasspasspasspasspasspasspath 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-invalidpasspassfailpassfailpasspassan 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-pathpasspasspasspasspasspasspassa 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-literalpasspasspasspasspasspasspassinside 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-stringpasspasspasspasspasspasspassa path expression is always a string, so true in a substitution names the key true rather than a boolean
023-substitution-in-dotted-key-illegalpasspasspasspasspasspasspassa 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-two-element-path-expandspasspasspasspasspasspasspassa 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-expandspasspasspasspasspasspasspassa three-element path key expands through two levels of nesting: foo.bar.baz:42 is equivalent to foo{bar{baz:42}}
003-merge-sibling-pathspasspasspasspasspasspasspassthe 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-keypasspasspasspasspasspasspassbecause 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-stringpasspasspasspasspasspasspasspath expressions are always converted to strings, so the unquoted boolean-looking key true:42 is "true":42
006-unquoted-number-key-becomes-stringpasspasspasspasspasspasspasspath expressions are always converted to strings, so the unquoted numeric key 3:42 is "3":42
007-decimal-key-splits-on-dotpasspasspasspasspasspasspassa 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-keyfailpassfailpassfailpasspassthe 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-ordinarypassfailfailpassfailpasspassonly 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-objectpasspasspasspasspasspasspassthe 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-elementspasspasspasspasspasspasspasswhitespace 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-bare-number-is-dayspasspasspasspasspasspasspassfor getPeriod(), bare numbers are taken to be in days, unlike getDuration() where bare numbers are milliseconds
002-uppercase-unit-is-illegalpasspasspasspasspasspasspassthe 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-spellingspasspasspasspassfailpasspassexactly d, day, days are the supported unit strings for days in the period format
004-weeks-spellingspasspasspasspassfailpasspassexactly w, week, weeks are the supported unit strings for weeks
005-months-spellingspasspasspasspassfailpasspassexactly m, mo, month, months are the supported unit strings for months
006-years-spellingspasspasspasspasspasspasspassexactly y, year, years are the supported unit strings for years
007-month-m-ambiguous-with-duration-minutespasspasspasspassfailpasspassthe 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-separator8 casesJava 6/8 · hocon2 6/8 · pulse-pyhocon 0/8 · py.hocon 8/8 · pyhocon 0/8 · rs.hocon 8/8 · zig-hocon 8/8
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-plus-equals-desugars-to-optional-self-ref-arraypasspassfailpassfailpasspassa += 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-arraypasspassfailpassfailpasspass+= appends an element to a previous array
003-plus-equals-errors-on-non-array-previous-valuepasspassfailpassfailpasspassif 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-twicepasspassfailpassfailpasspasseach += appends to the value the previous line left, so two of them append two elements
005-plus-equals-in-nested-object-with-substitutionpassfailfailpassfailpasspassinside 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-elementfailpassfailpassfailpasspassb 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-keypassfailfailpassfailpasspassthe 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-mergefailpassfailpassfailpasspassl += 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
self-referential-examples26 casesJava 23/26 · hocon2 25/26 · pulse-pyhocon 20/26 · py.hocon 22/26 · pyhocon 21/26 · rs.hocon 22/26 · zig-hocon 25/26
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-isolated-self-reference-is-an-errorpasspasspasspasspasspasspassin 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-valuepasspasspasspasspasspasspasswhen 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-undefinedpasspassfailfailpassfailpassif 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-silentlypasspasspasspasspasspasspassbecause 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-evaluatedpasspasspasspasspasspasspassif 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-literalpasspasspasspasspasspasspassthe 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-belowpasspasspasspasspasspasspassa 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-itselfpasspasspasspasspasspasspassan 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-mergepasspasspasspasspasspasspassbecause 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-referentialpasspasspasspasspasspasspassmutually-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-backpasspasspasspasspasspasspassan 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-unresolvablepasspasspasspasspasspasspassthe 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-invalidpasspasspasspasspasspasspassa 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-valuefailfailfailfailfailfailfailthis case has undefined behavior depending on resolution order: implementations are allowed to set both a and b to 1, both to 2, or to error, but must set both to the same value because substitutions are memoized by instance, so a and b must never diverge (e.g. a=1,b=2 is not permitted)
015-list-hidden-by-substituted-object-is-never-evaluatedfailpasspassfailpassfailpassa 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-evaluatedpasspasspasspassfailpasspass${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-evaluatedpasspasspasspassfailpasspassa 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-evaluatedpasspassfailpasspasspasspassa 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-evaluatedpasspasspasspasspasspasspassthe 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-evaluatedpasspasspasspasspasspasspassa 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-orderfailpasspassfailpassfailpassa 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-hidepasspassfailpasspasspasspassan 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-hidepasspassfailpassfailpasspassw += 1 is w = ${?w} [1], which reads the earlier list, so ${does-not-exist} is evaluated: an error
024-substitution-target-is-fetched-lazilypasspasspasspasspasspasspassresolving ${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-undefinedpasspassfailpassfailpasspassa 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-pathpasspasspasspasspasspasspassthe 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
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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-path-builds-on-older-valuepasspasspasspasspasspasspassa 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-concatenationpasspasspasspasspasspasspassa 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-referentialpasspasspasspasspasspasspassan 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-referentialpasspasspasspasspasspasspassan 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-concatenationpasspasspasspasspasspasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-bare-number-is-bytespasspasspasspasspasspasspassbare numbers are taken to be in bytes already
002-single-byte-spellingspasspasspasspasspasspasspassfor single bytes, exactly B, b, byte, bytes are supported
003-powers-of-ten-suffixespasspasspasspasspasspasspassevery power-of-10 suffix and its long forms name the same unit family; one table, one rule
004-powers-of-two-suffixespasspasspasspassfailpasspassevery 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-twopasspasspasspasspasspasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-substitution-in-string-concatenationpasspasspasspasspasspasspassstring value concatenation supports substitutions inside the concatenated string
002-only-simple-values-participatepasspasspasspasspasspasspassit is invalid for arrays or objects to appear in a string value concatenation, since only simple values participate
003-leading-and-trailing-whitespace-trimmedpasspasspasspasspasspasspasswhitespace before the first and after the last simple value is discarded; only the whitespace between the values is kept
004-concatenation-never-spans-newlinepasspasspasspasspasspasspassstring 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-concatenationpasspasspasspasspasspasspassa string value concatenation may appear in any place a string may appear, including array elements
006-boolean-becomes-string-in-concatenationpasspassfailpassfailpasspasstrue and false become the strings "true" and "false" when they participate in a concatenation
007-null-becomes-string-in-concatenationpasspassfailpassfailpasspassnull becomes the string "null" when it participates in a concatenation
008-number-kept-as-written-in-concatenationpasspasspasspasspasspasspassnumbers 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-stringpasspasspasspasspasspasspassa 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-scalarfailpasspasspasspasspasspassit 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-scalarfailpasspasspasspasspasspassthe 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-errorpasspasspasspasspasspasspassthe same rule with the scalar first, which typesafe/config does reject -- the half of the rule it enforces
014-array-then-unquoted-text-without-spacefailpasspasspasspasspasspassan 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-spacefailpasspasspasspasspasspassthe 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-droppedfailpasspasspasspasspasspasstext 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-writtenpasspasspasspasspasspasspassa number in a concatenation is stringified as it appeared in the file, whatever its size
021-false-becomes-string-in-concatenationpasspassfailpassfailpasspassfalse becomes the string "false" in a concatenation, as true does
022-quoted-and-unquoted-strings-concatenatepasspasspasspasspasspasspassa 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-env-fallback-for-undefined-pathpasspasspasspasspasspasspassa substitution whose path is not defined in the configuration falls back to an environment variable of the same name
002-required-substitution-resolved-from-envpasspasspasspasspasspasspassa 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-envpasspasspasspasspasspasspassthe 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-lookuppasspassfailpassfailpasspasssetting 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-sensitivepasspasspasspasspasspasspassthe 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-stringpasspasspasspasspasspasspassan 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-fieldpasspasspasspasspasspasspassan 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-stringspasspasspasspasspasspasspassenvironment 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-basic-substitutionpasspasspasspasspasspasspassthe syntax ${pathexpression} refers to another part of the configuration tree
002-optional-substitution-syntaxpasspasspasspasspasspasspass${?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-illegalpasspasspasspasspasspasspassthe ? in ${?pathexpression} must not have whitespace before it; the three characters ${? must be exactly grouped together
004-substitution-not-parsed-in-quoted-stringpasspasspasspasspasspasspasssubstitutions are not parsed inside quoted strings, so this stays the literal text
005-substitution-value-concatenation-unquotedpasspasspasspasspasspasspassworked example: to get a string containing a substitution, use value concatenation with the substitution in the unquoted portion
006-substitution-value-concatenation-quoted-tailpasspasspasspasspasspasspassworked example: quoting the non-substitution portion also forms a value concatenation with the substitution
007-substitution-path-absolutepasspasspasspasspasspasspasssubstitutions 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-referencepasspasspasspasspasspasspasssubstitution processing happens as the last parsing step, so a substitution can look forward in the configuration
009-substitution-latest-duplicate-keypasspasspasspasspasspasspassif 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-mergepasspasspasspasspasspasspassa substitution to a key set more than once evaluates to the merged object, not an earlier snapshot
011-undefined-required-substitution-is-errorpasspasspasspasspasspasspassan undefined substitution with the ${foo} syntax is invalid and should generate an error
012-undefined-optional-substitution-field-not-createdpasspasspasspasspasspasspassif an undefined ${?foo} substitution is the value of an object field, the field should not be created
013-undefined-optional-substitution-keeps-previous-valuepasspassfailpasspasspasspassif an undefined ${?foo} would override a previously-set value for the same field, the previous value remains
014-undefined-optional-substitution-array-element-not-addedpasspasspasspasspasspasspassif an undefined ${?foo} substitution is an array element, the element should not be added
015-undefined-optional-substitution-concatenation-string-emptypasspasspasspasspasspasspassan undefined ${?foo} in a value concatenation with another string becomes an empty string
016-undefined-optional-substitution-concatenation-object-emptypasspasspasspasspasspasspassan undefined ${?foo} in a value concatenation with an object becomes an empty object
017-undefined-optional-substitution-concatenation-array-emptypasspasspasspasspasspasspassan undefined ${?foo} in a value concatenation with an array becomes an empty array
018-two-optional-substitutions-both-undefined-field-not-createdpasspasspasspasspasspasspassfoo : ${?bar}${?baz} avoids creating the field only if both bar and baz are undefined
019-two-optional-substitutions-one-defined-field-createdpasspasspasspasspasspasspassfoo : ${?bar}${?baz} creates the field once at least one of bar or baz is defined
020-substitution-not-allowed-in-keypasspasspasspasspasspasspasssubstitutions are only allowed in field values and array elements, not in keys
021-substitution-not-allowed-nested-in-path-expressionpasspasspasspasspasspasspasssubstitutions are not allowed nested inside other substitutions (path expressions)
022-substitution-preserves-type-arraypasspasspasspasspasspasspassa 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-stringpasspasspasspasspasspasspasswhen 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-nullpasspassfailpassfailpasspassa 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-errorpasspasspasspasspasspasspassthe 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-definedpasspassfailpassfailpasspassa 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-substitutionpasspasspasspasspasspasspasssubstitutions 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-trimmedpassfailpasspassfailpasspassthe 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-illegalpasspasspasspasspasspasspass${} contains no path expression at all, so it is neither a substitution nor anything else
033-path-through-a-non-object-is-undefinedpassfailpasspasspasspasspassa 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-omittedpassfailpasspassfailpasspassthe same path with the optional form: undefined, so the field is not created
035-quoted-path-element-with-spacepasspasspasspasspasspasspassa 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-elementpasspassfailpassfailpasspassan empty path element is legal when quoted, in a substitution as in a key
037-path-into-a-list-element-is-undefinedpassfailpasspassfailpasspassa 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-markpassfailpasspassfailpasspassonly 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-stringfailfailpassfailfailfailpass${?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-lookuppasspassfailpassfailpasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-quoted-string-json-formatpasspassfailpassfailpasspassquoted strings are in the same format as JSON strings, so JSON escapes like \n, \t, \", \\ and \u unicode escapes are honored
002-string-value-typepasspasspasspasspasspasspassstring is one of the possible value types
003-number-value-typepasspasspasspasspasspasspassnumber is one of the possible value types
004-object-value-typepasspasspasspasspasspasspassobject is one of the possible value types
005-array-value-typepasspasspasspasspasspasspassarray is one of the possible value types
006-boolean-value-typepasspasspasspasspasspasspassboolean is one of the possible value types
007-null-value-typepasspasspasspasspasspasspassnull is one of the possible value types
008-number-format-decimal-and-exponentpasspasspasspasspasspasspassallowed number formats match JSON, including negative numbers, decimals, and exponent notation
009-number-leading-zero-illegalfailfailfailfailfailfailpassallowed 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-illegalfailfailfailfailfailfailpassallowed 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-illegalfailpassfailfailfailfailpassthe 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-illegalfailfailfailfailfailfailpassJSON 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-illegalfailfailfailfailfailfailpassthe 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-illegalfailfailfailfailfailfailpassJSON 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-escapespasspassfailpassfailpasspassthe JSON escapes \/ \b \f and \r are valid in a quoted string and mean slash, backspace, form feed and carriage return
016-unknown-escape-illegalpasspassfailpassfailpasspassonly the JSON escape sequences exist; \q is not one of them, so the string is invalid
017-non-hex-unicode-escape-illegalpasspassfailpassfailpasspass\u must be followed by exactly four hex digits; zzzz are not hex digits
018-unterminated-quoted-string-illegalpasspasspasspasspasspasspassa quoted string that is still open at the end of the file is invalid
019-backslash-at-end-of-file-illegalpasspasspasspasspasspasspassa 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-illegalpasspasspasspasspasspasspassthe file ends after two of the four hex digits a \u escape requires
021-raw-newline-in-quoted-string-illegalpasspassfailpasspasspasspassJSON forbids unescaped control characters in strings, and a raw newline is one; a quoted string cannot span lines
022-raw-tab-in-quoted-string-illegalpasspassfailfailfailfailpassa raw tab is a control character too, so it must be written as \t inside quotes
023-64-bit-integerpasspasspasspasspasspasspassa number beyond 32 bits is still a number and keeps its exact value
024-raw-utf8-in-quoted-stringpasspasspasspassfailpasspassthe file is UTF-8, so a multi-byte sequence is one character
025-nan-is-not-a-numberpassfailpasspasspasspasspassNaN is not a JSON number, so the bare token is an unquoted string
026-surrogate-pair-escapepasspassfailpassfailfailpassJSON writes a character outside the BMP as two \u escapes forming a surrogate pair, which decode to one character
027-unpaired-surrogate-escape-is-keptpasspassfailpassfailfailpassquoted 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-number-value-is-default-unitpasspasspasspasspasspasspassif the value is a number, it is taken to be a number in the default unit
002-quoted-string-with-no-unit-uses-default-unitpasspasspasspasspasspasspassa 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-interpretationpasspasspasspassfailpasspassa string value with a unit name has that name specify the value's interpretation
004-whitespace-optional-around-number-and-unitpasspasspasspasspasspasspassthe units-format grammar is optional whitespace, a number, optional whitespace, an optional unit name, optional whitespace
005-unit-name-must-be-letters-onlypasspasspasspasspasspasspassthe 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-illegalpasspasspasspasspasspasspassthe 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-strings24 casesJava 22/24 · hocon2 21/24 · pulse-pyhocon 11/24 · py.hocon 21/24 · pyhocon 16/24 · rs.hocon 21/24 · zig-hocon 24/24
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-simple-unquoted-stringpasspasspasspasspasspasspassa sequence of characters outside a quoted string is a string value when it contains none of the forbidden characters
002-quoted-alternative-for-forbidden-charpasspasspasspasspasspasspassa 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-signpasspassfailpasspasspasspass'@' is one of the listed forbidden characters, so it cannot appear inside an unquoted string
004-double-slash-starts-commentpasspassfailpassfailpasspassthe two-character string '//' starts a comment, ending the unquoted string before it even mid-token
005-single-slash-allowedpasspasspasspasspasspasspassonly 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-foopassfailpasspasspasspasspassworked 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-stringpasspasspasspasspasspasspassworked example: footrue does not begin with true, false, null, or a number, so it parses as a single unquoted string
008-number-then-suffix-stringpasspasspasspasspasspasspassworked 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-stringpasspasspasspasspasspasspassworked 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-recognizedpasspasspasspasspasspasspassembedded (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-backslashpasspassfailpassfailpasspassbackslash 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-hyphenfailfailfailfailfailfailpassan 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-stringpasspasspasspasspasspasspassa 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-stringpasspassfailpasspasspasspass'#' is forbidden inside an unquoted string because it already has meaning in HOCON: it starts a comment
015-reserved-forbidden-char-no-current-meaningpasspassfailpasspasspasspasssome 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-unquotedpasspasspasspasspasspasspassunquoted strings place no restriction on control characters, other than the forbidden characters listed
017-control-char-forbidden-in-quotedpasspassfailfailfailfailpassquoted JSON strings may not contain control characters, per the JSON spec, unlike unquoted strings
018-leading-dot-is-not-a-numberpasspassfailpassfailpasspassonly 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-substitutionpasspassfailpasspasspasspass$ is a forbidden character in an unquoted string, and outside quotes it may only begin ${; $foo is neither
020-plus-must-be-followed-by-equalspasspassfailpasspasspasspass+ 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-illegalpasspassfailpassfailpasspass1+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-illegalfailfailfailfailfailfailpass1e 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-filepasspasspasspasspasspasspassan 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-illegalpasspassfailpassfailpasspassunquoted 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
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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-simple-values-concatenate-to-stringpasspasspasspasspasspasspassif all the values are simple values they are concatenated into a string
004-string-concatenation-allowed-in-field-keyspasspasspasspasspasspasspassstring 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-nonbreaking-space-as-whitespacepasspassfailpassfailpasspassa nonbreaking space (0x00A0) is a Unicode Zs separator and must be treated as whitespace
002-bom-at-start-is-whitespacepasspassfailpassfailpasspassthe BOM (0xFEFF) must be treated as whitespace, not as part of the key
003-unicode-line-paragraph-separatorpasspassfailfailpassfailpassthe 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-whitespacepasspassfailpassfailpasspassvertical tab (0x000B) and form feed (0x000C) are whitespace characters
005-ascii-separator-control-charspasspasspassfailpassfailpassthe ASCII separator controls 0x1C-0x1F are whitespace but not newlines, so they do not separate two fields
006-special-nonbreaking-spacespasspassfailpassfailpasspassthe spec names figure space (0x2007) and narrow no-break space (0x202F) explicitly among the nonbreaking spaces treated as whitespace
007-tab-is-whitespacepasspasspasspasspasspasspassa tab is whitespace around a key and a separator
008-crlf-line-endingspasspasspasspasspasspasspassa 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-newlinepassfailfailfailfailfailpassa lone carriage return is whitespace but not a newline, so 1 b concatenates and the second = is a stray token
010-line-separator-is-whitespacepasspassfailpassfailpasspassU+2028 around the separator is whitespace, not part of the key or the value
011-ascii-separator-controls-are-whitespacepasspassfailpassfailpasspassthe file, group, record and unit separators 0x1C-0x1F are whitespace around keys and separators
012-ideographic-space-is-whitespacepasspassfailpassfailpasspassthe ideographic space U+3000 is a Zs space separator and so whitespace, like every member of the category
013-bom-inside-the-document-is-whitespacepasspassfailpassfailpasspassthe BOM is whitespace wherever it appears, not only as a file-start marker
+
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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-arrays-concatenatepasspasspasspasspasspasspassarrays can be concatenated with arrays when only non-newline whitespace separates them
002-objects-concatenatepasspasspasspasspasspasspassobjects can be concatenated with objects when only non-newline whitespace separates them
003-mixing-array-and-object-is-errorpasspasspasspasspasspasspassit is an error to concatenate an array with an object
004-substitution-counts-as-array-for-concatenationpasspasspasspasspasspasspassfor 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-concatenationpasspasspasspasspasspasspassfor 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-concatenationpasspasspasspasspasspasspassnewlines 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-overridespasspasspasspasspasspasspassfor objects, concatenation means merging, and the second object overrides the first on overlapping keys
008-array-cannot-be-field-keypasspasspasspasspasspasspassarrays 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-keypasspasspasspasspasspasspassobjects 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-inheritancepasspasspasspasspasspasspassthe spec's worked example: object concatenation with a substitution is a common way to express inheritance
011-worked-example-array-path-appendpasspasspasspasspasspasspassthe spec's worked example: array concatenation with a substitution is a common way to add to paths
012-worked-example-self-referential-array-concatpasspasspasspasspasspasspassthe 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-concatenationpasspasspasspasspasspasspassnewlines 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-errorpasspasspasspasspasspasspassa 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-errorpasspasspasspasspasspasspassa 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-errorpasspasspasspasspasspasspassit 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-concatenationpasspasspasspasspasspasspassa 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-substitutionfailpasspasspasspasspasspassan 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-mergefailpasspasspasspasspasspass${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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-newlines-instead-of-commaspasspasspasspasspasspasspassarrays allow newlines to separate elements instead of commas
002-whitespace-instead-of-comma-concatenates-into-one-elementpasspasspasspasspasspasspassthe 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-elementspasspasspasspasspasspasspassthe spec's worked example: a newline between two bracketed arrays makes them two separate elements
004-same-line-whitespace-concatenates-two-arrays-into-one-elementpasspasspasspasspasspasspassthe 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-arraypasspasspasspasspasspasspassthe 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-concatenationpasspasspasspasspasspasspassthe spec's worked example: whitespace between two substitutions concatenates them into one element, and the comma separates the two concatenations
007-whitespace-never-separates-fieldspasspassfailfailpassfailpassnon-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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-array-trailing-commapasspasspasspasspasspasspassthe 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-commapasspasspasspasspasspasspassarray 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-invalidpasspassfailpassfailpasspass[1,2,3,,] is explicitly called invalid because it has two trailing commas
004-array-initial-comma-invalidpasspassfailpassfailpasspass[,1,2,3] is explicitly called invalid because it has an initial comma
005-array-double-comma-invalidpasspassfailpassfailpasspass[1,,2,3] is explicitly called invalid because it has two commas in a row
006-object-trailing-commapasspasspasspasspasspasspassthese same comma rules apply to fields in objects, so a single trailing comma after the last field is ignored
007-object-newline-no-commapasspasspasspasspasspasspassfields 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-invalidpasspassfailpassfailpasspassthese 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-commapasspassfailfailpassfailpasstwo 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-rootpasspasspasspasspasspasspassthe implicit root object follows the object comma rules, so its last field may carry a single trailing comma
012-leading-comma-at-implicit-root-illegalpasspassfailpassfailpasspassan initial comma is invalid in an object, the implicit root included
013-double-trailing-comma-in-object-illegalpasspassfailpassfailpasspassthe array comma rules apply to object fields: two trailing commas are invalid
014-leading-comma-in-object-illegalpasspassfailpassfailpasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-hash-commentpasspasspasspasspasspasspassanything between # and the next newline is a comment and ignored
002-double-slash-commentpasspasspasspasspasspasspassanything between // and the next newline is a comment and ignored
003-whole-line-commentpasspasspasspasspasspasspassa comment may occupy an entire line by itself, ignored just like a trailing one
004-comment-stops-at-newlinepasspasspasspasspasspasspassa comment runs only to the next newline, so the following line is parsed normally
005-hash-inside-quoted-string-is-not-a-commentpasspasspasspasspasspasspassa # inside a quoted string is not treated as starting a comment
006-double-slash-inside-quoted-string-is-not-a-commentpasspasspasspasspasspasspassa // inside a quoted string is not treated as starting a comment
007-comment-between-separator-and-valuepasspasspasspasspasspasspassa 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-fieldpasspasspasspasspasspasspassa comment separated from the following field by a blank line is ignored like any other comment
009-comment-inside-objectpasspasspasspasspasspasspasscomments and blank lines inside an object body are ignored
010-comments-inside-arraypasspasspasspasspasspasspasscomments and blank lines between array elements are ignored, and do not join or separate elements
011-comment-before-values-of-every-typepasspasspasspasspasspasspassa comment before a field is ignored whatever type its value has
012-comment-before-explicit-root-objectpasspasspasspasspasspasspassa 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-arraypasspasspasspasspasspasspassa // comment after an array is not text in a concatenation; the value is just the array
014-hash-comment-after-array-without-spacepasspasspasspasspasspasspassa # 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-unquoted-whitespace-between-substitutions-is-significant-for-stringspasspasspasspasspasspasspasswhen 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-errorpasspasspasspasspasspasspassbetween 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-ignoredpasspasspasspasspasspasspassunquoted 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-ignoredpasspasspasspasspasspasspasswhitespace between two list substitutions is ignored, tabs included, and the lists concatenate
005-nonbreaking-space-between-list-substitutions-is-ignoredpasspasspasspassfailpasspassU+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-ignoredpasspassfailpassfailpasspassthe BOM must be treated as whitespace, so between two list substitutions it is ignored
007-zero-width-space-between-list-substitutions-is-errorfailpasspasspasspasspasspassU+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-listpasspasspasspasspasspasspassan undefined optional substitution in a concatenation with an array becomes an empty array
009-undefined-optional-then-list-is-the-listfailpasspasspasspasspasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
002-file-merge-is-recursive-and-later-file-winspasspasspasspasspasspasspassmerging 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-objectpasspasspasspassfailpasspassthe 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-mergepasspasspasspassfailpasspassthe 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-objectpasspasspasspasspasspasspasssetting 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-later-scalar-winspasspasspasspasspasspasspassfor a non-object value the later assignment simply replaces the earlier one
002-objects-mergepasspasspasspasspasspasspasstwo objects under the same key merge key-by-key, they do not replace
003-scalar-breaks-the-mergepasspasspasspassfailpasspassmerging 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-winspasspasspasspasspasspasspassfor a non-object-valued field present in both merged objects, the field from the second object is used
005-merge-nested-object-recursivepasspasspasspasspasspasspassfor an object-valued field present in both objects, the object values are recursively merged by the same rules
006-null-prevents-mergepasspasspasspasspasspasspassthe 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-sidepasspasspasspasspasspasspassfields 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-objectspasspasspasspasspasspasspassonly 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-overridespasspasspasspasspasspasspassnull 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-mergefailpassfailpasspasspasspassthe 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-mergefailpassfailpasspasspasspassthe 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-valuefailpasspasspasspasspasspassa 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-substitutionfailpassfailpasspasspasspassthe 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-objectpasspasspasspasspasspasspassthe 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-mergedfailpassfailpasspasspasspass${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-keyfailpassfailpassfailpasspassv'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-mergesfailpasspasspasspasspasspasstwo 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-objectpasspasspasspasspasspasspasstwo objects merge, so the empty later object keeps x; replacement would have given {}
019-duplicate-keys-merge-inside-a-list-elementpasspasspasspasspasspasspassan 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-alternatepasspassfailfailpassfailpassfour values for a, two of them substitutions that resolve to objects: all four merge in order
021-later-object-replaces-earlier-concatenationpasspasspasspassfailpasspassa string concatenation is a non-object value, so the later object replaces it rather than merging with it
022-scalar-then-empty-object-then-objectpasspasspasspassfailpasspassthe 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-substitutionpasspasspasspasspasspasspassan 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-stackpasspassfailpasspasspasspassthe 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-listfailpasspasspasspasspasspassthe 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-listpasspasspasspasspasspasspassthe 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-mergepasspasspasspasspasspasspassinside 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-winspasspasspasspasspasspasspassnull 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-bare-number-is-millisecondspasspasspasspasspasspasspassbare numbers are taken to be in milliseconds already
002-uppercase-unit-is-illegalpasspasspasspasspasspasspassthe 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-spellingspasspasspasspassfailpasspassexactly ns, nano, nanos, nanosecond, nanoseconds are the supported unit strings for nanoseconds
004-microseconds-spellingspasspasspasspassfailpasspassexactly us, micro, micros, microsecond, microseconds are the supported unit strings for microseconds
005-milliseconds-spellingspasspasspasspassfailpasspassexactly ms, milli, millis, millisecond, milliseconds are the supported unit strings for milliseconds
006-seconds-spellingspasspasspasspassfailpasspassexactly s, second, seconds are the supported unit strings for seconds
007-minutes-spellingspasspasspasspassfailpasspassexactly m, minute, minutes are the supported unit strings for minutes
008-hours-spellingspasspasspasspassfailpasspassexactly h, hour, hours are the supported unit strings for hours
009-days-spellingspasspasspasspassfailpasspassexactly 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-basename-loads-every-extensionpassfailfailpassfailpasspassa 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-confpassfailfailpassfailpasspassa basename with only base.conf present loads just that file
003-basename-with-only-propertiespassfailfailpassfailpasspassa basename with only base.properties present loads it as Java properties, whose values are all strings
004-basename-with-no-file-is-ignoredpasspasspasspasspasspasspassa 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-errorpasspasspasspasspasspasspassrequired() makes the missing basename an error
006-json-extension-means-json-syntaxfailfailfailfailfailfailpassa 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-syntaxfailfailfailpassfailpasspassa 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-pathspassfailfailpassfailpasspassin 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-typepassfailfailpassfailpasspassa .json file found through a basename is parsed as JSON and contributes its object
011-json-rejects-unquoted-valuespassfailfailfailfailfailpassthe .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-substitutionspassfailfailpassfailpasspassthe .json file is found through a basename, so it is parsed as JSON; ${} is a HOCON extension, not JSON
013-json-rejects-trailing-commapassfailfailfailfailfailpassthe .json file is found through a basename, so it is parsed as JSON; JSON has no trailing commas
014-json-rejects-duplicate-keyspassfailfailfailfailfailpassthe .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-commaspassfailfailfailfailfailpassthe .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-documentpassfailfailfailfailfailpassthe .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-rootpassfailfailpassfailpasspassthe .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-stringpassfailfailfailfailfailpassthe .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-separatorpassfailfailfailfailfailpassthe .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-keypassfailfailpassfailpasspassthe .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-illegalfailfailfailfailfailfailpassthe .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-basenamefailpasspassfailpasspasspassx.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-propertiespassfailfailpassfailpasspassbase.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-jsonpassfailfailpassfailpasspassbase.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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-include-string-that-is-a-url-is-a-urlpasspasspasspasspasspasspassa 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-filepasspasspasspasspasspasspassinner.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-onepasspasspasspasspasspasspassx.conf is not a URL, so it names a file adjacent to main.conf
004-absolute-path-stays-absolutepasspasspasspasspasspasspassan 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-filepassfailfailpassfailpasspassa 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-included-keys-merge-in-placepasspasspasspasspasspasspassthe 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-errorpasspasspasspasspasspasspassan 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-errorpasspasspasspasspasspasspassboth 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-objectpasspasspasspasspasspasspassthe 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-keypasspasspasspasspasspasspassif 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-objectpasspasspasspasspasspasspassif 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-objectpasspasspasspasspasspasspassan 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-keypasspasspasspasspasspasspassif 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-objectpasspasspasspasspasspasspassif 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-listpasspasspasspasspasspasspassan include statement may stand wherever a field may, including in an object that is a list element
011-include-inside-a-list-with-a-substitutionfailpasspasspasspasspasspassthe 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-objectpasspassfailfailpassfailpassthe 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-substitutionpasspassfailfailpassfailpassthe earlier value is itself a substitution; once everything resolves to objects, all three layers merge
014-included-merge-stack-over-earlier-scalarpasspassfailpasspasspasspassthe 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-substitutionpasspassfailfailpassfailpassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-missing-include-ignoredpasspasspasspasspasspasspassby 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-objectpasspasspasspasspasspasspassa 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-ignoredpasspasspasspasspasspasspassthe 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-errorpasspasspasspasspasspasspasswhen 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-errorpasspasspasspasspasspasspassrequired() 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-includedpasspasspasspasspasspasspassrequired() 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-substitution-relativized-to-include-scopepasspassfailpassfailpasspassa 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-redefinitionpasspassfailpassfailpasspasssubstitution 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-fallbackpasspasspasspasspasspasspassit 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-originalpassfailfailpassfailpasspasssubstitutions 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-errorpasspasspasspasspasspasspassonly 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-omittedpasspasspasspasspasspasspassan 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-levelspasspassfailpassfailpasspassthe 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-environmentpassfailpasspasspasspasspassan 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-backfailpasspassfailpassfailpassan 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-originalpasspasspassfailpassfailpasswith neither common.x.y nor x.y defined, the required ${x.y} in the included file is unresolved
011-optional-reference-falls-back-toofailpasspassfailpassfailpassthe 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-winspassfailfailpassfailpasspassan 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-backpasspasspasspasspasspasspassthe 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-resultfailpasspassfailpassfailpassan 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-errorpasspassfailfailpassfailpasswhen 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-uppasspasspasspasspasspasspassa 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-uppasspasspasspasspasspasspassa substitution that is a list element in the included file is fixed up as well
018-substitution-in-a-merge-stack-is-fixed-uppasspassfailpassfailpasspassan 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-foundpassfailfailpassfailpasspassa.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 14/15 · hocon2 12/15 · pulse-pyhocon 11/15 · py.hocon 12/15 · pyhocon 12/15 · rs.hocon 13/15 · zig-hocon 14/15
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-include-later-in-key-is-literalpasspasspasspasspasspasspassunquoted 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-stringpasspasspasspasspasspasspassunquoted 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-stringpasspasspasspasspasspasspassunquoted 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-argumentpasspassfailpasspasspasspassvalue 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-argumentpasspasspasspasspasspasspassno 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-quotedpasspasspasspasspasspasspassif 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-keypasspasspasspasspasspasspassonly 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-quotedpasspasspasspasspasspasspassthe argument inside file() must be a single quoted string; an unquoted name is a syntax error
009-file-missing-close-parenthesispasspasspassfailpasspasspassfile("...") must be closed; the line ends before the parenthesis
010-required-missing-close-parenthesispasspasspassfailpassfailpassrequired(...) must be closed too
011-url-argument-must-be-a-urlfailfailfailfailfailfailfailthe string inside url() is interpreted as a URL, and x.conf has no scheme, so it is not one
013-newlines-between-include-and-namepasspassfailpassfailpasspassany amount of whitespace, newlines included, may stand between include and the resource name
014-whitespace-inside-parenthesespassfailpasspasspasspasspasswhitespace inside the parentheses, outside the quotes, is allowed
015-include-followed-by-separator-is-not-a-keypasspassfailpassfailpasspassan 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-fieldpassfailpasspasspasspasspassan 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-equals-as-separatorpasspasspasspasspasspasspassthe = character can be used anywhere JSON allows :, to separate keys from values
002-omitted-separator-before-objectpasspasspasspasspasspasspassif a key is followed by {, the : or = may be omitted; the spec's own example is "foo" {} meaning "foo" : {}
003-omitted-separator-unquoted-keypasspasspasspasspasspasspassthe separator-omission rule before { applies to unquoted keys too, not just the quoted example in the spec
004-value-on-the-line-after-the-separatorpasspasspasspasspasspasspass= stands wherever JSON allows :, and JSON allows any whitespace, newlines included, between the separator and the value
005-separator-cannot-be-omitted-before-arraypasspasspasspasspasspasspassthe separator may be omitted only before {; a key followed directly by [ is invalid
006-newline-between-key-and-bracepasspasspasspasspasspasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-list-from-indexed-variablespassfailfailpassfailpasspassthe [] 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-undefinedpassfailpasspasspasspasspasswith no CONFORMANCE_LIST_0 set, an optional ${?X[]} is undefined and the field is not created
003-required-list-with-no-elements-is-errorpasspasspasspasspasspasspasswith no CONFORMANCE_LIST_0 set, a required ${X[]} is an error
004-collection-stops-at-the-first-gappassfailfailpassfailpasspassindices are collected from 0 until one is missing, so with _0 and _2 set the list holds only the first
005-elements-are-stringspassfailfailpassfailpasspassan element taken from the environment is a string, so 1 stays "1"
006-config-keys-are-not-collectedpassfailpasspasspasspasspassthe [] 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-basic-triple-quotepasspasspasspasspasspasspassthe three-character sequence """ starts a multi-line string whose unicode characters are used unmodified to create the string value
002-newlines-and-whitespace-preservedpasspasspasspasspasspasspassnewlines and whitespace inside a multi-line string receive no special treatment and are kept in the value
003-unicode-escape-not-interpretedpasspasspasspasspasspasspassunlike 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-stringpasspasspasspasspasspasspassany 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-extrapasspassfailpasspasspasspassa 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-illegalpasspasspasspasspasspasspassa 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-closepasspassfailpasspasspasspassonly 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-literallypasspassfailpasspasspasspassa backslash sequence inside a triple-quoted string is used unmodified, not as an escape
009-six-quotes-are-an-empty-stringpasspasspasspasspasspasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-object-with-numeric-keys-stays-objectpasspasspasspasspasspasspassthe 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-arraypassfailfailpassfailpasspassthe 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-keyspassfailfailpassfailpasspassthe 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-convertedpasspasspasspasspasspasspassthe 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-convertedpasspasspasspasspasspasspassthe 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-reindexedpassfailfailpassfailpasspassthe 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-conversionpassfailfailpassfailpasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-implicit-root-objectpasspasspasspasspasspasspassif the file does not begin with [ or {, it is parsed as if enclosed with {} curly braces
002-explicit-root-object-unaffectedpasspasspasspasspasspasspassthe 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-illegalpasspasspassfailpassfailpassa HOCON file is invalid if it omits the opening { but still has a closing }, since the curly braces must be balanced
004-empty-filepassfailpasspasspasspasspassan 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-illegalpasspasspasspasspasspasspassthe 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-unaffectedfailpasspassfailpassfailpassthe 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-rootfailpasspassfailpassfailpassan 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-rootfailpasspassfailpassfailpassthe {} 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-rootfailpasspassfailpassfailpassleading whitespace does not trigger the {} wrap either, for the same reason as a leading commentjava: unsupported
011-trailing-array-after-root-arraypasspasspasspasspasspasspasstwo 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-illegalpasspasspassfailpassfailpassa 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-illegalpasspasspasspasspasspasspassan opening brace with no closing brace is unbalanced
014-comment-only-filepassfailpasspasspasspasspassa file holding only a comment does not begin with a bracket or brace, so it is the empty object
015-bare-number-root-illegalpasspasspasspasspasspasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-quoted-dot-has-no-meaningpasspasspasspasspasspasspassa 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-pathpasspassfailpassfailpasspassfoo.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-stringpasspasspasspasspasspasspass10.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-dotpasspasspasspasspasspasspassfoo10.0 is an unquoted string with a dot in it, giving the two-element path foo10 and 0
005-unquoted-concatenated-with-quoted-numberpasspassfailfailfailfailpassfoo followed by quoted "10.0" is an unquoted then a quoted string which concatenate, giving a single-element path
006-all-numeric-pathpasspasspasspasspasspasspassdots in numbers count as path separators, so 1.2.3 is the three-element path with elements 1, 2, 3
008-value-concatenation-keeps-booleanpasspasspasspasspasspasspassunlike 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-validpasspassfailpassfailpasspassa 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-invalidpasspasspasspassfailpasspassan unquoted empty path element is invalid, so a..b must generate an error
011-path-starting-with-dot-invalidpasspasspasspassfailpasspassa path that starts with a dot is invalid and should generate an error
012-path-ending-with-dot-invalidpassfailpasspassfailpasspassa path that ends with a dot is invalid and should generate an error
015-dotted-path-in-substitutionpasspasspasspasspasspasspasspath 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-pathpasspasspasspasspasspasspassa 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-pathpasspasspasspasspasspasspasspath 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-pathpasspasspasspasspasspasspasspath 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-invalidpasspassfailpassfailpasspassan 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-pathpasspasspasspasspasspasspassa 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-literalpasspasspasspasspasspasspassinside 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-stringpasspasspasspasspasspasspassa path expression is always a string, so true in a substitution names the key true rather than a boolean
023-substitution-in-dotted-key-illegalpasspasspasspasspasspasspassa 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-two-element-path-expandspasspasspasspasspasspasspassa 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-expandspasspasspasspasspasspasspassa three-element path key expands through two levels of nesting: foo.bar.baz:42 is equivalent to foo{bar{baz:42}}
003-merge-sibling-pathspasspasspasspasspasspasspassthe 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-keypasspasspasspasspasspasspassbecause 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-stringpasspasspasspasspasspasspasspath expressions are always converted to strings, so the unquoted boolean-looking key true:42 is "true":42
006-unquoted-number-key-becomes-stringpasspasspasspasspasspasspasspath expressions are always converted to strings, so the unquoted numeric key 3:42 is "3":42
007-decimal-key-splits-on-dotpasspasspasspasspasspasspassa 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-keyfailpassfailpassfailpasspassthe 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-ordinarypassfailfailpassfailpasspassonly 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-objectpasspasspasspasspasspasspassthe 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-elementspasspasspasspasspasspasspasswhitespace 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-bare-number-is-dayspasspasspasspasspasspasspassfor getPeriod(), bare numbers are taken to be in days, unlike getDuration() where bare numbers are milliseconds
002-uppercase-unit-is-illegalpasspasspasspasspasspasspassthe 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-spellingspasspasspasspassfailpasspassexactly d, day, days are the supported unit strings for days in the period format
004-weeks-spellingspasspasspasspassfailpasspassexactly w, week, weeks are the supported unit strings for weeks
005-months-spellingspasspasspasspassfailpasspassexactly m, mo, month, months are the supported unit strings for months
006-years-spellingspasspasspasspasspasspasspassexactly y, year, years are the supported unit strings for years
007-month-m-ambiguous-with-duration-minutespasspasspasspassfailpasspassthe 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-separator8 casesJava 6/8 · hocon2 6/8 · pulse-pyhocon 0/8 · py.hocon 8/8 · pyhocon 0/8 · rs.hocon 8/8 · zig-hocon 8/8
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-plus-equals-desugars-to-optional-self-ref-arraypasspassfailpassfailpasspassa += 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-arraypasspassfailpassfailpasspass+= appends an element to a previous array
003-plus-equals-errors-on-non-array-previous-valuepasspassfailpassfailpasspassif 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-twicepasspassfailpassfailpasspasseach += appends to the value the previous line left, so two of them append two elements
005-plus-equals-in-nested-object-with-substitutionpassfailfailpassfailpasspassinside 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-elementfailpassfailpassfailpasspassb 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-keypassfailfailpassfailpasspassthe 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-mergefailpassfailpassfailpasspassl += 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
self-referential-examples26 casesJava 24/26 · hocon2 26/26 · pulse-pyhocon 21/26 · py.hocon 23/26 · pyhocon 22/26 · rs.hocon 23/26 · zig-hocon 26/26
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-isolated-self-reference-is-an-errorpasspasspasspasspasspasspassin 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-valuepasspasspasspasspasspasspasswhen 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-undefinedpasspassfailfailpassfailpassif 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-silentlypasspasspasspasspasspasspassbecause 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-evaluatedpasspasspasspasspasspasspassif 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-literalpasspasspasspasspasspasspassthe 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-belowpasspasspasspasspasspasspassa 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-itselfpasspasspasspasspasspasspassan 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-mergepasspasspasspasspasspasspassbecause 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-referentialpasspasspasspasspasspasspassmutually-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-backpasspasspasspasspasspasspassan 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-unresolvablepasspasspasspasspasspasspassthe 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-invalidpasspasspasspasspasspasspassa 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-valuepasspasspasspasspasspasspassthe 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-evaluatedfailpasspassfailpassfailpassa 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-evaluatedpasspasspasspassfailpasspass${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-evaluatedpasspasspasspassfailpasspassa 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-evaluatedpasspassfailpasspasspasspassa 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-evaluatedpasspasspasspasspasspasspassthe 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-evaluatedpasspasspasspasspasspasspassa 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-orderfailpasspassfailpassfailpassa 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-hidepasspassfailpasspasspasspassan 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-hidepasspassfailpassfailpasspassw += 1 is w = ${?w} [1], which reads the earlier list, so ${does-not-exist} is evaluated: an error
024-substitution-target-is-fetched-lazilypasspasspasspasspasspasspassresolving ${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-undefinedpasspassfailpassfailpasspassa 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-pathpasspasspasspasspasspasspassthe 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
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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-path-builds-on-older-valuepasspasspasspasspasspasspassa 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-concatenationpasspasspasspasspasspasspassa 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-referentialpasspasspasspasspasspasspassan 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-referentialpasspasspasspasspasspasspassan 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-concatenationpasspasspasspasspasspasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-bare-number-is-bytespasspasspasspasspasspasspassbare numbers are taken to be in bytes already
002-single-byte-spellingspasspasspasspasspasspasspassfor single bytes, exactly B, b, byte, bytes are supported
003-powers-of-ten-suffixespasspasspasspasspasspasspassevery power-of-10 suffix and its long forms name the same unit family; one table, one rule
004-powers-of-two-suffixespasspasspasspassfailpasspassevery 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-twopasspasspasspasspasspasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-substitution-in-string-concatenationpasspasspasspasspasspasspassstring value concatenation supports substitutions inside the concatenated string
002-only-simple-values-participatepasspasspasspasspasspasspassit is invalid for arrays or objects to appear in a string value concatenation, since only simple values participate
003-leading-and-trailing-whitespace-trimmedpasspasspasspasspasspasspasswhitespace before the first and after the last simple value is discarded; only the whitespace between the values is kept
004-concatenation-never-spans-newlinepasspasspasspasspasspasspassstring 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-concatenationpasspasspasspasspasspasspassa string value concatenation may appear in any place a string may appear, including array elements
006-boolean-becomes-string-in-concatenationpasspassfailpassfailpasspasstrue and false become the strings "true" and "false" when they participate in a concatenation
007-null-becomes-string-in-concatenationpasspassfailpassfailpasspassnull becomes the string "null" when it participates in a concatenation
008-number-kept-as-written-in-concatenationpasspasspasspasspasspasspassnumbers 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-stringpasspasspasspasspasspasspassa 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-scalarfailpasspasspasspasspasspassit 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-scalarfailpasspasspasspasspasspassthe 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-errorpasspasspasspasspasspasspassthe same rule with the scalar first, which typesafe/config does reject -- the half of the rule it enforces
014-array-then-unquoted-text-without-spacefailpasspasspasspasspasspassan 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-spacefailpasspasspasspasspasspassthe 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-droppedfailpasspasspasspasspasspasstext 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-writtenpasspasspasspasspasspasspassa number in a concatenation is stringified as it appeared in the file, whatever its size
021-false-becomes-string-in-concatenationpasspassfailpassfailpasspassfalse becomes the string "false" in a concatenation, as true does
022-quoted-and-unquoted-strings-concatenatepasspasspasspasspasspasspassa 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-env-fallback-for-undefined-pathpasspasspasspasspasspasspassa substitution whose path is not defined in the configuration falls back to an environment variable of the same name
002-required-substitution-resolved-from-envpasspasspasspasspasspasspassa 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-envpasspasspasspasspasspasspassthe 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-lookuppasspassfailpassfailpasspasssetting 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-sensitivepasspasspasspasspasspasspassthe 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-stringpasspasspasspasspasspasspassan 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-fieldpasspasspasspasspasspasspassan 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-stringspasspasspasspasspasspasspassenvironment 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-basic-substitutionpasspasspasspasspasspasspassthe syntax ${pathexpression} refers to another part of the configuration tree
002-optional-substitution-syntaxpasspasspasspasspasspasspass${?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-illegalpasspasspasspasspasspasspassthe ? in ${?pathexpression} must not have whitespace before it; the three characters ${? must be exactly grouped together
004-substitution-not-parsed-in-quoted-stringpasspasspasspasspasspasspasssubstitutions are not parsed inside quoted strings, so this stays the literal text
005-substitution-value-concatenation-unquotedpasspasspasspasspasspasspassworked example: to get a string containing a substitution, use value concatenation with the substitution in the unquoted portion
006-substitution-value-concatenation-quoted-tailpasspasspasspasspasspasspassworked example: quoting the non-substitution portion also forms a value concatenation with the substitution
007-substitution-path-absolutepasspasspasspasspasspasspasssubstitutions 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-referencepasspasspasspasspasspasspasssubstitution processing happens as the last parsing step, so a substitution can look forward in the configuration
009-substitution-latest-duplicate-keypasspasspasspasspasspasspassif 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-mergepasspasspasspasspasspasspassa substitution to a key set more than once evaluates to the merged object, not an earlier snapshot
011-undefined-required-substitution-is-errorpasspasspasspasspasspasspassan undefined substitution with the ${foo} syntax is invalid and should generate an error
012-undefined-optional-substitution-field-not-createdpasspasspasspasspasspasspassif an undefined ${?foo} substitution is the value of an object field, the field should not be created
013-undefined-optional-substitution-keeps-previous-valuepasspassfailpasspasspasspassif an undefined ${?foo} would override a previously-set value for the same field, the previous value remains
014-undefined-optional-substitution-array-element-not-addedpasspasspasspasspasspasspassif an undefined ${?foo} substitution is an array element, the element should not be added
015-undefined-optional-substitution-concatenation-string-emptypasspasspasspasspasspasspassan undefined ${?foo} in a value concatenation with another string becomes an empty string
016-undefined-optional-substitution-concatenation-object-emptypasspasspasspasspasspasspassan undefined ${?foo} in a value concatenation with an object becomes an empty object
017-undefined-optional-substitution-concatenation-array-emptypasspasspasspasspasspasspassan undefined ${?foo} in a value concatenation with an array becomes an empty array
018-two-optional-substitutions-both-undefined-field-not-createdpasspasspasspasspasspasspassfoo : ${?bar}${?baz} avoids creating the field only if both bar and baz are undefined
019-two-optional-substitutions-one-defined-field-createdpasspasspasspasspasspasspassfoo : ${?bar}${?baz} creates the field once at least one of bar or baz is defined
020-substitution-not-allowed-in-keypasspasspasspasspasspasspasssubstitutions are only allowed in field values and array elements, not in keys
021-substitution-not-allowed-nested-in-path-expressionpasspasspasspasspasspasspasssubstitutions are not allowed nested inside other substitutions (path expressions)
022-substitution-preserves-type-arraypasspasspasspasspasspasspassa 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-stringpasspasspasspasspasspasspasswhen 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-nullpasspassfailpassfailpasspassa 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-errorpasspasspasspasspasspasspassthe 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-definedpasspassfailpassfailpasspassa 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-substitutionpasspasspasspasspasspasspasssubstitutions 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-trimmedpassfailpasspassfailpasspassthe 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-illegalpasspasspasspasspasspasspass${} contains no path expression at all, so it is neither a substitution nor anything else
033-path-through-a-non-object-is-undefinedpassfailpasspasspasspasspassa 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-omittedpassfailpasspassfailpasspassthe same path with the optional form: undefined, so the field is not created
035-quoted-path-element-with-spacepasspasspasspasspasspasspassa 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-elementpasspassfailpassfailpasspassan empty path element is legal when quoted, in a substitution as in a key
037-path-into-a-list-element-is-undefinedpassfailpasspassfailpasspassa 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-markpassfailpasspassfailpasspassonly 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-stringfailfailpassfailfailfailpass${?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-lookuppasspassfailpassfailpasspassthe 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-quoted-string-json-formatpasspassfailpassfailpasspassquoted strings are in the same format as JSON strings, so JSON escapes like \n, \t, \", \\ and \u unicode escapes are honored
002-string-value-typepasspasspasspasspasspasspassstring is one of the possible value types
003-number-value-typepasspasspasspasspasspasspassnumber is one of the possible value types
004-object-value-typepasspasspasspasspasspasspassobject is one of the possible value types
005-array-value-typepasspasspasspasspasspasspassarray is one of the possible value types
006-boolean-value-typepasspasspasspasspasspasspassboolean is one of the possible value types
007-null-value-typepasspasspasspasspasspasspassnull is one of the possible value types
008-number-format-decimal-and-exponentpasspasspasspasspasspasspassallowed number formats match JSON, including negative numbers, decimals, and exponent notation
009-number-leading-zero-illegalfailfailfailfailfailfailpassallowed 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-illegalfailfailfailfailfailfailpassallowed 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-illegalfailpassfailfailfailfailpassthe 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-illegalfailfailfailfailfailfailpassJSON 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-illegalfailfailfailfailfailfailpassthe 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-illegalfailfailfailfailfailfailpassJSON 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-escapespasspassfailpassfailpasspassthe JSON escapes \/ \b \f and \r are valid in a quoted string and mean slash, backspace, form feed and carriage return
016-unknown-escape-illegalpasspassfailpassfailpasspassonly the JSON escape sequences exist; \q is not one of them, so the string is invalid
017-non-hex-unicode-escape-illegalpasspassfailpassfailpasspass\u must be followed by exactly four hex digits; zzzz are not hex digits
018-unterminated-quoted-string-illegalpasspasspasspasspasspasspassa quoted string that is still open at the end of the file is invalid
019-backslash-at-end-of-file-illegalpasspasspasspasspasspasspassa 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-illegalpasspasspasspasspasspasspassthe file ends after two of the four hex digits a \u escape requires
021-raw-newline-in-quoted-string-illegalpasspassfailpasspasspasspassJSON forbids unescaped control characters in strings, and a raw newline is one; a quoted string cannot span lines
022-raw-tab-in-quoted-string-illegalpasspassfailfailfailfailpassa raw tab is a control character too, so it must be written as \t inside quotes
023-64-bit-integerpasspasspasspasspasspasspassa number beyond 32 bits is still a number and keeps its exact value
024-raw-utf8-in-quoted-stringpasspasspasspassfailpasspassthe file is UTF-8, so a multi-byte sequence is one character
025-nan-is-not-a-numberpassfailpasspasspasspasspassNaN is not a JSON number, so the bare token is an unquoted string
026-surrogate-pair-escapepasspassfailpassfailfailpassJSON writes a character outside the BMP as two \u escapes forming a surrogate pair, which decode to one character
027-unpaired-surrogate-escape-is-keptpasspassfailpassfailfailpassquoted 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-number-value-is-default-unitpasspasspasspasspasspasspassif the value is a number, it is taken to be a number in the default unit
002-quoted-string-with-no-unit-uses-default-unitpasspasspasspasspasspasspassa 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-interpretationpasspasspasspassfailpasspassa string value with a unit name has that name specify the value's interpretation
004-whitespace-optional-around-number-and-unitpasspasspasspasspasspasspassthe units-format grammar is optional whitespace, a number, optional whitespace, an optional unit name, optional whitespace
005-unit-name-must-be-letters-onlypasspasspasspasspasspasspassthe 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-illegalpasspasspasspasspasspasspassthe 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-strings24 casesJava 22/24 · hocon2 21/24 · pulse-pyhocon 11/24 · py.hocon 21/24 · pyhocon 16/24 · rs.hocon 21/24 · zig-hocon 24/24
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-simple-unquoted-stringpasspasspasspasspasspasspassa sequence of characters outside a quoted string is a string value when it contains none of the forbidden characters
002-quoted-alternative-for-forbidden-charpasspasspasspasspasspasspassa 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-signpasspassfailpasspasspasspass'@' is one of the listed forbidden characters, so it cannot appear inside an unquoted string
004-double-slash-starts-commentpasspassfailpassfailpasspassthe two-character string '//' starts a comment, ending the unquoted string before it even mid-token
005-single-slash-allowedpasspasspasspasspasspasspassonly 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-foopassfailpasspasspasspasspassworked 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-stringpasspasspasspasspasspasspassworked example: footrue does not begin with true, false, null, or a number, so it parses as a single unquoted string
008-number-then-suffix-stringpasspasspasspasspasspasspassworked 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-stringpasspasspasspasspasspasspassworked 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-recognizedpasspasspasspasspasspasspassembedded (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-backslashpasspassfailpassfailpasspassbackslash 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-hyphenfailfailfailfailfailfailpassan 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-stringpasspasspasspasspasspasspassa 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-stringpasspassfailpasspasspasspass'#' is forbidden inside an unquoted string because it already has meaning in HOCON: it starts a comment
015-reserved-forbidden-char-no-current-meaningpasspassfailpasspasspasspasssome 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-unquotedpasspasspasspasspasspasspassunquoted strings place no restriction on control characters, other than the forbidden characters listed
017-control-char-forbidden-in-quotedpasspassfailfailfailfailpassquoted JSON strings may not contain control characters, per the JSON spec, unlike unquoted strings
018-leading-dot-is-not-a-numberpasspassfailpassfailpasspassonly 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-substitutionpasspassfailpasspasspasspass$ is a forbidden character in an unquoted string, and outside quotes it may only begin ${; $foo is neither
020-plus-must-be-followed-by-equalspasspassfailpasspasspasspass+ 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-illegalpasspassfailpassfailpasspass1+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-illegalfailfailfailfailfailfailpass1e 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-filepasspasspasspasspasspasspassan 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-illegalpasspassfailpassfailpasspassunquoted 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
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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-simple-values-concatenate-to-stringpasspasspasspasspasspasspassif all the values are simple values they are concatenated into a string
004-string-concatenation-allowed-in-field-keyspasspasspasspasspasspasspassstring 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
caseJavahocon2pulse-pyhoconpy.hoconpyhoconrs.hoconzig-hoconrule
001-nonbreaking-space-as-whitespacepasspassfailpassfailpasspassa nonbreaking space (0x00A0) is a Unicode Zs separator and must be treated as whitespace
002-bom-at-start-is-whitespacepasspassfailpassfailpasspassthe BOM (0xFEFF) must be treated as whitespace, not as part of the key
003-unicode-line-paragraph-separatorpasspassfailfailpassfailpassthe 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-whitespacepasspassfailpassfailpasspassvertical tab (0x000B) and form feed (0x000C) are whitespace characters
005-ascii-separator-control-charspasspasspassfailpassfailpassthe ASCII separator controls 0x1C-0x1F are whitespace but not newlines, so they do not separate two fields
006-special-nonbreaking-spacespasspassfailpassfailpasspassthe spec names figure space (0x2007) and narrow no-break space (0x202F) explicitly among the nonbreaking spaces treated as whitespace
007-tab-is-whitespacepasspasspasspasspasspasspassa tab is whitespace around a key and a separator
008-crlf-line-endingspasspasspasspasspasspasspassa 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-newlinepassfailfailfailfailfailpassa lone carriage return is whitespace but not a newline, so 1 b concatenates and the second = is a stray token
010-line-separator-is-whitespacepasspassfailpassfailpasspassU+2028 around the separator is whitespace, not part of the key or the value
011-ascii-separator-controls-are-whitespacepasspassfailpassfailpasspassthe file, group, record and unit separators 0x1C-0x1F are whitespace around keys and separators
012-ideographic-space-is-whitespacepasspassfailpassfailpasspassthe ideographic space U+3000 is a Zs space separator and so whitespace, like every member of the category
013-bom-inside-the-document-is-whitespacepasspassfailpassfailpasspassthe BOM is whitespace wherever it appears, not only as a file-start marker