From 9fad6dfb19be96bb75ea1d20531afad5f61c498a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Milo=C5=A1=20Koz=C3=A1k?= HOCON (Human-Optimized Config Object Notation)
@@ -867,19 +867,22 @@ Include semantics: locating resources
specified, the included items are NOT interpreted relative to the
including items. Relative-to-including-file paths only work with
the heuristic `include "foo.conf"`. This may change in the future.
The string inside `url()` is always an absolute URL, with a scheme +([RFC 3986, section 4.3](https://www.rfc-editor.org/rfc/rfc3986#section-4.3)); +`url("foo.conf")` is an error, with or without `required()`.
In some file formats and contexts, such as Java properties files, +
In some file formats and contexts, such as Java properties files, there isn't a good way to define arrays. To provide some mechanism for this, implementations should support converting objects with numeric keys into arrays. For example, this object:
-{ "0" : "a", "1" : "b" }
-could be treated as:
-[ "a", "b" ]
-This allows creating an array in a properties file like this:
-foo.0 = "a" +
{ "0" : "a", "1" : "b" }
+could be treated as:
+[ "a", "b" ]
+This allows creating an array in a properties file like this:
+foo.0 = "a" foo.1 = "b"
-The details:
- - the conversion should be done lazily when required to avoid
+ The details: - the conversion should be done lazily when required to avoid
a type error, NOT eagerly anytime an object has numeric
keys.
- the conversion should be done when you would do an automatic
@@ -896,14 +899,14 @@ Conversion of numerically-indexed objects to arrays
"2" then the resulting array would have indices "0" and "1",
i.e. missing indices in the object are eliminated.
Use "application/hocon" for Content-Type.
+Use "application/hocon" for Content-Type.
Implementations of HOCON ideally follow certain conventions and +
Implementations of HOCON ideally follow certain conventions and work in a predictable way.
If an application asks for a value with a particular type, the +
If an application asks for a value with a particular type, the implementation should attempt to convert types as follows:
-- number to string: convert the number into a string +
- number to string: convert the number into a string representation that would be a valid number in JSON. - boolean to string: should become the string "true" or "false" - string to number: parse the number with the JSON rules @@ -917,51 +920,51 @@
The following type conversions should NOT be performed:
-- null to anything: If the application asks for a specific type +
The following type conversions should NOT be performed:
+- null to anything: If the application asks for a specific type and finds null instead, that should usually result in an error. - object to anything - array to anything - anything to object - anything to array, with the exception of numerically-indexed object to array
-Converting objects and arrays to and from strings is tempting, but +
Converting objects and arrays to and from strings is tempting, but in practical situations raises thorny issues of quoting and double-escaping.
Implementations may wish to support interpreting a value with some +
Implementations may wish to support interpreting a value with some family of units, such as time units or memory size units: `10ms` or `512K`. HOCON does not have an extensible type system and there is no way to add a "duration" type. However, for example, if an application asks for milliseconds, the implementation can try to interpret a value as a milliseconds value.
-If an API supports this, for each family of units it should define +
If an API supports this, for each family of units it should define a default unit in the family. For example, the family of duration units might default to milliseconds (see below for details on durations). The implementation should then interpret values as follows:
- - if the value is a number, it is taken to be a number in
+ - if the value is a number, it is taken to be a number in
the default unit.
- if the value is a string, it is taken to be this sequence: - optional whitespace
+ - optional whitespace
- a number
- optional whitespace
- an optional unit name consisting only of letters (letters
are the Unicode `L*` categories, Java `isLetter()`)
- optional whitespace If a string value has no unit name, then it should be
+ If a string value has no unit name, then it should be
interpreted with the default unit, as if it were a number. If a
string value has a unit name, that name of course specifies the
value's interpretation. Implementations may wish to support a `getMilliseconds()` (and
+ Implementations may wish to support a `getMilliseconds()` (and
similar for other time units). This can use the general "units format" described above; bare
+ This can use the general "units format" described above; bare
numbers are taken to be in milliseconds already, while strings are
parsed as a number plus an optional unit string. The supported unit strings for duration are case-sensitive and
+ The supported unit strings for duration are case-sensitive and
must be lowercase. Exactly these strings are supported: - `ns`, `nano`, `nanos`, `nanosecond`, `nanoseconds`
+ - `ns`, `nano`, `nanos`, `nanosecond`, `nanoseconds`
- `us`, `micro`, `micros`, `microsecond`, `microseconds`
- `ms`, `milli`, `millis`, `millisecond`, `milliseconds`
- `s`, `second`, `seconds`
@@ -969,14 +972,14 @@ Similar to the `getDuration()` method, there is a `getPeriod()` method
+ Similar to the `getDuration()` method, there is a `getPeriod()` method
available for getting time units as a `java.time.Period`. This can use the general "units format" described above; bare
+ This can use the general "units format" described above; bare
numbers are taken to be in days, while strings are
parsed as a number plus an optional unit string. The supported unit strings for period are case-sensitive and
+ The supported unit strings for period are case-sensitive and
must be lowercase. Exactly these strings are supported: - `d`, `day`, `days`
+ - `d`, `day`, `days`
- `w`, `week`, `weeks`
- `m`, `mo`, `month`, `months` (note that if you are using `getTemporal()`
which may return either a `java.time.Duration` or a `java.time.Period`
@@ -984,15 +987,15 @@ Implementations may wish to support a `getBytes()` returning a
+ Implementations may wish to support a `getBytes()` returning a
size in bytes. This can use the general "units format" described above; bare
+ This can use the general "units format" described above; bare
numbers are taken to be in bytes already, while strings are
parsed as a number plus an optional unit string. The one-letter unit strings may be uppercase (note: duration units
+ The one-letter unit strings may be uppercase (note: duration units
are always lowercase, so this convention is specific to size
units). There is an unfortunate nightmare with size-in-bytes units, that
+ There is an unfortunate nightmare with size-in-bytes units, that
they may be in powers or two or powers of ten. The approach
defined by standards bodies appears to differ from common usage,
such that following the standard leads to people being confused.
@@ -1002,10 +1005,10 @@ For single bytes, exactly these strings are supported: - `B`, `b`, `byte`, `bytes` For powers of ten, exactly these strings are supported: - `kB`, `kilobyte`, `kilobytes`
+ For single bytes, exactly these strings are supported: - `B`, `b`, `byte`, `bytes` For powers of ten, exactly these strings are supported: - `kB`, `kilobyte`, `kilobytes`
- `MB`, `megabyte`, `megabytes`
- `GB`, `gigabyte`, `gigabytes`
- `TB`, `terabyte`, `terabytes`
@@ -1013,8 +1016,8 @@ For powers of two, exactly these strings are supported: - `K`, `k`, `Ki`, `KiB`, `kibibyte`, `kibibytes`
+ For powers of two, exactly these strings are supported: - `K`, `k`, `Ki`, `KiB`, `kibibyte`, `kibibytes`
- `M`, `m`, `Mi`, `MiB`, `mebibyte`, `mebibytes`
- `G`, `g`, `Gi`, `GiB`, `gibibyte`, `gibibytes`
- `T`, `t`, `Ti`, `TiB`, `tebibyte`, `tebibytes`
@@ -1022,97 +1025,97 @@ It's very unclear which units the single-character abbreviations
+ It's very unclear which units the single-character abbreviations
("128K") should go with; some precedents such as `java -Xmx 2G`
and the GNU tools such as `ls` map these to powers of two, so this
spec copies that. You can certainly find examples of mapping these
to powers of ten, though. If you don't like ambiguity, don't use
the single-letter abbreviations. Note: any value in zetta/zebi or yotta/yobi will overflow a 64-bit
+ Note: any value in zetta/zebi or yotta/yobi will overflow a 64-bit
integer, and of course large-enough values in any of the units may
overflow. Most real-world APIs and apps will not support byte
counts that overflow a 64-bit integer. The huge units are provided
just to be complete but probably aren't useful in practice. At
least not in 2014. It may be useful to offer a method to merge two objects. If such a
+ It may be useful to offer a method to merge two objects. If such a
method is provided, it should work as if the two objects were
duplicate values for the same key in the same file. (See the
section earlier on duplicate key handling.) As with duplicate keys, an intermediate non-object value "hides"
+ As with duplicate keys, an intermediate non-object value "hides"
earlier object values. So say you merge three objects in this
order: - `{ a : { x : 1 } }` (first priority)
+ - `{ a : { x : 1 } }` (first priority)
- `{ a : 42 }` (fallback)
- `{ a : { y : 2 } }` (another fallback) The result would be `{ a : { x : 1 } }`. The two objects are not
+ The result would be `{ a : { x : 1 } }`. The two objects are not
merged because they are not "adjacent"; the merging is done in
pairs, and when `42` is paired with `{ y : 2 }`, `42` simply wins
and loses all information about what it overrode. But if you re-ordered like this: - `{ a : { x : 1 } }` (first priority)
+ But if you re-ordered like this: - `{ a : { x : 1 } }` (first priority)
- `{ a : { y : 2 } }` (fallback)
- `{ a : 42 }` (another fallback) Now the result would be `{ a : { x : 1, y : 2 } }` because the two
+ Now the result would be `{ a : { x : 1, y : 2 } }` because the two
objects are adjacent. This rule for merging objects loaded from different files is
+ This rule for merging objects loaded from different files is
_exactly_ the same behavior as for merging duplicate fields in the
same file. All merging works the same way. Needless to say, normally it's well-defined whether a config
+ Needless to say, normally it's well-defined whether a config
setting is supposed to be a number or an object. This kind of
weird pathology where the two are mixed should not be happening. The one place where it matters, though, is that it allows you to
+ The one place where it matters, though, is that it allows you to
"clear" an object and start over by setting it to null and then
setting it back to a new object. So this behavior gives people a
way to get rid of default fallback values they don't want. It may be useful to merge Java properties data with data loaded
+ It may be useful to merge Java properties data with data loaded
from JSON or HOCON. See the Java properties spec here:
https://docs.oracle.com/javase/8/docs/api/java/util/Properties.html#load-java.io.Reader- Java properties parse as a one-level map from string keys to
+ Java properties parse as a one-level map from string keys to
string values. To convert to HOCON, first split each key on the `.` character,
+ To convert to HOCON, first split each key on the `.` character,
keeping any empty strings (including leading and trailing empty
strings). Note that this is _very different_ from parsing a path
expression. The key split on `.` is a series of path elements. So the
+ The key split on `.` is a series of path elements. So the
properties key with just `.` is a path with two elements, both of
them an empty string. `a.` is a path with two elements, `a` and
empty string. (Java's `String.split()` does NOT do what you want
for this.) It is impossible to represent a key with a `.` in it in a
+ It is impossible to represent a key with a `.` in it in a
properties file. If a JSON/HOCON key has a `.` in it, which is
possible if the key is quoted, then there is no way to refer to it
as a Java property. It is not recommended to name HOCON keys with
a `.` in them, since it would be confusing at best in any case. Once you have a path for each value, construct a tree of
+ Once you have a path for each value, construct a tree of
JSON-style objects with the string value of each property located
at that value's path. Values from properties files are _always_ strings, even if they
+ Values from properties files are _always_ strings, even if they
could be parsed as some other type. Implementations should do type
conversion if an app asks for an integer, as described in an
earlier section. When Java loads a properties file, unfortunately it does not
+ When Java loads a properties file, unfortunately it does not
preserve the order of the file. As a result, there is an
intractable case where a single key needs to refer to both a
parent object and a string value. For example, say the Java
properties file has: a=hello
+ a=hello
a.b=world In this case, `a` needs to be both an object and a string value.
+ In this case, `a` needs to be both an object and a string value.
The _object_ must always win in this case... the "object wins"
rule throws out at most one value (the string) while "string wins"
would throw out all values in the object. Unfortunately, when
properties files are mapped to the JSON structure, there is no way
to access these strings that conflict with objects. The usual rule in HOCON would be that the later assignment in the
+ The usual rule in HOCON would be that the later assignment in the
file wins, rather than "object wins"; but implementing that for
Java properties would require implementing a custom Java
properties parser, which is surely not worth it and wouldn't help
with system properties anyway. By convention, JVM apps have two parts to their configuration: - the _reference_ config is made up of all resources named
+ By convention, JVM apps have two parts to their configuration: - the _reference_ config is made up of all resources named
`reference.conf` on the classpath, merged in the order they
are returned by `ClassLoader.getResources()`; also, system
property overrides are applied.
@@ -1126,38 +1129,38 @@ The reference config for a given class loader should be merged and
+ The reference config for a given class loader should be merged and
resolved first, and may be shared among all application configs in
that class loader. Substitutions in the reference config are not
affected by any application configs, because the reference config
should be resolved by itself. The application config should then be loaded, have the reference
+ The application config should then be loaded, have the reference
config added as a fallback, and have substitutions resolved. This
means the application config can refer to the reference config in
its substitutions. For an application's config, Java system properties _override_
+ For an application's config, Java system properties _override_
settings found in the configuration file. This supports specifying
config options on the command line. Recall that if a substitution is not present (not even set to
+ Recall that if a substitution is not present (not even set to
`null`) within a configuration tree, implementations may search
for it from external sources. One such source could be environment
variables. It's recommended that HOCON keys always use lowercase, because
+ It's recommended that HOCON keys always use lowercase, because
environment variables generally are capitalized. This avoids
naming collisions between environment variables and configuration
properties. (While on Windows getenv() is generally not
case-sensitive, the lookup will be case-sensitive all the way
until the env variable fallback lookup is reached). See also the notes below on Windows and case sensitivity. An application can explicitly block looking up a substitution in
+ See also the notes below on Windows and case sensitivity. An application can explicitly block looking up a substitution in
the environment by setting a value in the configuration, with the
same name as the environment variable. You could set `HOME : null`
in your root object to avoid expanding `${HOME}` from the
environment, for example. Environment variables are interpreted as follows: - env variables set to the empty string are kept as such (set to
+ Environment variables are interpreted as follows: - env variables set to the empty string are kept as such (set to
empty string, rather than undefined)
- System.getenv throws SecurityException: treated as not present
- encoding is handled by Java (System.getenv already returns
@@ -1166,16 +1169,16 @@ Config keys are encouraged to be `hyphen-separated` rather than
+ Config keys are encouraged to be `hyphen-separated` rather than
`camelCase`. You can write a HOCON file that looks much like a Java properties
+ You can write a HOCON file that looks much like a Java properties
file, and many valid Java properties files will also parse as
HOCON. However, HOCON is not a Java properties superset and the corner
+ However, HOCON is not a Java properties superset and the corner
cases work like JSON, not like properties. Differences include but are probably not limited to: - certain characters that can be unquoted in properties files
+ Differences include but are probably not limited to: - certain characters that can be unquoted in properties files
have to be placed in JSON-style double-quoted strings in HOCON
- unquoted strings in HOCON do not support escape sequences
- unquoted strings in HOCON do not preserve trailing whitespace
@@ -1190,13 +1193,13 @@ HOCON's lookup of environment variable values is always case-sensitive, but
+ HOCON's lookup of environment variable values is always case-sensitive, but
Linux and Windows differ in their handling of case. Linux allows one to define multiple environment variables with the same
+ Linux allows one to define multiple environment variables with the same
name but with different case; so both "PATH" and "Path" may be defined
simultaneously. HOCON's access to these environment variables on Linux
is straightforward; ie just make sure you define all your vars with the required case. Windows is more confusing. Windows environment variables names may contain a
+ Windows is more confusing. Windows environment variables names may contain a
mix of upper and lowercase characters, eg "Path", however Windows does not
allow one to define multiple instances of the same name but differing in case.
Whilst accessing env vars in Windows is case-insensitive, accessing env vars in
@@ -1209,15 +1212,15 @@ For example, the ambient environment might have this definition ... For example, the ambient environment might have this definition ... .. we just don't know. But if the HOCON needs "PATH", then the start script must
+ .. we just don't know. But if the HOCON needs "PATH", then the start script must
take a precautionary approach and enforce the necessary case as follows ... You cannot know what ambient environment variables might exist in the ambient environment
+ You cannot know what ambient environment variables might exist in the ambient environment
when your program is invoked, nor what case those definitions might have.
Therefore the only safe thing to do is redefine all the vars you rely on as shown above.Duration format
-Duration format
- `h`, `hour`, `hours`
- `d`, `day`, `days`Period Format
-Period Format
parsed as minutes)
- `y`, `year`, `years`Size in bytes format
-Size in bytes format
https://en.wikipedia.org/wiki/Binary_prefix#Deviation_between_powers_of_1024_and_powers_of_1000
for examples. It appears impossible to sort this out without
causing confusion for someone sometime.Size in bytes format
- `EB`, `exabyte`, `exabytes`
- `ZB`, `zettabyte`, `zettabytes`
- `YB`, `yottabyte`, `yottabytes`Size in bytes format
- `E`, `e`, `Ei`, `EiB`, `exbibyte`, `exbibytes`
- `Z`, `z`, `Zi`, `ZiB`, `zebibyte`, `zebibytes`
- `Y`, `y`, `Yi`, `YiB`, `yobibyte`, `yobibytes`Config object merging and file merging
-Java properties mapping
-Conventional configuration files for JVM apps
-Conventional configuration files for JVM apps
to go with the code in that jar.
- a single JVM may have multiple application configs if
it has multiple modules or contexts of some kind.Conventional override by system properties
-Substitution fallback to environment variables
-Substitution fallback to environment variables
if an app asks for another type automatic type conversion
would kick inhyphen-separated vs. camelCase
-Note on Java properties similarity
-Note on Java properties similarity
occur as the first character on the line
- HOCON interprets `${}` as a substitutionNote on Windows and case sensitivity of environment variables
-Note on Windows and case sensitivity of environment variables
The only way to ensure that your environment variables have the desired case
is to first undefine all the env vars that you will depend on then redefine
them with the required case.set Path=A;B;C
-set OLDPATH=%PATH%
set PATH=
set PATH=%OLDPATH%
%JAVA_HOME%/bin/java ....
-HOCON Conformance
- Where the two bars disagree
By section of the specification
One fold per heading of HOCON.md.
- array-and-object-concatenation19 casesJava 17/19 · hocon2 19/19 · pulse-pyhocon 19/19 · py.hocon 19/19 · pyhocon 19/19 · rs.hocon 19/19 · zig-hocon 19/19
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-arrays-concatenate pass pass pass pass pass pass pass arrays can be concatenated with arrays when only non-newline whitespace separates them 002-objects-concatenate pass pass pass pass pass pass pass objects can be concatenated with objects when only non-newline whitespace separates them 003-mixing-array-and-object-is-error pass pass pass pass pass pass pass it is an error to concatenate an array with an object 004-substitution-counts-as-array-for-concatenation pass pass pass pass pass pass pass for purposes of concatenation a substitution that resolves to an array also means an array, so ${x} [ 1, 2 ] with x an array concatenates into one array 005-substitution-counts-as-object-for-concatenation pass pass pass pass pass pass pass for purposes of concatenation a substitution that resolves to an object also means an object, so ${x} { c : 1 } with x an object merges into one object 006-newline-between-values-prevents-concatenation pass pass pass pass pass pass pass newlines between a substitution and an array prevent concatenation, so inside an array they stay two separate elements instead of one concatenated array 007-object-concatenation-merges-and-second-overrides pass pass pass pass pass pass pass for objects, concatenation means merging, and the second object overrides the first on overlapping keys 008-array-cannot-be-field-key pass pass pass pass pass pass pass arrays cannot be field keys; nested inside an object so the rejection comes from the key position, not from the document root 009-object-cannot-be-field-key pass pass pass pass pass pass pass objects cannot be field keys; nested inside an object so the rejection comes from the key position, not from the document root 010-worked-example-object-inheritance pass pass pass pass pass pass pass the spec's worked example: object concatenation with a substitution is a common way to express inheritance 011-worked-example-array-path-append pass pass pass pass pass pass pass the spec's worked example: array concatenation with a substitution is a common way to add to paths 012-worked-example-self-referential-array-concat pass pass pass pass pass pass pass the spec's worked example: a later array definition can concatenate a substitution referring to its own earlier value with a new array 013-newlines-within-value-still-allow-concatenation pass pass pass pass pass pass pass newlines may occur within an array without preventing it from concatenating with a second array on the same line as its closing bracket 014-substitution-object-with-literal-array-is-error pass pass pass pass pass pass pass a substitution that resolves to an object means an object for concatenation, and concatenating an object with an array is an error 015-substitution-array-with-literal-object-is-error pass pass pass pass pass pass pass a substitution that resolves to an array means an array for concatenation, and concatenating an array with an object is an error 016-mixing-object-then-array-is-error pass pass pass pass pass pass pass it is an error to concatenate an object with an array in this order too, not only array then object 017-newline-between-arrays-in-field-value-prevents-concatenation pass pass pass pass pass pass pass a newline between two arrays in a field value prevents concatenation, so the second array begins a new field, and an array cannot be a field key 018-object-with-inner-merge-concatenated-with-substitution fail pass pass pass pass pass pass an object whose field a is itself a merge of an object and a substitution concatenates with a substituted object like any other: the two merge. typesafe/config fails with an internal error (BugOrBroken) because the concatenation is not registered as the merge's parent; the same objects written as literals concatenate finejava: unsupported 019-self-referential-object-concatenation-with-inner-merge fail pass pass pass pass pass pass ${a} in a's own value refers to the earlier a, an object, so the concatenation merges {p: ...} with {q: 1}; the inner p is itself a merge of an object and a substitution. typesafe/config fails with an internal error (BugOrBroken) when the merge inside a concatenation piece resolves, as in 018java: unsupported arrays-without-commas-or-newlines7 casesJava 7/7 · hocon2 7/7 · pulse-pyhocon 6/7 · py.hocon 6/7 · pyhocon 7/7 · rs.hocon 6/7 · zig-hocon 7/7
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-newlines-instead-of-commas pass pass pass pass pass pass pass arrays allow newlines to separate elements instead of commas 002-whitespace-instead-of-comma-concatenates-into-one-element pass pass pass pass pass pass pass the spec's worked example: non-newline whitespace instead of a comma produces concatenation, so this array has one element, the string 1 2 3 4, not four integers 003-newline-separates-two-arrays-into-elements pass pass pass pass pass pass pass the spec's worked example: a newline between two bracketed arrays makes them two separate elements 004-same-line-whitespace-concatenates-two-arrays-into-one-element pass pass pass pass pass pass pass the spec's worked example: whitespace on the same line between two bracketed arrays concatenates them into a single element, the array [1, 2, 3, 4] 005-unquoted-string-concatenation-with-substitutions-in-array pass pass pass pass pass pass pass the spec's worked example: unquoted words and substitutions separated by whitespace concatenate into one string element, and only the comma separates elements 006-two-elements-each-a-substitution-concatenation pass pass pass pass pass pass pass the spec's worked example: whitespace between two substitutions concatenates them into one element, and the comma separates the two concatenations 007-whitespace-never-separates-fields pass pass fail fail pass fail pass non-newline whitespace is never a field separator, so two field definitions cannot be written on one line separated only by spaces commas13 casesJava 13/13 · hocon2 13/13 · pulse-pyhocon 5/13 · py.hocon 12/13 · pyhocon 6/13 · rs.hocon 12/13 · zig-hocon 13/13
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-array-trailing-comma pass pass pass pass pass pass pass the last element in an array may be followed by a single comma, which is ignored: [1,2,3,] and [1,2,3] are the same array 002-array-newline-no-comma pass pass pass pass pass pass pass array elements need not have a comma between them as long as an ASCII newline separates them: [1\n2\n3] and [1,2,3] are the same array 003-array-double-trailing-comma-invalid pass pass fail pass fail pass pass [1,2,3,,] is explicitly called invalid because it has two trailing commas 004-array-initial-comma-invalid pass pass fail pass fail pass pass [,1,2,3] is explicitly called invalid because it has an initial comma 005-array-double-comma-invalid pass pass fail pass fail pass pass [1,,2,3] is explicitly called invalid because it has two commas in a row 006-object-trailing-comma pass pass pass pass pass pass pass these same comma rules apply to fields in objects, so a single trailing comma after the last field is ignored 007-object-newline-no-comma pass pass pass pass pass pass pass fields in objects need not have a comma between them as long as an ASCII newline separates them, same as array elements 008-object-double-comma-invalid pass pass fail pass fail pass pass these same comma rules apply to fields in objects, so two commas in a row between fields is invalid just as in an array 009-fields-on-one-line-need-a-comma pass pass fail fail pass fail pass two fields need a comma or a newline between them; on one line with neither, 1 b becomes a value concatenation and the second = is a stray token 011-trailing-comma-at-implicit-root pass pass pass pass pass pass pass the implicit root object follows the object comma rules, so its last field may carry a single trailing comma 012-leading-comma-at-implicit-root-illegal pass pass fail pass fail pass pass an initial comma is invalid in an object, the implicit root included 013-double-trailing-comma-in-object-illegal pass pass fail pass fail pass pass the array comma rules apply to object fields: two trailing commas are invalid 014-leading-comma-in-object-illegal pass pass fail pass fail pass pass the array comma rules apply to object fields: an initial comma is invalid comments14 casesJava 14/14 · hocon2 14/14 · pulse-pyhocon 14/14 · py.hocon 14/14 · pyhocon 14/14 · rs.hocon 14/14 · zig-hocon 14/14
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-hash-comment pass pass pass pass pass pass pass anything between # and the next newline is a comment and ignored 002-double-slash-comment pass pass pass pass pass pass pass anything between // and the next newline is a comment and ignored 003-whole-line-comment pass pass pass pass pass pass pass a comment may occupy an entire line by itself, ignored just like a trailing one 004-comment-stops-at-newline pass pass pass pass pass pass pass a comment runs only to the next newline, so the following line is parsed normally 005-hash-inside-quoted-string-is-not-a-comment pass pass pass pass pass pass pass a # inside a quoted string is not treated as starting a comment 006-double-slash-inside-quoted-string-is-not-a-comment pass pass pass pass pass pass pass a // inside a quoted string is not treated as starting a comment 007-comment-between-separator-and-value pass pass pass pass pass pass pass a comment runs to the end of its line and is then over; the value may follow on the next line just as it may after a bare = 008-comment-then-blank-line-then-field pass pass pass pass pass pass pass a comment separated from the following field by a blank line is ignored like any other comment 009-comment-inside-object pass pass pass pass pass pass pass comments and blank lines inside an object body are ignored 010-comments-inside-array pass pass pass pass pass pass pass comments and blank lines between array elements are ignored, and do not join or separate elements 011-comment-before-values-of-every-type pass pass pass pass pass pass pass a comment before a field is ignored whatever type its value has 012-comment-before-explicit-root-object pass pass pass pass pass pass pass a comment and a blank line before an explicit root object are skipped before the implicit-braces rule looks at the first token; the spec's wording ("does not begin with") is loose here and this row records the consensus reading, as omit-root-braces/009 does 013-line-comment-after-array pass pass pass pass pass pass pass a // comment after an array is not text in a concatenation; the value is just the array 014-hash-comment-after-array-without-space pass pass pass pass pass pass pass a # comment directly after ] is a comment, not text in a concatenation concatenation-whitespace-and-substitutions9 casesJava 7/9 · hocon2 9/9 · pulse-pyhocon 8/9 · py.hocon 9/9 · pyhocon 7/9 · rs.hocon 9/9 · zig-hocon 9/9
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-unquoted-whitespace-between-substitutions-is-significant-for-strings pass pass pass pass pass pass pass when the substitutions turn out to be strings, the unquoted whitespace between them is significant and survives into the concatenated string 002-quoted-whitespace-between-substitutions-is-error pass pass pass pass pass pass pass between substitutions that resolve to objects, unquoted whitespace is ignored but quoted whitespace should be an error 003-unquoted-whitespace-between-object-and-list-substitutions-is-ignored pass pass pass pass pass pass pass unquoted whitespace between substitutions that resolve to objects or lists must be ignored, so they merge and concatenate as if adjacent 004-tab-between-list-substitutions-is-ignored pass pass pass pass pass pass pass whitespace between two list substitutions is ignored, tabs included, and the lists concatenate 005-nonbreaking-space-between-list-substitutions-is-ignored pass pass pass pass fail pass pass U+00A0 is a Zs space and so whitespace in HOCON; between two list substitutions it is ignored (lightbend/config#862 review) 006-bom-between-list-substitutions-is-ignored pass pass fail pass fail pass pass the BOM must be treated as whitespace, so between two list substitutions it is ignored 007-zero-width-space-between-list-substitutions-is-error fail pass pass pass pass pass pass U+200B is format (Cf), not a space separator, so it is text, and text between two list substitutions is invalid. typesafe/config drops it and concatenates the lists; lightbend/config#862 rejects itjava: lenient 008-list-then-undefined-optional-is-the-list pass pass pass pass pass pass pass an undefined optional substitution in a concatenation with an array becomes an empty array 009-undefined-optional-then-list-is-the-list fail pass pass pass pass pass pass the mirror of 008: an undefined optional substitution before an array becomes an empty array too. typesafe/config keeps the whitespace before the list as text and rejects it, while accepting 008 (noted on lightbend/config#862)java: unsupported config-and-file-merging4 casesJava 4/4 · hocon2 4/4 · pulse-pyhocon 4/4 · py.hocon 4/4 · pyhocon 2/4 · rs.hocon 4/4 · zig-hocon 4/4
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 002-file-merge-is-recursive-and-later-file-wins pass pass pass pass pass pass pass merging objects loaded from different files is exactly the same behavior as merging duplicate fields in the same file: object-valued fields present in both files merge recursively and a non-object field takes the value from the later file 003-non-object-between-files-hides-earlier-object pass pass pass pass fail pass pass the spec's own example: an intermediate non-object value hides earlier object values because merging is done in pairs, so when 42 is paired with { y : 2 } it simply wins and loses all information about what it overrode, and the two objects are never merged 004-adjacent-objects-across-files-merge pass pass pass pass fail pass pass the spec's re-ordered example: with the non-object 42 moved to the lowest priority the two objects are adjacent, so they merge to { a : { x : 1, y : 2 } } 005-null-clears-included-object pass pass pass pass pass pass pass setting an object to null and then setting it back to a new object clears it and starts over, which is how to get rid of default fallback values from another file that you don't want duplicate-keys-and-object-merging28 casesJava 20/28 · hocon2 28/28 · pulse-pyhocon 21/28 · py.hocon 27/28 · pyhocon 24/28 · rs.hocon 27/28 · zig-hocon 28/28
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-later-scalar-wins pass pass pass pass pass pass pass for a non-object value the later assignment simply replaces the earlier one 002-objects-merge pass pass pass pass pass pass pass two objects under the same key merge key-by-key, they do not replace 003-scalar-breaks-the-merge pass pass pass pass fail pass pass merging is always done two values at a time: the scalar replaces the first object (non-object always wins), then the second object replaces the scalar (no merging, object is the new value), so the two objects never see each other 004-merge-scalar-field-later-wins pass pass pass pass pass pass pass for a non-object-valued field present in both merged objects, the field from the second object is used 005-merge-nested-object-recursive pass pass pass pass pass pass pass for an object-valued field present in both objects, the object values are recursively merged by the same rules 006-null-prevents-merge pass pass pass pass pass pass pass the spec's own example: an intermediate null is a non-object, so it replaces the first object and is in turn replaced by the second object with no merging, which prevents the two objects from ever seeing each other 007-merge-keeps-fields-from-either-side pass pass pass pass pass pass pass fields present in only one of the two objects are added to the merged object as they are, whether the first or the second side has them and whether the value is a scalar or an object with nothing to merge against 008-arrays-are-not-objects pass pass pass pass pass pass pass only two object values merge; an array is not an object, so a later array replaces an earlier array (no concatenation) and replaces an earlier object (no merge) 009-later-null-overrides pass pass pass pass pass pass pass null is a value like any other, so a later null overrides an earlier scalar or object: the key stays, set to null, rather than being removed or the assignment skipped 010-null-in-referenced-key-history-does-not-block-merge fail pass fail pass pass pass pass the null at r only stops merging at r; ${r} is the object {x:1}, and a later object merges with the earlier a, so helper stays. typesafe/config carries r's null through the substitution and drops helper (lightbend/config#864)java: diverges 011-nested-null-in-referenced-object-does-not-block-merge fail pass fail pass pass pass pass the same rule one level down: the null in v.nested's history belongs to v.nested, so a.nested still merges with the substituted {x:1} (lightbend/config#864)java: diverges 012-reference-to-merged-field-sees-the-same-value fail pass pass pass pass pass pass a substitution sees the resolved value of the field it names, so o24bbd equals a.nested, which keeps helper. The key name is chosen to sort before a in typesafe/config's root HashMap, the order in which the first version of lightbend/config#864 answered differently for the twojava: diverges 013-top-level-null-before-object-with-substitution fail pass fail pass pass pass pass the null belongs to v's history even when v's object is still unresolved, so a merges and keeps helper (lightbend/config#864)java: diverges 014-own-null-still-blocks-merge-with-referenced-object pass pass pass pass pass pass pass the receiver's own intermediate null still breaks the merge: a is {helper:0}, then null, then the substituted object, so helper is gone 015-earlier-receiver-value-is-evaluated-once-merged fail pass fail pass pass pass pass ${r} is an object, so it merges with the earlier a and the ${missing} inside it is evaluated: an error. typesafe/config 1.4.5 agreed; 1.4.9 drops the earlier a and yields {y:1}, and lightbend/config#864 restores the errorjava: lenient 016-null-deeper-in-a-merge-stack-stays-at-its-key fail pass fail pass fail pass pass v's value is a merge whose top is an object and whose null sits further down, under nested; that null is still v.nested's merge instruction, so a.nested keeps helper (lightbend/config#864 review, round 3)java: diverges 017-lookup-into-nested-null-merges fail pass pass pass pass pass pass two nested nulls each break only their own key's merge, and a lookup of a.n.d sees the final {x:1}; typesafe/config throws BugOrBroken in this key order (reported on lightbend/config#864, same on main)java: unsupported 018-object-then-empty-object pass pass pass pass pass pass pass two objects merge, so the empty later object keeps x; replacement would have given {} 019-duplicate-keys-merge-inside-a-list-element pass pass pass pass pass pass pass an object that is a list element follows the same duplicate-key rules: the later value, here resolved from a substitution, wins 020-objects-and-substitutions-alternate pass pass fail fail pass fail pass four values for a, two of them substitutions that resolve to objects: all four merge in order 021-later-object-replaces-earlier-concatenation pass pass pass pass fail pass pass a string concatenation is a non-object value, so the later object replaces it rather than merging with it 022-scalar-then-empty-object-then-object pass pass pass pass fail pass pass the number cuts the merge off from anything before it; the empty object after it and the object after that still merge, and the empty one contributes nothing 023-empty-object-then-substitution pass pass pass pass pass pass pass an empty object followed by a substitution that resolves to an object merges into that object; the empty layer contributes nothing 024-merge-stack-nested-inside-a-merge-stack pass pass fail pass pass pass pass the object layer of a's merge contains a field b that is itself a merge of a substitution and an object; both levels merge 025-lookup-into-object-that-replaced-a-list fail pass pass pass pass pass pass the object replaces the earlier list, so a.m exists and p is 1 whatever order the keys are resolved in. typesafe/config fails when the reader p is resolved before a (it iterates p before a); the same file with the reader named zzz resolvesjava: unsupported 026-missing-key-in-object-that-replaced-a-list pass pass pass pass pass pass pass the same shape with a key the object does not have: the list below it is not consulted, a.b is undefined and the optional p is not created 027-nested-object-then-scalar-inside-merge pass pass pass pass pass pass pass inside a merge the same rules apply per field: b is an object in the first and a number in the second, so the later value replaces it 028-null-in-second-object-wins pass pass pass pass pass pass pass null is a non-object value, so the second object's null is used for b rather than dropped duration-format9 casesJava 9/9 · hocon2 9/9 · pulse-pyhocon 9/9 · py.hocon 9/9 · pyhocon 2/9 · rs.hocon 9/9 · zig-hocon 9/9
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-bare-number-is-milliseconds pass pass pass pass pass pass pass bare numbers are taken to be in milliseconds already 002-uppercase-unit-is-illegal pass pass pass pass pass pass pass the supported unit strings for duration are case-sensitive and must be lowercase, so an uppercase unit is not one of the supported strings 003-nanoseconds-spellings pass pass pass pass fail pass pass exactly ns, nano, nanos, nanosecond, nanoseconds are the supported unit strings for nanoseconds 004-microseconds-spellings pass pass pass pass fail pass pass exactly us, micro, micros, microsecond, microseconds are the supported unit strings for microseconds 005-milliseconds-spellings pass pass pass pass fail pass pass exactly ms, milli, millis, millisecond, milliseconds are the supported unit strings for milliseconds 006-seconds-spellings pass pass pass pass fail pass pass exactly s, second, seconds are the supported unit strings for seconds 007-minutes-spellings pass pass pass pass fail pass pass exactly m, minute, minutes are the supported unit strings for minutes 008-hours-spellings pass pass pass pass fail pass pass exactly h, hour, hours are the supported unit strings for hours 009-days-spellings pass pass pass pass fail pass pass exactly d, day, days are the supported unit strings for days in the duration format include-file-formats23 casesJava 19/23 · hocon2 3/23 · pulse-pyhocon 3/23 · py.hocon 13/23 · pyhocon 3/23 · rs.hocon 14/23 · zig-hocon 23/23
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-basename-loads-every-extension pass fail fail pass fail pass pass a name without an extension is a basename: all of base.properties, base.json and base.conf exist, so all three are loaded and merged, properties first and HOCON last, so the .conf value wins where they overlap 002-basename-with-only-conf pass fail fail pass fail pass pass a basename with only base.conf present loads just that file 003-basename-with-only-properties pass fail fail pass fail pass pass a basename with only base.properties present loads it as Java properties, whose values are all strings 004-basename-with-no-file-is-ignored pass pass pass pass pass pass pass a basename that exists with no extension at all is a missing include, and a missing include is ignored 005-required-basename-with-no-file-is-error pass pass pass pass pass pass pass required() makes the missing basename an error 006-json-extension-means-json-syntax fail fail fail fail fail fail pass a file named .json is in JSON format, and an unquoted value is not JSON. typesafe/config parses a .json named outright with the HOCON parser, so the file is accepted; the same file found through a basename is rejectedjava: lenient 007-properties-extension-means-properties-syntax fail fail fail pass fail pass pass a file named .properties is in Java properties format, where the first = separates key from value and values are strings. typesafe/config parses a .properties named outright with the HOCON parser and fails on the second =; the same file found through a basename is read as propertiesjava: unsupported 008-properties-keys-are-paths pass fail fail pass fail pass pass in a properties file a dotted key is a path, so a.b and a.c build an object at a; the spec's Java properties mapping says the object must always win when a key is both a value and a parent, whatever the order; values are strings 010-json-file-with-every-value-type pass fail fail pass fail pass pass a .json file found through a basename is parsed as JSON and contributes its object 011-json-rejects-unquoted-values pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; an unquoted value is a HOCON extension, not JSON 012-json-rejects-substitutions pass fail fail pass fail pass pass the .json file is found through a basename, so it is parsed as JSON; ${} is a HOCON extension, not JSON 013-json-rejects-trailing-comma pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; JSON has no trailing commas 014-json-rejects-duplicate-keys pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; the spec's own footing is that duplicate keys are invalid JSON (L194-195), so the second a is an error rather than an override 015-json-array-needs-commas pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; JSON separates array elements with commas only; whitespace does not concatenate or separate 016-json-rejects-empty-document pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; an empty document is not JSON, although it is an empty HOCON object 017-json-rejects-scalar-root pass fail fail pass fail pass pass the .json file is found through a basename, so it is parsed as JSON; a JSON document included here must be an object; a bare number is neither an object nor valid as an included root 019-json-key-must-be-a-string pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; a JSON key is a quoted string; a number cannot be a key 020-json-newline-is-not-a-separator pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; in JSON a newline between fields does not stand in for the comma 022-json-dotted-key-is-one-key pass fail fail pass fail pass pass the .json file is found through a basename, so it is parsed as JSON; a JSON key is a plain string, not a path expression, so a.b stays a single key 023-json-leading-zero-illegal fail fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; JSON forbids a leading zero on a number. typesafe/config reads 01 as 1, the same leniency it shows in HOCONjava: lenient 024-unknown-extension-is-not-a-basename fail pass pass fail pass pass pass x.txt has an extension, so it is not a basename to probe; it is a file adjacent to the including one and of the same type. The spec does not define what counts as an extension, and this row reads any dotted suffix as one. typesafe/config probes every name that does not end in .conf, .json or .properties as a basename and finds nothingjava: diverges 025-json-is-parsed-after-properties pass fail fail pass fail pass pass base.properties and base.json both exist; JSON is parsed next-to-last, after properties, so its value for a wins 026-conf-is-parsed-after-json pass fail fail pass fail pass pass base.json and base.conf both exist; HOCON is parsed last, after JSON, so its value for a wins include-locating5 casesJava 5/5 · hocon2 4/5 · pulse-pyhocon 4/5 · py.hocon 5/5 · pyhocon 4/5 · rs.hocon 5/5 · zig-hocon 5/5
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-include-string-that-is-a-url-is-a-url pass pass pass pass pass pass pass a quoted include name with a known URL scheme is a URL, not a file adjacent to this one. With nothing behind the URL the include is ignored, so a parser that read the string as a filename would also get {}; the row records the rule, it cannot distinguish the two readings 002-nested-include-is-relative-to-the-including-file pass pass pass pass pass pass pass inner.conf includes x.conf, which is found beside inner.conf, not beside main.conf and not in the process's working directory 003-plain-name-is-a-file-beside-the-including-one pass pass pass pass pass pass pass x.conf is not a URL, so it names a file adjacent to main.conf 004-absolute-path-stays-absolute pass pass pass pass pass pass pass an absolute path is loaded as such, so the file of the same name beside main.conf is not it; the absolute file does not exist and the include is ignored 005-file-argument-is-not-relative-to-the-including-file pass fail fail pass fail pass pass a file() argument is not interpreted relative to the including file, so the marker beside sub/inner.conf is not loaded; nothing of that name exists elsewhere and the include is ignored include-merging15 casesJava 14/15 · hocon2 15/15 · pulse-pyhocon 11/15 · py.hocon 12/15 · pyhocon 15/15 · rs.hocon 12/15 · zig-hocon 15/15
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-included-keys-merge-in-place pass pass pass pass pass pass pass the keys from the included object are conceptually substituted for the include statement in the including file, so the included field lands between the fields around it and merges by the usual duplicate-key rules 002-included-conf-array-root-is-error pass pass pass pass pass pass pass an included file must contain an object, not an array; if it contains an array as the root value it is invalid and an error should be generated 003-included-json-array-root-is-error pass pass pass pass pass pass pass both JSON and HOCON allow arrays as root values in a document, but an included file must contain an object, so a JSON file with an array root is an error when included 004-include-inside-nested-object pass pass pass pass pass pass pass the keys from the included root object are conceptually substituted for the include statement in the including file, so an include inside a nested object contributes its keys to that object 005-included-scalar-overrides-earlier-key pass pass pass pass pass pass pass if a key in the included object occurred prior to the include statement in the including object, the included key's value overrides the earlier value, exactly as with duplicate keys found in a single file 006-included-object-merges-with-earlier-object pass pass pass pass pass pass pass if a key in the included object occurred prior to the include statement and both values are objects, the included value merges with the earlier value, exactly as with duplicate keys found in a single file 007-included-scalar-replaces-earlier-object pass pass pass pass pass pass pass an included key's value overrides the earlier value exactly as with duplicate keys in a single file, so an included non-object replaces an earlier object rather than merging with it 008-later-key-overrides-included-key pass pass pass pass pass pass pass if the including file repeats a key from an earlier-included object, the including file's value overrides the one from the included file 009-later-object-merges-with-included-object pass pass pass pass pass pass pass if the including file repeats a key from an earlier-included object and both values are objects, the including file's value merges with the one from the included file 010-include-inside-an-object-inside-a-list pass pass pass pass pass pass pass an include statement may stand wherever a field may, including in an object that is a list element 011-include-inside-a-list-with-a-substitution fail pass pass pass pass pass pass the included file may contain substitutions like any other; typesafe/config refuses an include nested in a list when the included file is not fully resolvedjava: unsupported 012-included-merge-stack-over-earlier-object pass pass fail fail pass fail pass the included file leaves a as an object merged with a substitution; that whole stack merges with the object the including file set earlier 013-included-merge-stack-over-earlier-substitution pass pass fail fail pass fail pass the earlier value is itself a substitution; once everything resolves to objects, all three layers merge 014-included-merge-stack-over-earlier-scalar pass pass fail pass pass pass pass the earlier value is a number, so the included object stack replaces it and merges only within itself 015-included-object-on-top-of-substitution-over-earlier-substitution pass pass fail fail pass fail pass the same as 013 with the included stack the other way up, an object over a substitution; the result does not depend on which layer is on top include-missing-and-required6 casesJava 6/6 · hocon2 6/6 · pulse-pyhocon 6/6 · py.hocon 6/6 · pyhocon 6/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-missing-include-ignored pass pass pass pass pass pass pass by default, if an included file does not exist the include statement is silently ignored, so the keys around it parse as if the line were not there 002-missing-include-is-empty-object pass pass pass pass pass pass pass a missing include is treated as if the included file contained only an empty object, so an object whose only content is that include stays an empty object 003-missing-file-include-ignored pass pass pass pass pass pass pass the silent-ignore default applies to an explicit file() include as well: a file that does not exist contributes nothing and parsing continues 004-required-missing-is-error pass pass pass pass pass pass pass when the included resource name is wrapped with required(), file parsing fails with an error if the resource cannot be resolved 005-required-file-missing-is-error pass pass pass pass pass pass pass required() may also wrap the file() form, and a required file that cannot be resolved is a parse error rather than an empty object 006-required-present-is-included pass pass pass pass pass pass pass required() only makes the include mandatory; a resource that does resolve is included exactly as an unwrapped include would be include-substitution19 casesJava 16/19 · hocon2 15/19 · pulse-pyhocon 11/19 · py.hocon 14/19 · pyhocon 12/19 · rs.hocon 14/19 · zig-hocon 19/19
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-substitution-relativized-to-include-scope pass pass fail pass fail pass pass a substitution in an included file must be fixed up to be relative to the app's configuration root, so ${x} in a file included at key a becomes ${a.x} 002-relativized-path-sees-later-redefinition pass pass fail pass fail pass pass substitution happens after parsing the whole configuration, so the ${x} fixed up to ${a.x} evaluates to the root's later redefinition 42 rather than the included file's 10 003-original-path-tried-as-fallback pass pass pass pass pass pass pass it is not enough to only look up the fixed-up path; when a.x is undefined the original path x is looked up relative to the root of the including configuration 004-relativized-path-tried-before-original pass fail fail pass fail pass pass substitutions in included files are looked up first relative to the root of the included file and only second relative to the root of the including configuration, so a.x wins over x 005-neither-path-defined-is-error pass pass pass pass pass pass pass only the fixed-up path a.x and the original path x are looked up; with neither defined the substitution is undefined and the configuration is invalid 006-optional-substitution-with-neither-path-is-omitted pass pass pass pass pass pass pass an optional substitution in an included file is looked up at the fixed-up path a.x and at the original path x; with neither defined the field is omitted rather than an error 007-nested-include-relativized-through-both-levels pass pass fail pass fail pass pass the fix-up is relative to the app's configuration root, so ${x} in a file included at b inside a file included at a becomes ${a.b.x} 008-original-path-falls-back-to-environment pass fail pass pass pass pass pass an included file may intend to refer to the application's root config, for example to get a value from the environment; the original path CONFORMANCE_HOME is looked up and falls back to the environment variable 009-undefined-optional-at-fixed-up-path-falls-back fail pass pass fail pass fail pass an undefined ${?nope} means common.x.y is never created, so ${x.y} is not found at the fixed-up path and is looked up at the original path x.y. typesafe/config treats the vanished value as a miss that ends the lookup -- except in some root key orders (see 014); lightbend/config#863 falls backjava: unsupported 010-undefined-optional-at-fixed-up-path-with-no-original pass pass pass fail pass fail pass with neither common.x.y nor x.y defined, the required ${x.y} in the included file is unresolved 011-optional-reference-falls-back-too fail pass pass fail pass fail pass the optional form of 009 falls back the same way; typesafe/config leaves a out instead (lightbend/config#863)java: diverges 012-earlier-value-at-fixed-up-path-wins pass fail fail pass fail pass pass an undefined optional that would override keeps the previous value, so common.x.y is 9 and found at the fixed-up path; the original path is not consulted 013-missing-leaf-under-existing-object-falls-back pass pass pass pass pass pass pass the metamorphic twin of 009 without the optional: common.x exists, common.x.y does not, so x.y is looked up at the original path 014-unrelated-key-does-not-change-the-result fail pass pass fail pass fail pass an unrelated key that reads common.x must not change what common.a resolves to; in typesafe/config this input fails while the same input with the key named o24bbd resolves to 0 (lightbend/config#863)java: unsupported 015-undefined-at-both-paths-is-error pass pass fail fail pass fail pass when the original path is itself an undefined optional, neither lookup finds a value and the required ${x.y} is an error 016-substitution-inside-a-concatenation-is-fixed-up pass pass pass pass pass pass pass a substitution that is one piece of a concatenation in the included file is fixed up like a bare one: p.x is not set, so the original path x is used 017-substitution-inside-a-list-is-fixed-up pass pass pass pass pass pass pass a substitution that is a list element in the included file is fixed up as well 018-substitution-in-a-merge-stack-is-fixed-up pass pass fail pass fail pass pass an object merged with a substitution in the included file keeps working after the fix-up, whichever of the two is on top: q is found at p.q 019-null-at-fixed-up-path-is-found pass fail fail pass fail pass pass a.x is present, set to null, so the lookup at the fixed-up path succeeds and the original path x is not consulted include-syntax15 casesJava 14/15 · hocon2 12/15 · pulse-pyhocon 11/15 · py.hocon 12/15 · pyhocon 12/15 · rs.hocon 13/15 · zig-hocon 14/15
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-include-later-in-key-is-literal pass pass pass pass pass pass pass unquoted include has no special meaning if it is not the start of a key's path expression; it may appear later in the key, so { foo include : 42 } is equivalent to { "foo include" : 42 } 002-include-as-object-value-is-string pass pass pass pass pass pass pass unquoted include has no special meaning if it is not the start of a key's path expression; as an object value it is the string "include" 003-include-as-array-element-is-string pass pass pass pass pass pass pass unquoted include has no special meaning if it is not the start of a key's path expression; as an array element it is the string "include". the spec's example is the root array [ include ]; it is wrapped in a field here so the row does not depend on array-root support 004-no-concatenation-on-include-argument pass pass fail pass pass pass pass value concatenation is NOT performed on the argument to include; the argument must be a single quoted string, so a second quoted string after the resource name is an error whether or not the first resource exists 005-no-substitution-in-include-argument pass pass pass pass pass pass pass no substitutions are allowed in the argument to include, which must be a single quoted string; ${x} after include is an error even though x is defined, so an implementation that resolves the argument first still fails this row 006-include-argument-must-be-quoted pass pass pass pass pass pass pass if an unquoted include at the start of a key is followed by anything other than a single quoted string or the url()/file()/classpath() syntax, it is invalid and an error should be generated; the argument may not be an unquoted string 007-quoted-include-is-ordinary-key pass pass pass pass pass pass pass only unquoted include is special; quoting it gives a key that starts with the word include, so { "include" : 42 } is an ordinary field named include 008-file-argument-must-be-quoted pass pass pass pass pass pass pass the argument inside file() must be a single quoted string; an unquoted name is a syntax error 009-file-missing-close-parenthesis pass pass pass fail pass pass pass file("...") must be closed; the line ends before the parenthesis 010-required-missing-close-parenthesis pass pass pass fail pass fail pass required(...) must be closed too 011-url-argument-must-be-a-url fail fail fail fail fail fail fail the string inside url() is interpreted as a URL, and x.conf has no scheme, so it is not one 013-newlines-between-include-and-name pass pass fail pass fail pass pass any amount of whitespace, newlines included, may stand between include and the resource name 014-whitespace-inside-parentheses pass fail pass pass pass pass pass whitespace inside the parentheses, outside the quotes, is allowed 015-include-followed-by-separator-is-not-a-key pass pass fail pass fail pass pass an unquoted include at the start of a key is never a key; followed by a separator instead of a quoted name it is an include with an invalid argument 016-comma-separates-include-from-next-field pass fail pass pass pass pass pass an include stands where a field would, so the usual comma separates it from the field that follows key-value-separator6 casesJava 6/6 · hocon2 6/6 · pulse-pyhocon 6/6 · py.hocon 6/6 · pyhocon 6/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-equals-as-separator pass pass pass pass pass pass pass the = character can be used anywhere JSON allows :, to separate keys from values 002-omitted-separator-before-object pass pass pass pass pass pass pass if a key is followed by {, the : or = may be omitted; the spec's own example is "foo" {} meaning "foo" : {} 003-omitted-separator-unquoted-key pass pass pass pass pass pass pass the separator-omission rule before { applies to unquoted keys too, not just the quoted example in the spec 004-value-on-the-line-after-the-separator pass pass pass pass pass pass pass = stands wherever JSON allows :, and JSON allows any whitespace, newlines included, between the separator and the value 005-separator-cannot-be-omitted-before-array pass pass pass pass pass pass pass the separator may be omitted only before {; a key followed directly by [ is invalid 006-newline-between-key-and-brace pass pass pass pass pass pass pass the separator may be omitted before {, and a newline before the { is whitespace like any other between a key and its value, as JSON allows one before : list-values-from-environment-variables6 casesJava 6/6 · hocon2 1/6 · pulse-pyhocon 3/6 · py.hocon 6/6 · pyhocon 3/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-list-from-indexed-variables pass fail fail pass fail pass pass the [] suffix collects CONFORMANCE_LIST_0, CONFORMANCE_LIST_1, ... until an index is missing, into a list of strings 002-optional-list-with-no-elements-is-undefined pass fail pass pass pass pass pass with no CONFORMANCE_LIST_0 set, an optional ${?X[]} is undefined and the field is not created 003-required-list-with-no-elements-is-error pass pass pass pass pass pass pass with no CONFORMANCE_LIST_0 set, a required ${X[]} is an error 004-collection-stops-at-the-first-gap pass fail fail pass fail pass pass indices are collected from 0 until one is missing, so with _0 and _2 set the list holds only the first 005-elements-are-strings pass fail fail pass fail pass pass an element taken from the environment is a string, so 1 stays "1" 006-config-keys-are-not-collected pass fail pass pass pass pass pass the [] suffix collects environment variables only, so the config key a_0 is not gathered and the optional substitution is undefined multi-line-strings9 casesJava 9/9 · hocon2 9/9 · pulse-pyhocon 6/9 · py.hocon 9/9 · pyhocon 9/9 · rs.hocon 9/9 · zig-hocon 9/9
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-basic-triple-quote pass pass pass pass pass pass pass the three-character sequence """ starts a multi-line string whose unicode characters are used unmodified to create the string value 002-newlines-and-whitespace-preserved pass pass pass pass pass pass pass newlines and whitespace inside a multi-line string receive no special treatment and are kept in the value 003-unicode-escape-not-interpreted pass pass pass pass pass pass pass unlike JSON quoted strings, a triple-quoted string does not interpret escapes, so the six characters \u0041 stay as written rather than becoming A 004-extra-quote-becomes-part-of-string pass pass pass pass pass pass pass any sequence of at least three quotes ends the multi-line string and any extra quotes are part of the string, so HOCON works like Scala's four-character string foo" rather than Python's syntax error 005-five-closing-quotes-two-extra pass pass fail pass pass pass pass a closing run of five quotes still only needs three to end the string, so both extra quotes are appended to the string value 006-unterminated-is-illegal pass pass pass pass pass pass pass a multi-line string opened with a three-character quote sequence that never reaches a closing """ is not a legal value 007-fewer-than-three-quotes-does-not-close pass pass fail pass pass pass pass only a run of at least three quote characters ends the multi-line string, so the single embedded quote pairs around hi do not terminate it early 008-backslash-kept-literally pass pass fail pass pass pass pass a backslash sequence inside a triple-quoted string is used unmodified, not as an escape 009-six-quotes-are-an-empty-string pass pass pass pass pass pass pass the three opening quotes are followed at once by the closing run, so the string is empty numerically-indexed-objects-to-arrays7 casesJava 7/7 · hocon2 3/7 · pulse-pyhocon 3/7 · py.hocon 7/7 · pyhocon 3/7 · rs.hocon 7/7 · zig-hocon 7/7
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-object-with-numeric-keys-stays-object pass pass pass pass pass pass pass the conversion should be done lazily when required to avoid a type error, not eagerly anytime an object has numeric keys, so a bare object with numeric keys stays an object 003-concatenation-converts-numeric-object-to-array pass fail fail pass fail pass pass the conversion should be done in a concatenation when a list is expected and an object with numeric keys is found, so the trailing object concatenates onto the array as elements x, y 004-concatenation-ignores-non-integer-keys pass fail fail pass fail pass pass the conversion should ignore any keys which do not parse as positive integers, so key "foo" is dropped and only "0" contributes to the resulting array 005-empty-object-concatenation-not-converted pass pass pass pass pass pass pass the conversion should not occur if the object is empty, so an empty object cannot satisfy a list-expected concatenation and this is illegal 006-object-without-integer-keys-not-converted pass pass pass pass pass pass pass the conversion should not occur if the object has no keys which parse as positive integers, so this object cannot satisfy a list-expected concatenation and this is illegal 007-sparse-integer-keys-sorted-and-reindexed pass fail fail pass fail pass pass the conversion should sort by the integer value of each key and then build the array; missing indices such as "1" are eliminated rather than left as gaps, so keys "0" and "2" become a two-element array 008-negative-key-ignored-in-conversion pass fail fail pass fail pass pass the conversion ignores any keys which do not parse as positive integers, and "-1" is not a positive integer, so it is dropped and only key "0" contributes omit-root-braces14 casesJava 10/14 · hocon2 12/14 · pulse-pyhocon 14/14 · py.hocon 8/14 · pyhocon 14/14 · rs.hocon 8/14 · zig-hocon 14/14
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-implicit-root-object pass pass pass pass pass pass pass if the file does not begin with [ or {, it is parsed as if enclosed with {} curly braces 002-explicit-root-object-unaffected pass pass pass pass pass pass pass the implicit-wrap rule only applies when the file does not begin with [ or {; a file already beginning with { is parsed as ordinary JSON-style object 003-missing-open-brace-with-close-brace-illegal pass pass pass fail pass fail pass a HOCON file is invalid if it omits the opening { but still has a closing }, since the curly braces must be balanced 004-empty-file pass fail pass pass pass pass pass an empty file does not begin with a square bracket or a curly brace, so it is parsed as if enclosed in {}: the empty object 005-bare-string-root-illegal pass pass pass pass pass pass pass the file does not begin with a bracket or brace, so it is wrapped in {} and becomes a key with no separator and no value 006-array-root-unaffected fail pass pass fail pass fail pass the implicit {} wrap fires only when the file does not begin with a square bracket or curly brace, so a file starting with [ keeps its array root; the include section says so outright -- 'both JSON and HOCON allow arrays as root values in a document' (the reason an included file may not be one). typesafe/config has no array-rooted config at all and rejects the document as LIST rather than object.java: unsupported 008-empty-array-root fail pass pass fail pass fail pass an empty array is still an array root, and the wrap rule looks at the opening bracket, not at whether the document has contentjava: unsupported 009-comment-before-array-root fail pass pass fail pass fail pass the {} wrap fires only for a file that does not begin with a bracket or brace, and a leading comment does not make it one -- both typesafe/config and pyhocon read the first token, not the first byte. the spec's wording is loose here; this row records the consensus readingjava: unsupported 010-blank-line-before-array-root fail pass pass fail pass fail pass leading whitespace does not trigger the {} wrap either, for the same reason as a leading commentjava: unsupported 011-trailing-array-after-root-array pass pass pass pass pass pass pass two arrays side by side concatenate when only non-newline whitespace separates them, which would make this document [1,2,3]; but a document is one array or one object at the root, and nothing in the spec lets a root value be a concatenation of two. typesafe/config, pyhocon and hocon-rs all reject it 012-close-brace-after-value-on-one-line-illegal pass pass pass fail pass fail pass a close brace with no open brace is invalid wherever it stands, on the same line as a value as much as on its own 013-open-brace-never-closed-illegal pass pass pass pass pass pass pass an opening brace with no closing brace is unbalanced 014-comment-only-file pass fail pass pass pass pass pass a file holding only a comment does not begin with a bracket or brace, so it is the empty object 015-bare-number-root-illegal pass pass pass pass pass pass pass the file does not begin with { or [, so it is read as if wrapped in braces, and a bare number there is a key with no value path-expressions20 casesJava 20/20 · hocon2 19/20 · pulse-pyhocon 16/20 · py.hocon 19/20 · pyhocon 13/20 · rs.hocon 19/20 · zig-hocon 20/20
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-quoted-dot-has-no-meaning pass pass pass pass pass pass pass a dot inside a quoted string has no special meaning as a path separator, so a quoted "a.b" key is a single-element path 002-mixed-quoted-and-unquoted-path pass pass fail pass fail pass pass foo.bar."hello.world" is a path with three elements: foo, bar, and hello.world, since unquoted dots are path separators but a quoted dot is not 003-number-then-unquoted-string pass pass pass pass pass pass pass 10.0foo is a number then unquoted string foo, giving the two-element path 10 and 0foo, because a dot inside a number still counts as a path separator 004-unquoted-string-with-dot pass pass pass pass pass pass pass foo10.0 is an unquoted string with a dot in it, giving the two-element path foo10 and 0 005-unquoted-concatenated-with-quoted-number pass pass fail fail fail fail pass foo followed by quoted "10.0" is an unquoted then a quoted string which concatenate, giving a single-element path 006-all-numeric-path pass pass pass pass pass pass pass dots in numbers count as path separators, so 1.2.3 is the three-element path with elements 1, 2, 3 008-value-concatenation-keeps-boolean pass pass pass pass pass pass pass unlike a path expression, an array or element value consisting of the single value true is a value concatenation and retains its character as a boolean 009-empty-path-element-quoted-is-valid pass pass fail pass fail pass pass a path element that is an empty string must be quoted; a."".b is a valid three-element path whose middle element is the empty string 010-consecutive-dots-invalid pass pass pass pass fail pass pass an unquoted empty path element is invalid, so a..b must generate an error 011-path-starting-with-dot-invalid pass pass pass pass fail pass pass a path that starts with a dot is invalid and should generate an error 012-path-ending-with-dot-invalid pass fail pass pass fail pass pass a path that ends with a dot is invalid and should generate an error 015-dotted-path-in-substitution pass pass pass pass pass pass pass path expressions appear in substitutions like ${foo.bar}, where the unquoted dot separates foo and bar into two path elements, so the lookup walks key foo then key bar 016-number-keeps-original-text-in-path pass pass pass pass pass pass pass a number in a path expression must keep its original string representation as it appeared in the file, so 1.10 is the two-element path 1 and 10, not the 1 and 1 a generic number-to-string conversion would give 017-whitespace-concatenation-in-path pass pass pass pass pass pass pass path expressions are syntactically identical to a value concatenation, so the whitespace-separated a b concatenates into one element and the unquoted dot then splits off c, giving the two-element path a b and c 018-quoted-element-in-substitution-path pass pass pass pass pass pass pass path expressions appear in substitutions as well as keys, and inside quoted strings a dot has no special meaning there either, so ${foo."bar.baz"} looks up key foo then the single key bar.baz 019-empty-element-in-substitution-path-invalid pass pass fail pass fail pass pass an unquoted empty path element is invalid in a substitution just as in a key, so ${a..b} must generate an error rather than collapsing to ${a.b} (a.b is defined so a lenient implementation would produce a value and fail) 020-negative-leading-dot-key-is-a-path pass pass pass pass pass pass pass a dot in a key separates path elements whether or not the text around it would be a number, so -.33 names - then 33; numbers in paths keep their original text 021-quoted-dot-in-substitution-path-is-literal pass pass pass pass pass pass pass inside quotes a dot is not a separator, so ${"b.c"} names one field called b.c, which does not exist; it does not reach b's field c 022-keyword-in-substitution-path-is-a-string pass pass pass pass pass pass pass a path expression is always a string, so true in a substitution names the key true rather than a boolean 023-substitution-in-dotted-key-illegal pass pass pass pass pass pass pass a key is a path expression, and a path expression may not contain a substitution, not even as a later element of a dotted key paths-as-keys11 casesJava 10/11 · hocon2 10/11 · pulse-pyhocon 9/11 · py.hocon 11/11 · pyhocon 9/11 · rs.hocon 11/11 · zig-hocon 11/11
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-two-element-path-expands pass pass pass pass pass pass pass a key that is a multi-element path expands to a nested object per element, the last element combined with the value becomes a field in the most-nested object: foo.bar:42 is equivalent to foo{bar:42} 002-three-element-path-expands pass pass pass pass pass pass pass a three-element path key expands through two levels of nesting: foo.bar.baz:42 is equivalent to foo{bar{baz:42}} 003-merge-sibling-paths pass pass pass pass pass pass pass the objects created by expanding path keys are merged in the usual way, so a.x:42, a.y:43 is equivalent to a{x:42,y:43} 004-whitespace-in-key pass pass pass pass pass pass pass because path expressions work like value concatenations, whitespace is allowed in keys: a b c:42 is equivalent to "a b c":42 005-unquoted-true-key-becomes-string pass pass pass pass pass pass pass path expressions are always converted to strings, so the unquoted boolean-looking key true:42 is "true":42 006-unquoted-number-key-becomes-string pass pass pass pass pass pass pass path expressions are always converted to strings, so the unquoted numeric key 3:42 is "3":42 007-decimal-key-splits-on-dot pass pass pass pass pass pass pass a dot in an unquoted key is a path separator even when it looks like a decimal number, so 3.14:42 is "3":{"14":42} 008-include-cannot-begin-key fail pass fail pass fail pass pass the unquoted string include may not begin a path expression in a key: where an object key is expected, include is not read as a key at all and must be followed by a quoted resource name, so include.foo is an error. typesafe/config recognises the keyword only as a bare include token -- include.foo tokenizes as one unquoted string, so it is accepted as the path include.foo, while include = 42 is rejected under the very rule the spec states.java: lenient 009-include-not-at-start-is-ordinary pass fail fail pass fail pass pass only the unquoted string include that begins a path expression in a key is special: include later in a key (foo.include) and a quoted "include" beginning one are ordinary path elements 010-path-key-merges-with-brace-object pass pass pass pass pass pass pass the object created by expanding a path key is merged in the usual way with an object written in braces at the same key, whichever form comes first: a{x:1}, a.y:2 and b.x:1, b{y:2} are both equivalent to {x:1,y:2} 011-whitespace-around-a-dot-stays-in-the-elements pass pass pass pass pass pass pass whitespace inside a key is part of the key, and a dot splits the key into elements, so the elements here are 'a ' and ' b' with their spaces period-format7 casesJava 7/7 · hocon2 7/7 · pulse-pyhocon 7/7 · py.hocon 7/7 · pyhocon 3/7 · rs.hocon 7/7 · zig-hocon 7/7
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-bare-number-is-days pass pass pass pass pass pass pass for getPeriod(), bare numbers are taken to be in days, unlike getDuration() where bare numbers are milliseconds 002-uppercase-unit-is-illegal pass pass pass pass pass pass pass the supported unit strings for period are case-sensitive and must be lowercase, so an uppercase unit is not one of the supported strings 003-days-spellings pass pass pass pass fail pass pass exactly d, day, days are the supported unit strings for days in the period format 004-weeks-spellings pass pass pass pass fail pass pass exactly w, week, weeks are the supported unit strings for weeks 005-months-spellings pass pass pass pass fail pass pass exactly m, mo, month, months are the supported unit strings for months 006-years-spellings pass pass pass pass pass pass pass exactly y, year, years are the supported unit strings for years 007-month-m-ambiguous-with-duration-minutes pass pass pass pass fail pass pass the spec notes that getTemporal() callers should prefer mo over m for months, since m is also the duration unit for minutes and getTemporal() may return either a Duration or a Period plus-equals-field-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
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-plus-equals-desugars-to-optional-self-ref-array pass pass fail pass fail pass pass a += b transforms into a = ${?a} [b], and because the fallback is optional (${?a} not ${a}) this is legal even as the first mention of a in the file 002-plus-equals-appends-to-existing-array pass pass fail pass fail pass pass += appends an element to a previous array 003-plus-equals-errors-on-non-array-previous-value pass pass fail pass fail pass pass if the previous value was not an array, += results in an error just as the long form a = ${?a} [b] would with a non-array a 004-plus-equals-twice pass pass fail pass fail pass pass each += appends to the value the previous line left, so two of them append two elements 005-plus-equals-in-nested-object-with-substitution pass fail fail pass fail pass pass inside an object, += desugars to a self-reference by the field's full path o.a; the appended element may itself be a substitution 006-plus-equals-inside-a-list-element fail pass fail pass fail pass pass b has no previous value, so b += 1 is b = ${?b} [1] with the optional part undefined: [1]. typesafe/config refuses += inside a list because its desugared path cannot name a list elementjava: unsupported 007-plus-equals-under-a-dotted-key pass fail fail pass fail pass pass the self-reference += desugars to uses the full path of the field, o.p.a, even when the enclosing key was written as the path o.p 008-plus-equals-with-an-object-containing-a-merge fail pass fail pass fail pass pass l += v is l = ${?l} [v]; the appended object holds a merge of an object and a substitution, which resolves inside the new list. typesafe/config fails with an internal error (BugOrBroken) for the same reason as array-and-object-concatenation/018java: unsupported 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
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-isolated-self-reference-is-an-error pass pass pass pass pass pass pass in isolation, with no merges involved, a self-referential field is an error because the substitution cannot be resolved 002-self-reference-resolves-to-earlier-merged-value pass pass pass pass pass pass pass when foo:${foo} is merged with an earlier value for foo, the self-reference resolves to that overridden value, so foo ends up { a : 1 } 003-self-reference-before-any-value-is-undefined pass pass fail fail pass fail pass if the self-referential foo:${foo} comes before foo has any value, the reference is undefined, exactly as if it named a path not found in the document, so it is an error even though foo is given a value afterward 004-optional-self-reference-disappears-silently pass pass pass pass pass pass pass because the self-reference error is treated as undefined rather than an intractable cycle, the optional syntax foo:${?foo} makes the field disappear silently instead of erroring 005-substitution-hidden-by-later-non-mergeable-value-is-never-evaluated pass pass pass pass pass pass pass if a substitution is hidden by a later non-object value that could not be merged with it, it is never evaluated and no error is reported, no matter what it would have resolved to; foo ends up 42 006-self-reference-cycle-ignored-when-overridden-by-literal pass pass pass pass pass pass pass the same hiding rule applies to a self-reference cycle: once overridden by a non-mergeable literal, the initial foo:${foo} must simply be ignored, so foo ends up 42 with no error 007-self-reference-in-path-expression-resolves-to-value-below pass pass pass pass pass pass pass a self-reference resolves to the value below it even as part of a path expression, so ${foo.a} refers to { c : 1 } rather than 2, and the final merge is { a : 2, c : 1 } 008-object-may-refer-to-sibling-path-within-itself pass pass pass pass pass pass pass an implementation must allow an object to refer to a path within itself without treating it as a cycle, by resolving only the referenced field rather than recursing the whole enclosing object; bar.baz ends up 42 009-non-cycling-reference-looks-forward-across-merge pass pass pass pass pass pass pass because there is no inherent cycle here, the substitution must look forward including the field's own later merges, so bar.baz ends up 43 once foo is overridden to 43 010-mutually-referring-objects-are-not-self-referential pass pass pass pass pass pass pass mutually-referring objects should work and are not self-referential, so they look forward: bar.a ends up 4 and foo.c ends up 3 011-optional-self-reference-in-concatenation-looks-back pass pass pass pass pass pass pass an optional self-reference in a value concatenation has to look back to an undefined a, so a ends up "foo" rather than "foofoo" 012-mutual-non-self-cycle-is-unresolvable pass pass pass pass pass pass pass the spec gives bar:${foo}, foo:${bar} as an example that is not possible to resolve, since neither lazy evaluation nor looking backward breaks the cycle and neither field is optional 013-multi-step-cycle-is-invalid pass pass pass pass pass pass pass a multi-step loop across three fields (a:${b}, b:${c}, c:${a}) must also be detected as invalid, not just a direct two-field cycle 014-order-dependent-resolution-must-agree-on-one-value pass pass pass pass pass pass pass the order a and b resolve in is undefined: both 1, both 2 or an error are all allowed, but a and b must agree (a=1,b=2 is not permitted) 015-list-hidden-by-substituted-object-is-never-evaluated fail pass pass fail pass fail pass a list merges with nothing, so any later value hides it and it is never evaluated; that the hiding object arrives through ${replacement} changes nothing, since the substitution is required and so either resolves or fails on its own. typesafe/config evaluates the hidden list (lightbend/config#865)java: unsupported 016-object-hidden-by-substituted-scalar-is-never-evaluated pass pass pass pass fail pass pass ${replacement} resolves to 42, a non-object, so the earlier object is hidden and never evaluated, as with the literal in 005 (typesafe/config 1.4.5 evaluated it) 017-object-hidden-by-substituted-concatenation-is-never-evaluated pass pass pass pass fail pass pass a string concatenation is a non-object too, so it hides the earlier object (typesafe/config 1.4.5 evaluated it) 018-object-merged-with-substituted-object-is-evaluated pass pass fail pass pass pass pass a substituted object merges with the earlier object, so its fields are part of the result and ${does-not-exist} is an error 019-list-hidden-by-literal-scalar-is-never-evaluated pass pass pass pass pass pass pass the literal counterpart of 015: a literal number could not be merged with the list, so the list is never evaluated and there is no error 020-object-hidden-by-literal-list-is-never-evaluated pass pass pass pass pass pass pass a literal list is a non-object value, so it hides the earlier object and ${does-not-exist} is never evaluated 021-hidden-list-does-not-depend-on-key-order fail pass pass fail pass fail pass a reader of w.x must not change whether the hidden list is evaluated; only objects merge, so the object hides the list whatever else refers to it. typesafe/config fails here, yet resolves the same file with the reader named o24bbd (lightbend/config#865)java: unsupported 022-undefined-optional-does-not-hide pass pass fail pass pass pass pass an undefined optional that would override leaves the previous value in place, so the list is the result and ${does-not-exist} is evaluated: an error 023-append-does-not-hide pass pass fail pass fail pass pass w += 1 is w = ${?w} [1], which reads the earlier list, so ${does-not-exist} is evaluated: an error 024-substitution-target-is-fetched-lazily pass pass pass pass pass pass pass resolving ${b.c} fetches only b.c; resolving all of b first would reach ${a} and report a cycle that is not there 025-optional-two-field-cycle-is-undefined pass pass fail pass fail pass pass a genuine cycle is an error for required substitutions; with the optional syntax it is a missing value, so neither field is created 026-memoized-by-instance-not-by-path pass pass pass pass pass pass pass the text ${a} appears twice and resolves differently: to x in a's own self-reference and to the final xy in b; memoizing by path instead of by instance would give b the wrong value self-referential-substitutions5 casesJava 5/5 · hocon2 5/5 · pulse-pyhocon 5/5 · py.hocon 5/5 · pyhocon 5/5 · rs.hocon 5/5 · zig-hocon 5/5
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-path-builds-on-older-value pass pass pass pass pass pass pass a field may look up its own older value before being overridden with a new value based on it, letting path:${path}":d" extend the earlier path:"a:b:c" 002-self-referential-concatenation pass pass pass pass pass pass pass a value concatenation containing a substitution to its own field is self-referential, so ${a} looks back to the earlier x and the result is xbc 003-object-containing-self-reference-is-not-self-referential pass pass pass pass pass pass pass an object with a substitution inside it is not considered self-referential for this purpose, so a:{b:${a}} is an unbreakable cycle that must generate an error 004-array-containing-self-reference-is-not-self-referential pass pass pass pass pass pass pass an array with a substitution inside it is not considered self-referential for this purpose, so a:[${a}] is an unbreakable cycle that must generate an error 006-self-referential-array-concatenation pass pass pass pass pass pass pass the spec lists path:${path} [ /usr/bin ] among the examples of self-referential fields: a value concatenation of a substitution with an array literal is still self-referential size-in-bytes-format5 casesJava 5/5 · hocon2 5/5 · pulse-pyhocon 5/5 · py.hocon 5/5 · pyhocon 4/5 · rs.hocon 5/5 · zig-hocon 5/5
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-bare-number-is-bytes pass pass pass pass pass pass pass bare numbers are taken to be in bytes already 002-single-byte-spellings pass pass pass pass pass pass pass for single bytes, exactly B, b, byte, bytes are supported 003-powers-of-ten-suffixes pass pass pass pass pass pass pass every power-of-10 suffix and its long forms name the same unit family; one table, one rule 004-powers-of-two-suffixes pass pass pass pass fail pass pass every power-of-2 suffix and its long forms name the same unit family; one table, one rule. The E and e forms are quoted because an unquoted 5E is a number token that is not a number (see unquoted-strings/022) 019-single-letter-abbreviation-means-powers-of-two pass pass pass pass pass pass pass the single-letter abbreviations (like K) are ambiguous between powers of two and ten, and this spec follows the java -Xmx / GNU-tools precedent of mapping them to powers of two string-value-concatenation18 casesJava 13/18 · hocon2 18/18 · pulse-pyhocon 15/18 · py.hocon 18/18 · pyhocon 15/18 · rs.hocon 18/18 · zig-hocon 18/18
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-substitution-in-string-concatenation pass pass pass pass pass pass pass string value concatenation supports substitutions inside the concatenated string 002-only-simple-values-participate pass pass pass pass pass pass pass it is invalid for arrays or objects to appear in a string value concatenation, since only simple values participate 003-leading-and-trailing-whitespace-trimmed pass pass pass pass pass pass pass whitespace before the first and after the last simple value is discarded; only the whitespace between the values is kept 004-concatenation-never-spans-newline pass pass pass pass pass pass pass string value concatenations never span a newline, so a bare word on the following line is not appended to the value above it 005-array-element-string-concatenation pass pass pass pass pass pass pass a string value concatenation may appear in any place a string may appear, including array elements 006-boolean-becomes-string-in-concatenation pass pass fail pass fail pass pass true and false become the strings "true" and "false" when they participate in a concatenation 007-null-becomes-string-in-concatenation pass pass fail pass fail pass pass null becomes the string "null" when it participates in a concatenation 008-number-kept-as-written-in-concatenation pass pass pass pass pass pass pass numbers should be kept as they were originally written in the file when they participate in a concatenation, not renormalized 009-single-boolean-not-converted-to-string pass pass pass pass pass pass pass a single value is never converted to a string; true by itself must be parsed as a boolean-typed value, not a string 010-object-then-scalar-drops-the-scalar fail pass pass pass pass pass pass it is invalid for arrays or objects to appear in a string value concatenation; typesafe/config accepts this one silently and discards the trailing x, losing data with no diagnostic, while erroring on the same pair in the other orderjava: lenient 011-array-then-scalar-drops-the-scalar fail pass pass pass pass pass pass the same rule for arrays; typesafe/config again accepts and discards the trailing x, where it errors when the scalar comes firstjava: lenient 012-scalar-then-object-is-an-error pass pass pass pass pass pass pass the same rule with the scalar first, which typesafe/config does reject -- the half of the rule it enforces 014-array-then-unquoted-text-without-space fail pass pass pass pass pass pass an array may not appear in a string value concatenation, with or without whitespace between them; typesafe/config silently drops suffix (lightbend/config#862)java: lenient 015-object-then-unquoted-text-without-space fail pass pass pass pass pass pass the same rule for an object followed directly by text; typesafe/config silently drops suffix (lightbend/config#862)java: lenient 016-text-between-arrays-is-not-dropped fail pass pass pass pass pass pass text between two arrays makes a concatenation of array, string and array, which is invalid; typesafe/config drops the text and concatenates the arrays (lightbend/config#862)java: lenient 020-64-bit-number-kept-as-written pass pass pass pass pass pass pass a number in a concatenation is stringified as it appeared in the file, whatever its size 021-false-becomes-string-in-concatenation pass pass fail pass fail pass pass false becomes the string "false" in a concatenation, as true does 022-quoted-and-unquoted-strings-concatenate pass pass pass pass pass pass pass a quoted string is itself in a concatenation, and the whitespace between it and the unquoted string beside it is kept substitution-fallback-to-environment8 casesJava 8/8 · hocon2 8/8 · pulse-pyhocon 7/8 · py.hocon 8/8 · pyhocon 7/8 · rs.hocon 8/8 · zig-hocon 8/8
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-env-fallback-for-undefined-path pass pass pass pass pass pass pass a substitution whose path is not defined in the configuration falls back to an environment variable of the same name 002-required-substitution-resolved-from-env pass pass pass pass pass pass pass a required ${x} substitution that is not present in the configuration tree is searched for in the environment and resolves to the variable's value instead of being an error 003-config-value-wins-over-env pass pass pass pass pass pass pass the environment is only searched when the substitution is not present within the configuration tree, so a value set in the configuration wins over an environment variable of the same name 004-null-in-config-blocks-env-lookup pass pass fail pass fail pass pass setting a key to null in the configuration explicitly blocks looking that substitution up in the environment, so ${CONFORMANCE_X} resolves to null even though the variable is set 005-config-lookup-is-case-sensitive pass pass pass pass pass pass pass the lookup is case-sensitive all the way until the env variable fallback is reached, so a lowercase key does not shadow an uppercase substitution and the environment variable is used 006-empty-env-var-is-empty-string pass pass pass pass pass pass pass an env variable set to the empty string is kept as such, so a required substitution resolves to "" rather than being undefined 007-empty-env-var-creates-optional-field pass pass pass pass pass pass pass an env variable set to the empty string is set to empty string rather than undefined, so an optional ${?x} substitution creates the field with "" instead of dropping it 008-env-vars-always-become-strings pass pass pass pass pass pass pass environment variables always become a string value, so values that would parse as a number, a boolean or null in HOCON stay the strings "42", "true" and "null" substitutions37 casesJava 36/37 · hocon2 31/37 · pulse-pyhocon 32/37 · py.hocon 36/37 · pyhocon 28/37 · rs.hocon 36/37 · zig-hocon 37/37
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-basic-substitution pass pass pass pass pass pass pass the syntax ${pathexpression} refers to another part of the configuration tree 002-optional-substitution-syntax pass pass pass pass pass pass pass ${?pathexpression} is the optional substitution syntax; when the path is defined it resolves exactly like ${pathexpression}, keeping the value and its type 003-optional-substitution-whitespace-illegal pass pass pass pass pass pass pass the ? in ${?pathexpression} must not have whitespace before it; the three characters ${? must be exactly grouped together 004-substitution-not-parsed-in-quoted-string pass pass pass pass pass pass pass substitutions are not parsed inside quoted strings, so this stays the literal text 005-substitution-value-concatenation-unquoted pass pass pass pass pass pass pass worked example: to get a string containing a substitution, use value concatenation with the substitution in the unquoted portion 006-substitution-value-concatenation-quoted-tail pass pass pass pass pass pass pass worked example: quoting the non-substitution portion also forms a value concatenation with the substitution 007-substitution-path-absolute pass pass pass pass pass pass pass substitutions are resolved by looking up the path from the root configuration object, absolute rather than relative, so ${a} inside b finds the root a and not the sibling b.a 008-substitution-forward-reference pass pass pass pass pass pass pass substitution processing happens as the last parsing step, so a substitution can look forward in the configuration 009-substitution-latest-duplicate-key pass pass pass pass pass pass pass if a key has been specified more than once, a substitution referring to it always evaluates to its latest-assigned value 010-substitution-latest-duplicate-key-object-merge pass pass pass pass pass pass pass a substitution to a key set more than once evaluates to the merged object, not an earlier snapshot 011-undefined-required-substitution-is-error pass pass pass pass pass pass pass an undefined substitution with the ${foo} syntax is invalid and should generate an error 012-undefined-optional-substitution-field-not-created pass pass pass pass pass pass pass if an undefined ${?foo} substitution is the value of an object field, the field should not be created 013-undefined-optional-substitution-keeps-previous-value pass pass fail pass pass pass pass if an undefined ${?foo} would override a previously-set value for the same field, the previous value remains 014-undefined-optional-substitution-array-element-not-added pass pass pass pass pass pass pass if an undefined ${?foo} substitution is an array element, the element should not be added 015-undefined-optional-substitution-concatenation-string-empty pass pass pass pass pass pass pass an undefined ${?foo} in a value concatenation with another string becomes an empty string 016-undefined-optional-substitution-concatenation-object-empty pass pass pass pass pass pass pass an undefined ${?foo} in a value concatenation with an object becomes an empty object 017-undefined-optional-substitution-concatenation-array-empty pass pass pass pass pass pass pass an undefined ${?foo} in a value concatenation with an array becomes an empty array 018-two-optional-substitutions-both-undefined-field-not-created pass pass pass pass pass pass pass foo : ${?bar}${?baz} avoids creating the field only if both bar and baz are undefined 019-two-optional-substitutions-one-defined-field-created pass pass pass pass pass pass pass foo : ${?bar}${?baz} creates the field once at least one of bar or baz is defined 020-substitution-not-allowed-in-key pass pass pass pass pass pass pass substitutions are only allowed in field values and array elements, not in keys 021-substitution-not-allowed-nested-in-path-expression pass pass pass pass pass pass pass substitutions are not allowed nested inside other substitutions (path expressions) 022-substitution-preserves-type-array pass pass pass pass pass pass pass a substitution is replaced with any value type; if it is the only part of a value, the type (here an array) is preserved 024-substitution-concatenation-forms-string pass pass pass pass pass pass pass when a substitution is not the only part of a value, it is value-concatenated to form a string rather than keeping the substituted type 025-substitution-preserves-type-string-booleans-null pass pass fail pass fail pass pass a substitution is replaced with any value type and, when it is the only part of a value, the type is preserved: here string, true, false and null stay what they were rather than becoming strings 026-unterminated-substitution-is-error pass pass pass pass pass pass pass the syntax is ${pathexpression}: a substitution opened with ${ and never closed with } is not that syntax, and $ cannot start an unquoted string either, so it must be rejected 028-optional-substitution-to-null-value-is-defined pass pass fail pass fail pass pass a substitution is undefined only if it does not match any value present in the configuration, and null is among the value types a substitution is replaced with, so ${?a} with a = null is defined: the field is created with null rather than omitted 030-quoted-key-is-not-a-substitution pass pass pass pass pass pass pass substitutions are not parsed inside quoted strings, and a quoted key is a quoted string: "${a}" is the literal key ${a}, not a substitution in a key (which would be illegal) 031-whitespace-inside-braces-is-trimmed pass fail pass pass fail pass pass the path inside ${} has the syntax of an object key, and whitespace before the first and after the last element of a key is discarded, so ${ b } is ${b} 032-empty-substitution-illegal pass pass pass pass pass pass pass ${} contains no path expression at all, so it is neither a substitution nor anything else 033-path-through-a-non-object-is-undefined pass fail pass pass pass pass pass a is a number, so a.x names nothing; a required substitution to an undefined path is an error 034-optional-path-through-a-non-object-is-omitted pass fail pass pass fail pass pass the same path with the optional form: undefined, so the field is not created 035-quoted-path-element-with-space pass pass pass pass pass pass pass a substitution path has the syntax of an object key, so a quoted element may contain a space and names the field written the same way 036-empty-quoted-path-element pass pass fail pass fail pass pass an empty path element is legal when quoted, in a substitution as in a key 037-path-into-a-list-element-is-undefined pass fail pass pass fail pass pass a path names object fields only, so b.0 does not reach into the list b even though it has an element at index 0; the optional substitution is undefined and a is not created. The list is still unresolved when the lookup happens, which is what makes an implementation look inside it 038-whitespace-after-question-mark pass fail pass pass fail pass pass only whitespace before the ? is forbidden; after it the path follows and its leading whitespace is discarded as in any key 039-undefined-optional-with-number-is-a-string fail fail pass fail fail fail pass ${?x}42 is a concatenation of two parts, so the value is a string: the undefined optional becomes empty and the number keeps its written form. The spec states the empty-string rule for a concatenation 'with another string', so this row extends it to a number; typesafe/config instead drops the undefined part and keeps the lone number as a numberjava: diverges 040-null-blocks-environment-lookup pass pass fail pass fail pass pass the configuration sets CONFORMANCE_NULLED to null, so the substitution resolves to that null and the environment variable of the same name is never consulted unchanged-from-json27 casesJava 21/27 · hocon2 21/27 · pulse-pyhocon 13/27 · py.hocon 20/27 · pyhocon 13/27 · rs.hocon 18/27 · zig-hocon 27/27
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-quoted-string-json-format pass pass fail pass fail pass pass quoted strings are in the same format as JSON strings, so JSON escapes like \n, \t, \", \\ and \u unicode escapes are honored 002-string-value-type pass pass pass pass pass pass pass string is one of the possible value types 003-number-value-type pass pass pass pass pass pass pass number is one of the possible value types 004-object-value-type pass pass pass pass pass pass pass object is one of the possible value types 005-array-value-type pass pass pass pass pass pass pass array is one of the possible value types 006-boolean-value-type pass pass pass pass pass pass pass boolean is one of the possible value types 007-null-value-type pass pass pass pass pass pass pass null is one of the possible value types 008-number-format-decimal-and-exponent pass pass pass pass pass pass pass allowed number formats match JSON, including negative numbers, decimals, and exponent notation 009-number-leading-zero-illegal fail fail fail fail fail fail pass allowed number formats match JSON, and JSON forbids a zero followed by further digits; the initial number character plus the valid-in-JSON number characters after it must be parsed as a number value, so the whole token 01 has to be a number and is not one, with no fallback offered. typesafe/config parses it leniently as the number 1.java: lenient 010-negative-number-without-integer-part-illegal fail fail fail fail fail fail pass allowed number formats match JSON, and JSON requires a digit before the decimal point, so -.33 is a number token that is not a valid number. typesafe/config releases up to 1.4.9 parse it as -0.33; upstream main since lightbend/config#861 (unreleased) reads it as the unquoted string "-.33", like .33 -- still accepted, still not the error the hyphen rule asks forjava: lenient 011-negative-exponent-number-without-integer-part-illegal fail pass fail fail fail fail pass the same JSON rule with an exponent: -.33e+1 has no integer part. typesafe/config releases up to 1.4.9 parse it as -3.3; upstream main since lightbend/config#861 (unreleased) rejects it, on the reserved +java: lenient 012-number-with-trailing-dot-illegal fail fail fail fail fail fail pass JSON requires a digit after the decimal point; 1. is a number token that is not a valid number. typesafe/config reads it as 1, and lightbend/config#861 leaves it so on purposejava: lenient 013-trailing-dot-before-exponent-illegal fail fail fail fail fail fail pass the same rule with an exponent after the dot; typesafe/config reads it as -1000 (named out of scope in lightbend/config#861)java: lenient 014-negative-leading-zeros-illegal fail fail fail fail fail fail pass JSON forbids leading zeros after the minus sign as well; typesafe/config reads it as -33 (named out of scope in lightbend/config#861)java: lenient 015-remaining-json-escapes pass pass fail pass fail pass pass the JSON escapes \/ \b \f and \r are valid in a quoted string and mean slash, backspace, form feed and carriage return 016-unknown-escape-illegal pass pass fail pass fail pass pass only the JSON escape sequences exist; \q is not one of them, so the string is invalid 017-non-hex-unicode-escape-illegal pass pass fail pass fail pass pass \u must be followed by exactly four hex digits; zzzz are not hex digits 018-unterminated-quoted-string-illegal pass pass pass pass pass pass pass a quoted string that is still open at the end of the file is invalid 019-backslash-at-end-of-file-illegal pass pass pass pass pass pass pass a backslash with nothing after it is not an escape and the string is still open at the end of the file 020-unicode-escape-cut-off-by-end-of-file-illegal pass pass pass pass pass pass pass the file ends after two of the four hex digits a \u escape requires 021-raw-newline-in-quoted-string-illegal pass pass fail pass pass pass pass JSON forbids unescaped control characters in strings, and a raw newline is one; a quoted string cannot span lines 022-raw-tab-in-quoted-string-illegal pass pass fail fail fail fail pass a raw tab is a control character too, so it must be written as \t inside quotes 023-64-bit-integer pass pass pass pass pass pass pass a number beyond 32 bits is still a number and keeps its exact value 024-raw-utf8-in-quoted-string pass pass pass pass fail pass pass the file is UTF-8, so a multi-byte sequence is one character 025-nan-is-not-a-number pass fail pass pass pass pass pass NaN is not a JSON number, so the bare token is an unquoted string 026-surrogate-pair-escape pass pass fail pass fail fail pass JSON writes a character outside the BMP as two \u escapes forming a surrogate pair, which decode to one character 027-unpaired-surrogate-escape-is-kept pass pass fail pass fail fail pass quoted strings are JSON strings, and JSON's grammar accepts any \uXXXX escape, an unpaired surrogate included, so the string holds that one code unit units-format6 casesJava 6/6 · hocon2 6/6 · pulse-pyhocon 6/6 · py.hocon 6/6 · pyhocon 5/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-number-value-is-default-unit pass pass pass pass pass pass pass if the value is a number, it is taken to be a number in the default unit 002-quoted-string-with-no-unit-uses-default-unit pass pass pass pass pass pass pass a string value with no unit name should be interpreted with the default unit, as if it were a number 003-string-with-unit-name-specifies-interpretation pass pass pass pass fail pass pass a string value with a unit name has that name specify the value's interpretation 004-whitespace-optional-around-number-and-unit pass pass pass pass pass pass pass the units-format grammar is optional whitespace, a number, optional whitespace, an optional unit name, optional whitespace 005-unit-name-must-be-letters-only pass pass pass pass pass pass pass the unit name consists only of letters (Unicode L* categories, Java isLetter()), so a unit name with a trailing digit is not a legal units-format string 006-unit-before-number-is-illegal pass pass pass pass pass pass pass the units-format grammar puts the number before the unit name, not after, so a unit-then-number string is not a legal units-format value unquoted-strings25 casesJava 22/25 · hocon2 21/25 · pulse-pyhocon 11/25 · py.hocon 21/25 · pyhocon 16/25 · rs.hocon 21/25 · zig-hocon 25/25
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-simple-unquoted-string pass pass pass pass pass pass pass a sequence of characters outside a quoted string is a string value when it contains none of the forbidden characters 002-quoted-alternative-for-forbidden-char pass pass pass pass pass pass pass a quoted string may always be used as an alternative to write a character that is not permitted in an unquoted string, such as '@' 003-unquoted-cannot-contain-at-sign pass pass fail pass pass pass pass '@' is one of the listed forbidden characters, so it cannot appear inside an unquoted string 004-double-slash-starts-comment pass pass fail pass fail pass pass the two-character string '//' starts a comment, ending the unquoted string before it even mid-token 005-single-slash-allowed pass pass pass pass pass pass pass only the two-character string '//' is special for comments; a lone '/' is not a forbidden character and stays part of the unquoted string 006-truefoo-parses-as-true-then-foo pass fail pass pass pass pass pass worked example: truefoo parses as the boolean token true followed by the unquoted string foo, because its initial characters parse as true 007-footrue-is-one-unquoted-string pass pass pass pass pass pass pass worked example: footrue does not begin with true, false, null, or a number, so it parses as a single unquoted string 008-number-then-suffix-string pass pass pass pass pass pass pass worked example: 10.0bar is the number 10.0 followed by the unquoted string bar, since 10.0 parses as a number at the start 009-digits-not-at-start-stay-string pass pass pass pass pass pass pass worked example: bar10.0 does not begin with a digit, so the whole thing is the single unquoted string bar10.0, not a number 010-embedded-null-not-recognized pass pass pass pass pass pass pass embedded (non-initial) null, true, false, and numbers are not recognized as such: they are just part of the string, since only the leading characters are checked 011-unquoted-cannot-contain-backslash pass pass fail pass fail pass pass backslash is a forbidden character, and unquoted strings support no escaping at all, so a literal backslash requires a quoted string instead 012-unquoted-cannot-start-with-hyphen fail fail fail fail fail fail pass an unquoted string may not begin with a hyphen, and the initial number character plus any valid-in-JSON number characters that follow must be parsed as a number value -- here that is the lone '-', which is not a number. typesafe/config falls back to unquoted text and yields the string '-foo', the same leniency it shows for '1e'.java: lenient 013-negative-number-then-string pass pass pass pass pass pass pass a hyphen followed by digits begins number parsing (-1), after which foo continues as a separate unquoted string token, mirroring the 10.0bar example for negative numbers 014-hash-starts-comment-not-forbidden-char-in-string pass pass fail pass pass pass pass '#' is forbidden inside an unquoted string because it already has meaning in HOCON: it starts a comment 015-reserved-forbidden-char-no-current-meaning pass pass fail pass pass pass pass some forbidden characters, such as '!', have no meaning in HOCON today and are reserved as keywords for future extensions to the spec 016-control-char-allowed-unquoted pass pass pass pass pass pass pass unquoted strings place no restriction on control characters, other than the forbidden characters listed 017-control-char-forbidden-in-quoted pass pass fail fail fail fail pass quoted JSON strings may not contain control characters, per the JSON spec, unlike unquoted strings 018-leading-dot-is-not-a-number pass pass fail pass fail pass pass only 0-9 and - begin a number, so .33 is an ordinary unquoted string -- the reading lightbend/config#861 extends to -.33 019-dollar-must-start-a-substitution pass pass fail pass pass pass pass $ is a forbidden character in an unquoted string, and outside quotes it may only begin ${; $foo is neither 020-plus-must-be-followed-by-equals pass pass fail pass pass pass pass + is a forbidden character in an unquoted string and outside quotes only forms the += separator; a + with nothing after it, here at the end of the file, is invalid 021-plus-inside-a-number-illegal pass pass fail pass fail pass pass 1+2 begins like a number and is not one; + is a forbidden character, so the token cannot fall back to an unquoted string either 022-digit-then-exponent-letter-illegal fail fail fail fail fail fail pass 1e begins with a digit, so the run of number characters 1e must be parsed as a number, and it is not one; typesafe/config falls back to the unquoted string "1e", the same leniency it shows for -foojava: lenient 023-unquoted-string-ends-at-end-of-file pass pass pass pass pass pass pass an unquoted string runs to a forbidden character or whitespace, and the end of the file ends it as well; no final newline is needed 024-escape-sequence-in-unquoted-string-illegal pass pass fail pass fail pass pass unquoted strings support no escaping, so \u00e9 is not decoded, and the backslash it needs is forbidden in an unquoted string; the quoted "caf\u00e9" is how to write it 025-digit-initial-invalid-number fail fail fail fail fail fail pass an unquoted string may not begin with the digits 0-9, and the initial number character plus any valid-in-JSON number characters that follow it must be parsed as a number value -- 1.2.3 is that run, and it is not a number, so the input is rejected rather than read as a string (the everyday shape is an address like 127.0.0.1); typesafe/config and pyhocon both fall back to the unquoted string "1.2.3"java: lenient value-concatenation2 casesJava 2/2 · hocon2 2/2 · pulse-pyhocon 2/2 · py.hocon 2/2 · pyhocon 2/2 · rs.hocon 2/2 · zig-hocon 2/2
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-simple-values-concatenate-to-string pass pass pass pass pass pass pass if all the values are simple values they are concatenated into a string 004-string-concatenation-allowed-in-field-keys pass pass pass pass pass pass pass string value concatenation is allowed in field keys, in addition to field values and array elements whitespace13 casesJava 13/13 · hocon2 12/13 · pulse-pyhocon 3/13 · py.hocon 10/13 · pyhocon 4/13 · rs.hocon 10/13 · zig-hocon 13/13
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-nonbreaking-space-as-whitespace pass pass fail pass fail pass pass a nonbreaking space (0x00A0) is a Unicode Zs separator and must be treated as whitespace 002-bom-at-start-is-whitespace pass pass fail pass fail pass pass the BOM (0xFEFF) must be treated as whitespace, not as part of the key 003-unicode-line-paragraph-separator pass pass fail fail pass fail pass the line and paragraph separators U+2028 and U+2029 are whitespace but not newlines, so they do not separate two fields: 1 b concatenates and the second = is a stray token 004-vertical-tab-form-feed-whitespace pass pass fail pass fail pass pass vertical tab (0x000B) and form feed (0x000C) are whitespace characters 005-ascii-separator-control-chars pass pass pass fail pass fail pass the ASCII separator controls 0x1C-0x1F are whitespace but not newlines, so they do not separate two fields 006-special-nonbreaking-spaces pass pass fail pass fail pass pass the spec names figure space (0x2007) and narrow no-break space (0x202F) explicitly among the nonbreaking spaces treated as whitespace 007-tab-is-whitespace pass pass pass pass pass pass pass a tab is whitespace around a key and a separator 008-crlf-line-endings pass pass pass pass pass pass pass a carriage return is whitespace and the line feed beside it is the newline, so CRLF line endings separate fields; if the CR were text the first value would be "1\r" 009-lone-carriage-return-is-not-a-newline pass fail fail fail fail fail pass a lone carriage return is whitespace but not a newline, so 1 b concatenates and the second = is a stray token 010-line-separator-is-whitespace pass pass fail pass fail pass pass U+2028 around the separator is whitespace, not part of the key or the value 011-ascii-separator-controls-are-whitespace pass pass fail pass fail pass pass the file, group, record and unit separators 0x1C-0x1F are whitespace around keys and separators 012-ideographic-space-is-whitespace pass pass fail pass fail pass pass the ideographic space U+3000 is a Zs space separator and so whitespace, like every member of the category 013-bom-inside-the-document-is-whitespace pass pass fail pass fail pass pass the BOM is whitespace wherever it appears, not only as a file-start marker array-and-object-concatenation19 casesJava 17/19 · hocon2 19/19 · pulse-pyhocon 19/19 · py.hocon 19/19 · pyhocon 19/19 · rs.hocon 19/19 · zig-hocon 19/19
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-arrays-concatenate pass pass pass pass pass pass pass arrays can be concatenated with arrays when only non-newline whitespace separates them 002-objects-concatenate pass pass pass pass pass pass pass objects can be concatenated with objects when only non-newline whitespace separates them 003-mixing-array-and-object-is-error pass pass pass pass pass pass pass it is an error to concatenate an array with an object 004-substitution-counts-as-array-for-concatenation pass pass pass pass pass pass pass for purposes of concatenation a substitution that resolves to an array also means an array, so ${x} [ 1, 2 ] with x an array concatenates into one array 005-substitution-counts-as-object-for-concatenation pass pass pass pass pass pass pass for purposes of concatenation a substitution that resolves to an object also means an object, so ${x} { c : 1 } with x an object merges into one object 006-newline-between-values-prevents-concatenation pass pass pass pass pass pass pass newlines between a substitution and an array prevent concatenation, so inside an array they stay two separate elements instead of one concatenated array 007-object-concatenation-merges-and-second-overrides pass pass pass pass pass pass pass for objects, concatenation means merging, and the second object overrides the first on overlapping keys 008-array-cannot-be-field-key pass pass pass pass pass pass pass arrays cannot be field keys; nested inside an object so the rejection comes from the key position, not from the document root 009-object-cannot-be-field-key pass pass pass pass pass pass pass objects cannot be field keys; nested inside an object so the rejection comes from the key position, not from the document root 010-worked-example-object-inheritance pass pass pass pass pass pass pass the spec's worked example: object concatenation with a substitution is a common way to express inheritance 011-worked-example-array-path-append pass pass pass pass pass pass pass the spec's worked example: array concatenation with a substitution is a common way to add to paths 012-worked-example-self-referential-array-concat pass pass pass pass pass pass pass the spec's worked example: a later array definition can concatenate a substitution referring to its own earlier value with a new array 013-newlines-within-value-still-allow-concatenation pass pass pass pass pass pass pass newlines may occur within an array without preventing it from concatenating with a second array on the same line as its closing bracket 014-substitution-object-with-literal-array-is-error pass pass pass pass pass pass pass a substitution that resolves to an object means an object for concatenation, and concatenating an object with an array is an error 015-substitution-array-with-literal-object-is-error pass pass pass pass pass pass pass a substitution that resolves to an array means an array for concatenation, and concatenating an array with an object is an error 016-mixing-object-then-array-is-error pass pass pass pass pass pass pass it is an error to concatenate an object with an array in this order too, not only array then object 017-newline-between-arrays-in-field-value-prevents-concatenation pass pass pass pass pass pass pass a newline between two arrays in a field value prevents concatenation, so the second array begins a new field, and an array cannot be a field key 018-object-with-inner-merge-concatenated-with-substitution fail pass pass pass pass pass pass an object whose field a is itself a merge of an object and a substitution concatenates with a substituted object like any other: the two merge. typesafe/config fails with an internal error (BugOrBroken) because the concatenation is not registered as the merge's parent; the same objects written as literals concatenate finejava: unsupported 019-self-referential-object-concatenation-with-inner-merge fail pass pass pass pass pass pass ${a} in a's own value refers to the earlier a, an object, so the concatenation merges {p: ...} with {q: 1}; the inner p is itself a merge of an object and a substitution. typesafe/config fails with an internal error (BugOrBroken) when the merge inside a concatenation piece resolves, as in 018java: unsupported arrays-without-commas-or-newlines7 casesJava 7/7 · hocon2 7/7 · pulse-pyhocon 6/7 · py.hocon 6/7 · pyhocon 7/7 · rs.hocon 6/7 · zig-hocon 7/7
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-newlines-instead-of-commas pass pass pass pass pass pass pass arrays allow newlines to separate elements instead of commas 002-whitespace-instead-of-comma-concatenates-into-one-element pass pass pass pass pass pass pass the spec's worked example: non-newline whitespace instead of a comma produces concatenation, so this array has one element, the string 1 2 3 4, not four integers 003-newline-separates-two-arrays-into-elements pass pass pass pass pass pass pass the spec's worked example: a newline between two bracketed arrays makes them two separate elements 004-same-line-whitespace-concatenates-two-arrays-into-one-element pass pass pass pass pass pass pass the spec's worked example: whitespace on the same line between two bracketed arrays concatenates them into a single element, the array [1, 2, 3, 4] 005-unquoted-string-concatenation-with-substitutions-in-array pass pass pass pass pass pass pass the spec's worked example: unquoted words and substitutions separated by whitespace concatenate into one string element, and only the comma separates elements 006-two-elements-each-a-substitution-concatenation pass pass pass pass pass pass pass the spec's worked example: whitespace between two substitutions concatenates them into one element, and the comma separates the two concatenations 007-whitespace-never-separates-fields pass pass fail fail pass fail pass non-newline whitespace is never a field separator, so two field definitions cannot be written on one line separated only by spaces commas13 casesJava 13/13 · hocon2 13/13 · pulse-pyhocon 5/13 · py.hocon 12/13 · pyhocon 6/13 · rs.hocon 12/13 · zig-hocon 13/13
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-array-trailing-comma pass pass pass pass pass pass pass the last element in an array may be followed by a single comma, which is ignored: [1,2,3,] and [1,2,3] are the same array 002-array-newline-no-comma pass pass pass pass pass pass pass array elements need not have a comma between them as long as an ASCII newline separates them: [1\n2\n3] and [1,2,3] are the same array 003-array-double-trailing-comma-invalid pass pass fail pass fail pass pass [1,2,3,,] is explicitly called invalid because it has two trailing commas 004-array-initial-comma-invalid pass pass fail pass fail pass pass [,1,2,3] is explicitly called invalid because it has an initial comma 005-array-double-comma-invalid pass pass fail pass fail pass pass [1,,2,3] is explicitly called invalid because it has two commas in a row 006-object-trailing-comma pass pass pass pass pass pass pass these same comma rules apply to fields in objects, so a single trailing comma after the last field is ignored 007-object-newline-no-comma pass pass pass pass pass pass pass fields in objects need not have a comma between them as long as an ASCII newline separates them, same as array elements 008-object-double-comma-invalid pass pass fail pass fail pass pass these same comma rules apply to fields in objects, so two commas in a row between fields is invalid just as in an array 009-fields-on-one-line-need-a-comma pass pass fail fail pass fail pass two fields need a comma or a newline between them; on one line with neither, 1 b becomes a value concatenation and the second = is a stray token 011-trailing-comma-at-implicit-root pass pass pass pass pass pass pass the implicit root object follows the object comma rules, so its last field may carry a single trailing comma 012-leading-comma-at-implicit-root-illegal pass pass fail pass fail pass pass an initial comma is invalid in an object, the implicit root included 013-double-trailing-comma-in-object-illegal pass pass fail pass fail pass pass the array comma rules apply to object fields: two trailing commas are invalid 014-leading-comma-in-object-illegal pass pass fail pass fail pass pass the array comma rules apply to object fields: an initial comma is invalid comments14 casesJava 14/14 · hocon2 14/14 · pulse-pyhocon 14/14 · py.hocon 14/14 · pyhocon 14/14 · rs.hocon 14/14 · zig-hocon 14/14
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-hash-comment pass pass pass pass pass pass pass anything between # and the next newline is a comment and ignored 002-double-slash-comment pass pass pass pass pass pass pass anything between // and the next newline is a comment and ignored 003-whole-line-comment pass pass pass pass pass pass pass a comment may occupy an entire line by itself, ignored just like a trailing one 004-comment-stops-at-newline pass pass pass pass pass pass pass a comment runs only to the next newline, so the following line is parsed normally 005-hash-inside-quoted-string-is-not-a-comment pass pass pass pass pass pass pass a # inside a quoted string is not treated as starting a comment 006-double-slash-inside-quoted-string-is-not-a-comment pass pass pass pass pass pass pass a // inside a quoted string is not treated as starting a comment 007-comment-between-separator-and-value pass pass pass pass pass pass pass a comment runs to the end of its line and is then over; the value may follow on the next line just as it may after a bare = 008-comment-then-blank-line-then-field pass pass pass pass pass pass pass a comment separated from the following field by a blank line is ignored like any other comment 009-comment-inside-object pass pass pass pass pass pass pass comments and blank lines inside an object body are ignored 010-comments-inside-array pass pass pass pass pass pass pass comments and blank lines between array elements are ignored, and do not join or separate elements 011-comment-before-values-of-every-type pass pass pass pass pass pass pass a comment before a field is ignored whatever type its value has 012-comment-before-explicit-root-object pass pass pass pass pass pass pass a comment and a blank line before an explicit root object are skipped before the implicit-braces rule looks at the first token; the spec's wording ("does not begin with") is loose here and this row records the consensus reading, as omit-root-braces/009 does 013-line-comment-after-array pass pass pass pass pass pass pass a // comment after an array is not text in a concatenation; the value is just the array 014-hash-comment-after-array-without-space pass pass pass pass pass pass pass a # comment directly after ] is a comment, not text in a concatenation concatenation-whitespace-and-substitutions9 casesJava 7/9 · hocon2 9/9 · pulse-pyhocon 8/9 · py.hocon 9/9 · pyhocon 7/9 · rs.hocon 9/9 · zig-hocon 9/9
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-unquoted-whitespace-between-substitutions-is-significant-for-strings pass pass pass pass pass pass pass when the substitutions turn out to be strings, the unquoted whitespace between them is significant and survives into the concatenated string 002-quoted-whitespace-between-substitutions-is-error pass pass pass pass pass pass pass between substitutions that resolve to objects, unquoted whitespace is ignored but quoted whitespace should be an error 003-unquoted-whitespace-between-object-and-list-substitutions-is-ignored pass pass pass pass pass pass pass unquoted whitespace between substitutions that resolve to objects or lists must be ignored, so they merge and concatenate as if adjacent 004-tab-between-list-substitutions-is-ignored pass pass pass pass pass pass pass whitespace between two list substitutions is ignored, tabs included, and the lists concatenate 005-nonbreaking-space-between-list-substitutions-is-ignored pass pass pass pass fail pass pass U+00A0 is a Zs space and so whitespace in HOCON; between two list substitutions it is ignored (lightbend/config#862 review) 006-bom-between-list-substitutions-is-ignored pass pass fail pass fail pass pass the BOM must be treated as whitespace, so between two list substitutions it is ignored 007-zero-width-space-between-list-substitutions-is-error fail pass pass pass pass pass pass U+200B is format (Cf), not a space separator, so it is text, and text between two list substitutions is invalid. typesafe/config drops it and concatenates the lists; lightbend/config#862 rejects itjava: lenient 008-list-then-undefined-optional-is-the-list pass pass pass pass pass pass pass an undefined optional substitution in a concatenation with an array becomes an empty array 009-undefined-optional-then-list-is-the-list fail pass pass pass pass pass pass the mirror of 008: an undefined optional substitution before an array becomes an empty array too. typesafe/config keeps the whitespace before the list as text and rejects it, while accepting 008 (noted on lightbend/config#862)java: unsupported config-and-file-merging4 casesJava 4/4 · hocon2 4/4 · pulse-pyhocon 4/4 · py.hocon 4/4 · pyhocon 2/4 · rs.hocon 4/4 · zig-hocon 4/4
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 002-file-merge-is-recursive-and-later-file-wins pass pass pass pass pass pass pass merging objects loaded from different files is exactly the same behavior as merging duplicate fields in the same file: object-valued fields present in both files merge recursively and a non-object field takes the value from the later file 003-non-object-between-files-hides-earlier-object pass pass pass pass fail pass pass the spec's own example: an intermediate non-object value hides earlier object values because merging is done in pairs, so when 42 is paired with { y : 2 } it simply wins and loses all information about what it overrode, and the two objects are never merged 004-adjacent-objects-across-files-merge pass pass pass pass fail pass pass the spec's re-ordered example: with the non-object 42 moved to the lowest priority the two objects are adjacent, so they merge to { a : { x : 1, y : 2 } } 005-null-clears-included-object pass pass pass pass pass pass pass setting an object to null and then setting it back to a new object clears it and starts over, which is how to get rid of default fallback values from another file that you don't want duplicate-keys-and-object-merging28 casesJava 20/28 · hocon2 28/28 · pulse-pyhocon 21/28 · py.hocon 27/28 · pyhocon 24/28 · rs.hocon 27/28 · zig-hocon 28/28
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-later-scalar-wins pass pass pass pass pass pass pass for a non-object value the later assignment simply replaces the earlier one 002-objects-merge pass pass pass pass pass pass pass two objects under the same key merge key-by-key, they do not replace 003-scalar-breaks-the-merge pass pass pass pass fail pass pass merging is always done two values at a time: the scalar replaces the first object (non-object always wins), then the second object replaces the scalar (no merging, object is the new value), so the two objects never see each other 004-merge-scalar-field-later-wins pass pass pass pass pass pass pass for a non-object-valued field present in both merged objects, the field from the second object is used 005-merge-nested-object-recursive pass pass pass pass pass pass pass for an object-valued field present in both objects, the object values are recursively merged by the same rules 006-null-prevents-merge pass pass pass pass pass pass pass the spec's own example: an intermediate null is a non-object, so it replaces the first object and is in turn replaced by the second object with no merging, which prevents the two objects from ever seeing each other 007-merge-keeps-fields-from-either-side pass pass pass pass pass pass pass fields present in only one of the two objects are added to the merged object as they are, whether the first or the second side has them and whether the value is a scalar or an object with nothing to merge against 008-arrays-are-not-objects pass pass pass pass pass pass pass only two object values merge; an array is not an object, so a later array replaces an earlier array (no concatenation) and replaces an earlier object (no merge) 009-later-null-overrides pass pass pass pass pass pass pass null is a value like any other, so a later null overrides an earlier scalar or object: the key stays, set to null, rather than being removed or the assignment skipped 010-null-in-referenced-key-history-does-not-block-merge fail pass fail pass pass pass pass the null at r only stops merging at r; ${r} is the object {x:1}, and a later object merges with the earlier a, so helper stays. typesafe/config carries r's null through the substitution and drops helper (lightbend/config#864)java: diverges 011-nested-null-in-referenced-object-does-not-block-merge fail pass fail pass pass pass pass the same rule one level down: the null in v.nested's history belongs to v.nested, so a.nested still merges with the substituted {x:1} (lightbend/config#864)java: diverges 012-reference-to-merged-field-sees-the-same-value fail pass pass pass pass pass pass a substitution sees the resolved value of the field it names, so o24bbd equals a.nested, which keeps helper. The key name is chosen to sort before a in typesafe/config's root HashMap, the order in which the first version of lightbend/config#864 answered differently for the twojava: diverges 013-top-level-null-before-object-with-substitution fail pass fail pass pass pass pass the null belongs to v's history even when v's object is still unresolved, so a merges and keeps helper (lightbend/config#864)java: diverges 014-own-null-still-blocks-merge-with-referenced-object pass pass pass pass pass pass pass the receiver's own intermediate null still breaks the merge: a is {helper:0}, then null, then the substituted object, so helper is gone 015-earlier-receiver-value-is-evaluated-once-merged fail pass fail pass pass pass pass ${r} is an object, so it merges with the earlier a and the ${missing} inside it is evaluated: an error. typesafe/config 1.4.5 agreed; 1.4.9 drops the earlier a and yields {y:1}, and lightbend/config#864 restores the errorjava: lenient 016-null-deeper-in-a-merge-stack-stays-at-its-key fail pass fail pass fail pass pass v's value is a merge whose top is an object and whose null sits further down, under nested; that null is still v.nested's merge instruction, so a.nested keeps helper (lightbend/config#864 review, round 3)java: diverges 017-lookup-into-nested-null-merges fail pass pass pass pass pass pass two nested nulls each break only their own key's merge, and a lookup of a.n.d sees the final {x:1}; typesafe/config throws BugOrBroken in this key order (reported on lightbend/config#864, same on main)java: unsupported 018-object-then-empty-object pass pass pass pass pass pass pass two objects merge, so the empty later object keeps x; replacement would have given {} 019-duplicate-keys-merge-inside-a-list-element pass pass pass pass pass pass pass an object that is a list element follows the same duplicate-key rules: the later value, here resolved from a substitution, wins 020-objects-and-substitutions-alternate pass pass fail fail pass fail pass four values for a, two of them substitutions that resolve to objects: all four merge in order 021-later-object-replaces-earlier-concatenation pass pass pass pass fail pass pass a string concatenation is a non-object value, so the later object replaces it rather than merging with it 022-scalar-then-empty-object-then-object pass pass pass pass fail pass pass the number cuts the merge off from anything before it; the empty object after it and the object after that still merge, and the empty one contributes nothing 023-empty-object-then-substitution pass pass pass pass pass pass pass an empty object followed by a substitution that resolves to an object merges into that object; the empty layer contributes nothing 024-merge-stack-nested-inside-a-merge-stack pass pass fail pass pass pass pass the object layer of a's merge contains a field b that is itself a merge of a substitution and an object; both levels merge 025-lookup-into-object-that-replaced-a-list fail pass pass pass pass pass pass the object replaces the earlier list, so a.m exists and p is 1 whatever order the keys are resolved in. typesafe/config fails when the reader p is resolved before a (it iterates p before a); the same file with the reader named zzz resolvesjava: unsupported 026-missing-key-in-object-that-replaced-a-list pass pass pass pass pass pass pass the same shape with a key the object does not have: the list below it is not consulted, a.b is undefined and the optional p is not created 027-nested-object-then-scalar-inside-merge pass pass pass pass pass pass pass inside a merge the same rules apply per field: b is an object in the first and a number in the second, so the later value replaces it 028-null-in-second-object-wins pass pass pass pass pass pass pass null is a non-object value, so the second object's null is used for b rather than dropped duration-format9 casesJava 9/9 · hocon2 9/9 · pulse-pyhocon 9/9 · py.hocon 9/9 · pyhocon 2/9 · rs.hocon 9/9 · zig-hocon 9/9
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-bare-number-is-milliseconds pass pass pass pass pass pass pass bare numbers are taken to be in milliseconds already 002-uppercase-unit-is-illegal pass pass pass pass pass pass pass the supported unit strings for duration are case-sensitive and must be lowercase, so an uppercase unit is not one of the supported strings 003-nanoseconds-spellings pass pass pass pass fail pass pass exactly ns, nano, nanos, nanosecond, nanoseconds are the supported unit strings for nanoseconds 004-microseconds-spellings pass pass pass pass fail pass pass exactly us, micro, micros, microsecond, microseconds are the supported unit strings for microseconds 005-milliseconds-spellings pass pass pass pass fail pass pass exactly ms, milli, millis, millisecond, milliseconds are the supported unit strings for milliseconds 006-seconds-spellings pass pass pass pass fail pass pass exactly s, second, seconds are the supported unit strings for seconds 007-minutes-spellings pass pass pass pass fail pass pass exactly m, minute, minutes are the supported unit strings for minutes 008-hours-spellings pass pass pass pass fail pass pass exactly h, hour, hours are the supported unit strings for hours 009-days-spellings pass pass pass pass fail pass pass exactly d, day, days are the supported unit strings for days in the duration format include-file-formats23 casesJava 19/23 · hocon2 3/23 · pulse-pyhocon 3/23 · py.hocon 13/23 · pyhocon 3/23 · rs.hocon 14/23 · zig-hocon 23/23
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-basename-loads-every-extension pass fail fail pass fail pass pass a name without an extension is a basename: all of base.properties, base.json and base.conf exist, so all three are loaded and merged, properties first and HOCON last, so the .conf value wins where they overlap 002-basename-with-only-conf pass fail fail pass fail pass pass a basename with only base.conf present loads just that file 003-basename-with-only-properties pass fail fail pass fail pass pass a basename with only base.properties present loads it as Java properties, whose values are all strings 004-basename-with-no-file-is-ignored pass pass pass pass pass pass pass a basename that exists with no extension at all is a missing include, and a missing include is ignored 005-required-basename-with-no-file-is-error pass pass pass pass pass pass pass required() makes the missing basename an error 006-json-extension-means-json-syntax fail fail fail fail fail fail pass a file named .json is in JSON format, and an unquoted value is not JSON. typesafe/config parses a .json named outright with the HOCON parser, so the file is accepted; the same file found through a basename is rejectedjava: lenient 007-properties-extension-means-properties-syntax fail fail fail pass fail pass pass a file named .properties is in Java properties format, where the first = separates key from value and values are strings. typesafe/config parses a .properties named outright with the HOCON parser and fails on the second =; the same file found through a basename is read as propertiesjava: unsupported 008-properties-keys-are-paths pass fail fail pass fail pass pass in a properties file a dotted key is a path, so a.b and a.c build an object at a; the spec's Java properties mapping says the object must always win when a key is both a value and a parent, whatever the order; values are strings 010-json-file-with-every-value-type pass fail fail pass fail pass pass a .json file found through a basename is parsed as JSON and contributes its object 011-json-rejects-unquoted-values pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; an unquoted value is a HOCON extension, not JSON 012-json-rejects-substitutions pass fail fail pass fail pass pass the .json file is found through a basename, so it is parsed as JSON; ${} is a HOCON extension, not JSON 013-json-rejects-trailing-comma pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; JSON has no trailing commas 014-json-rejects-duplicate-keys pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; the spec's own footing is that duplicate keys are invalid JSON (L194-195), so the second a is an error rather than an override 015-json-array-needs-commas pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; JSON separates array elements with commas only; whitespace does not concatenate or separate 016-json-rejects-empty-document pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; an empty document is not JSON, although it is an empty HOCON object 017-json-rejects-scalar-root pass fail fail pass fail pass pass the .json file is found through a basename, so it is parsed as JSON; a JSON document included here must be an object; a bare number is neither an object nor valid as an included root 019-json-key-must-be-a-string pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; a JSON key is a quoted string; a number cannot be a key 020-json-newline-is-not-a-separator pass fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; in JSON a newline between fields does not stand in for the comma 022-json-dotted-key-is-one-key pass fail fail pass fail pass pass the .json file is found through a basename, so it is parsed as JSON; a JSON key is a plain string, not a path expression, so a.b stays a single key 023-json-leading-zero-illegal fail fail fail fail fail fail pass the .json file is found through a basename, so it is parsed as JSON; JSON forbids a leading zero on a number. typesafe/config reads 01 as 1, the same leniency it shows in HOCONjava: lenient 024-unknown-extension-is-not-a-basename fail pass pass fail pass pass pass x.txt has an extension, so it is not a basename to probe; it is a file adjacent to the including one and of the same type. The spec does not define what counts as an extension, and this row reads any dotted suffix as one. typesafe/config probes every name that does not end in .conf, .json or .properties as a basename and finds nothingjava: diverges 025-json-is-parsed-after-properties pass fail fail pass fail pass pass base.properties and base.json both exist; JSON is parsed next-to-last, after properties, so its value for a wins 026-conf-is-parsed-after-json pass fail fail pass fail pass pass base.json and base.conf both exist; HOCON is parsed last, after JSON, so its value for a wins include-locating5 casesJava 5/5 · hocon2 4/5 · pulse-pyhocon 4/5 · py.hocon 5/5 · pyhocon 4/5 · rs.hocon 5/5 · zig-hocon 5/5
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-include-string-that-is-a-url-is-a-url pass pass pass pass pass pass pass a quoted include name with a known URL scheme is a URL, not a file adjacent to this one. With nothing behind the URL the include is ignored, so a parser that read the string as a filename would also get {}; the row records the rule, it cannot distinguish the two readings 002-nested-include-is-relative-to-the-including-file pass pass pass pass pass pass pass inner.conf includes x.conf, which is found beside inner.conf, not beside main.conf and not in the process's working directory 003-plain-name-is-a-file-beside-the-including-one pass pass pass pass pass pass pass x.conf is not a URL, so it names a file adjacent to main.conf 004-absolute-path-stays-absolute pass pass pass pass pass pass pass an absolute path is loaded as such, so the file of the same name beside main.conf is not it; the absolute file does not exist and the include is ignored 005-file-argument-is-not-relative-to-the-including-file pass fail fail pass fail pass pass a file() argument is not interpreted relative to the including file, so the marker beside sub/inner.conf is not loaded; nothing of that name exists elsewhere and the include is ignored include-merging15 casesJava 14/15 · hocon2 15/15 · pulse-pyhocon 11/15 · py.hocon 12/15 · pyhocon 15/15 · rs.hocon 12/15 · zig-hocon 15/15
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-included-keys-merge-in-place pass pass pass pass pass pass pass the keys from the included object are conceptually substituted for the include statement in the including file, so the included field lands between the fields around it and merges by the usual duplicate-key rules 002-included-conf-array-root-is-error pass pass pass pass pass pass pass an included file must contain an object, not an array; if it contains an array as the root value it is invalid and an error should be generated 003-included-json-array-root-is-error pass pass pass pass pass pass pass both JSON and HOCON allow arrays as root values in a document, but an included file must contain an object, so a JSON file with an array root is an error when included 004-include-inside-nested-object pass pass pass pass pass pass pass the keys from the included root object are conceptually substituted for the include statement in the including file, so an include inside a nested object contributes its keys to that object 005-included-scalar-overrides-earlier-key pass pass pass pass pass pass pass if a key in the included object occurred prior to the include statement in the including object, the included key's value overrides the earlier value, exactly as with duplicate keys found in a single file 006-included-object-merges-with-earlier-object pass pass pass pass pass pass pass if a key in the included object occurred prior to the include statement and both values are objects, the included value merges with the earlier value, exactly as with duplicate keys found in a single file 007-included-scalar-replaces-earlier-object pass pass pass pass pass pass pass an included key's value overrides the earlier value exactly as with duplicate keys in a single file, so an included non-object replaces an earlier object rather than merging with it 008-later-key-overrides-included-key pass pass pass pass pass pass pass if the including file repeats a key from an earlier-included object, the including file's value overrides the one from the included file 009-later-object-merges-with-included-object pass pass pass pass pass pass pass if the including file repeats a key from an earlier-included object and both values are objects, the including file's value merges with the one from the included file 010-include-inside-an-object-inside-a-list pass pass pass pass pass pass pass an include statement may stand wherever a field may, including in an object that is a list element 011-include-inside-a-list-with-a-substitution fail pass pass pass pass pass pass the included file may contain substitutions like any other; typesafe/config refuses an include nested in a list when the included file is not fully resolvedjava: unsupported 012-included-merge-stack-over-earlier-object pass pass fail fail pass fail pass the included file leaves a as an object merged with a substitution; that whole stack merges with the object the including file set earlier 013-included-merge-stack-over-earlier-substitution pass pass fail fail pass fail pass the earlier value is itself a substitution; once everything resolves to objects, all three layers merge 014-included-merge-stack-over-earlier-scalar pass pass fail pass pass pass pass the earlier value is a number, so the included object stack replaces it and merges only within itself 015-included-object-on-top-of-substitution-over-earlier-substitution pass pass fail fail pass fail pass the same as 013 with the included stack the other way up, an object over a substitution; the result does not depend on which layer is on top include-missing-and-required6 casesJava 6/6 · hocon2 6/6 · pulse-pyhocon 6/6 · py.hocon 6/6 · pyhocon 6/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-missing-include-ignored pass pass pass pass pass pass pass by default, if an included file does not exist the include statement is silently ignored, so the keys around it parse as if the line were not there 002-missing-include-is-empty-object pass pass pass pass pass pass pass a missing include is treated as if the included file contained only an empty object, so an object whose only content is that include stays an empty object 003-missing-file-include-ignored pass pass pass pass pass pass pass the silent-ignore default applies to an explicit file() include as well: a file that does not exist contributes nothing and parsing continues 004-required-missing-is-error pass pass pass pass pass pass pass when the included resource name is wrapped with required(), file parsing fails with an error if the resource cannot be resolved 005-required-file-missing-is-error pass pass pass pass pass pass pass required() may also wrap the file() form, and a required file that cannot be resolved is a parse error rather than an empty object 006-required-present-is-included pass pass pass pass pass pass pass required() only makes the include mandatory; a resource that does resolve is included exactly as an unwrapped include would be include-substitution19 casesJava 16/19 · hocon2 15/19 · pulse-pyhocon 11/19 · py.hocon 14/19 · pyhocon 12/19 · rs.hocon 14/19 · zig-hocon 19/19
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-substitution-relativized-to-include-scope pass pass fail pass fail pass pass a substitution in an included file must be fixed up to be relative to the app's configuration root, so ${x} in a file included at key a becomes ${a.x} 002-relativized-path-sees-later-redefinition pass pass fail pass fail pass pass substitution happens after parsing the whole configuration, so the ${x} fixed up to ${a.x} evaluates to the root's later redefinition 42 rather than the included file's 10 003-original-path-tried-as-fallback pass pass pass pass pass pass pass it is not enough to only look up the fixed-up path; when a.x is undefined the original path x is looked up relative to the root of the including configuration 004-relativized-path-tried-before-original pass fail fail pass fail pass pass substitutions in included files are looked up first relative to the root of the included file and only second relative to the root of the including configuration, so a.x wins over x 005-neither-path-defined-is-error pass pass pass pass pass pass pass only the fixed-up path a.x and the original path x are looked up; with neither defined the substitution is undefined and the configuration is invalid 006-optional-substitution-with-neither-path-is-omitted pass pass pass pass pass pass pass an optional substitution in an included file is looked up at the fixed-up path a.x and at the original path x; with neither defined the field is omitted rather than an error 007-nested-include-relativized-through-both-levels pass pass fail pass fail pass pass the fix-up is relative to the app's configuration root, so ${x} in a file included at b inside a file included at a becomes ${a.b.x} 008-original-path-falls-back-to-environment pass fail pass pass pass pass pass an included file may intend to refer to the application's root config, for example to get a value from the environment; the original path CONFORMANCE_HOME is looked up and falls back to the environment variable 009-undefined-optional-at-fixed-up-path-falls-back fail pass pass fail pass fail pass an undefined ${?nope} means common.x.y is never created, so ${x.y} is not found at the fixed-up path and is looked up at the original path x.y. typesafe/config treats the vanished value as a miss that ends the lookup -- except in some root key orders (see 014); lightbend/config#863 falls backjava: unsupported 010-undefined-optional-at-fixed-up-path-with-no-original pass pass pass fail pass fail pass with neither common.x.y nor x.y defined, the required ${x.y} in the included file is unresolved 011-optional-reference-falls-back-too fail pass pass fail pass fail pass the optional form of 009 falls back the same way; typesafe/config leaves a out instead (lightbend/config#863)java: diverges 012-earlier-value-at-fixed-up-path-wins pass fail fail pass fail pass pass an undefined optional that would override keeps the previous value, so common.x.y is 9 and found at the fixed-up path; the original path is not consulted 013-missing-leaf-under-existing-object-falls-back pass pass pass pass pass pass pass the metamorphic twin of 009 without the optional: common.x exists, common.x.y does not, so x.y is looked up at the original path 014-unrelated-key-does-not-change-the-result fail pass pass fail pass fail pass an unrelated key that reads common.x must not change what common.a resolves to; in typesafe/config this input fails while the same input with the key named o24bbd resolves to 0 (lightbend/config#863)java: unsupported 015-undefined-at-both-paths-is-error pass pass fail fail pass fail pass when the original path is itself an undefined optional, neither lookup finds a value and the required ${x.y} is an error 016-substitution-inside-a-concatenation-is-fixed-up pass pass pass pass pass pass pass a substitution that is one piece of a concatenation in the included file is fixed up like a bare one: p.x is not set, so the original path x is used 017-substitution-inside-a-list-is-fixed-up pass pass pass pass pass pass pass a substitution that is a list element in the included file is fixed up as well 018-substitution-in-a-merge-stack-is-fixed-up pass pass fail pass fail pass pass an object merged with a substitution in the included file keeps working after the fix-up, whichever of the two is on top: q is found at p.q 019-null-at-fixed-up-path-is-found pass fail fail pass fail pass pass a.x is present, set to null, so the lookup at the fixed-up path succeeds and the original path x is not consulted include-syntax15 casesJava 15/15 · hocon2 13/15 · pulse-pyhocon 12/15 · py.hocon 13/15 · pyhocon 13/15 · rs.hocon 14/15 · zig-hocon 15/15
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-include-later-in-key-is-literal pass pass pass pass pass pass pass unquoted include has no special meaning if it is not the start of a key's path expression; it may appear later in the key, so { foo include : 42 } is equivalent to { "foo include" : 42 } 002-include-as-object-value-is-string pass pass pass pass pass pass pass unquoted include has no special meaning if it is not the start of a key's path expression; as an object value it is the string "include" 003-include-as-array-element-is-string pass pass pass pass pass pass pass unquoted include has no special meaning if it is not the start of a key's path expression; as an array element it is the string "include". the spec's example is the root array [ include ]; it is wrapped in a field here so the row does not depend on array-root support 004-no-concatenation-on-include-argument pass pass fail pass pass pass pass value concatenation is NOT performed on the argument to include; the argument must be a single quoted string, so a second quoted string after the resource name is an error whether or not the first resource exists 005-no-substitution-in-include-argument pass pass pass pass pass pass pass no substitutions are allowed in the argument to include, which must be a single quoted string; ${x} after include is an error even though x is defined, so an implementation that resolves the argument first still fails this row 006-include-argument-must-be-quoted pass pass pass pass pass pass pass if an unquoted include at the start of a key is followed by anything other than a single quoted string or the url()/file()/classpath() syntax, it is invalid and an error should be generated; the argument may not be an unquoted string 007-quoted-include-is-ordinary-key pass pass pass pass pass pass pass only unquoted include is special; quoting it gives a key that starts with the word include, so { "include" : 42 } is an ordinary field named include 008-file-argument-must-be-quoted pass pass pass pass pass pass pass the argument inside file() must be a single quoted string; an unquoted name is a syntax error 009-file-missing-close-parenthesis pass pass pass fail pass pass pass file("...") must be closed; the line ends before the parenthesis 010-required-missing-close-parenthesis pass pass pass fail pass fail pass required(...) must be closed too 011-url-argument-must-be-a-url pass pass pass pass pass pass pass the string inside url() is always an absolute URL, with a scheme; x.conf has none, so it is an error, as lightbend/config#887 settled 013-newlines-between-include-and-name pass pass fail pass fail pass pass any amount of whitespace, newlines included, may stand between include and the resource name 014-whitespace-inside-parentheses pass fail pass pass pass pass pass whitespace inside the parentheses, outside the quotes, is allowed 015-include-followed-by-separator-is-not-a-key pass pass fail pass fail pass pass an unquoted include at the start of a key is never a key; followed by a separator instead of a quoted name it is an include with an invalid argument 016-comma-separates-include-from-next-field pass fail pass pass pass pass pass an include stands where a field would, so the usual comma separates it from the field that follows key-value-separator6 casesJava 6/6 · hocon2 6/6 · pulse-pyhocon 6/6 · py.hocon 6/6 · pyhocon 6/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-equals-as-separator pass pass pass pass pass pass pass the = character can be used anywhere JSON allows :, to separate keys from values 002-omitted-separator-before-object pass pass pass pass pass pass pass if a key is followed by {, the : or = may be omitted; the spec's own example is "foo" {} meaning "foo" : {} 003-omitted-separator-unquoted-key pass pass pass pass pass pass pass the separator-omission rule before { applies to unquoted keys too, not just the quoted example in the spec 004-value-on-the-line-after-the-separator pass pass pass pass pass pass pass = stands wherever JSON allows :, and JSON allows any whitespace, newlines included, between the separator and the value 005-separator-cannot-be-omitted-before-array pass pass pass pass pass pass pass the separator may be omitted only before {; a key followed directly by [ is invalid 006-newline-between-key-and-brace pass pass pass pass pass pass pass the separator may be omitted before {, and a newline before the { is whitespace like any other between a key and its value, as JSON allows one before : list-values-from-environment-variables6 casesJava 6/6 · hocon2 1/6 · pulse-pyhocon 3/6 · py.hocon 6/6 · pyhocon 3/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-list-from-indexed-variables pass fail fail pass fail pass pass the [] suffix collects CONFORMANCE_LIST_0, CONFORMANCE_LIST_1, ... until an index is missing, into a list of strings 002-optional-list-with-no-elements-is-undefined pass fail pass pass pass pass pass with no CONFORMANCE_LIST_0 set, an optional ${?X[]} is undefined and the field is not created 003-required-list-with-no-elements-is-error pass pass pass pass pass pass pass with no CONFORMANCE_LIST_0 set, a required ${X[]} is an error 004-collection-stops-at-the-first-gap pass fail fail pass fail pass pass indices are collected from 0 until one is missing, so with _0 and _2 set the list holds only the first 005-elements-are-strings pass fail fail pass fail pass pass an element taken from the environment is a string, so 1 stays "1" 006-config-keys-are-not-collected pass fail pass pass pass pass pass the [] suffix collects environment variables only, so the config key a_0 is not gathered and the optional substitution is undefined multi-line-strings9 casesJava 9/9 · hocon2 9/9 · pulse-pyhocon 6/9 · py.hocon 9/9 · pyhocon 9/9 · rs.hocon 9/9 · zig-hocon 9/9
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-basic-triple-quote pass pass pass pass pass pass pass the three-character sequence """ starts a multi-line string whose unicode characters are used unmodified to create the string value 002-newlines-and-whitespace-preserved pass pass pass pass pass pass pass newlines and whitespace inside a multi-line string receive no special treatment and are kept in the value 003-unicode-escape-not-interpreted pass pass pass pass pass pass pass unlike JSON quoted strings, a triple-quoted string does not interpret escapes, so the six characters \u0041 stay as written rather than becoming A 004-extra-quote-becomes-part-of-string pass pass pass pass pass pass pass any sequence of at least three quotes ends the multi-line string and any extra quotes are part of the string, so HOCON works like Scala's four-character string foo" rather than Python's syntax error 005-five-closing-quotes-two-extra pass pass fail pass pass pass pass a closing run of five quotes still only needs three to end the string, so both extra quotes are appended to the string value 006-unterminated-is-illegal pass pass pass pass pass pass pass a multi-line string opened with a three-character quote sequence that never reaches a closing """ is not a legal value 007-fewer-than-three-quotes-does-not-close pass pass fail pass pass pass pass only a run of at least three quote characters ends the multi-line string, so the single embedded quote pairs around hi do not terminate it early 008-backslash-kept-literally pass pass fail pass pass pass pass a backslash sequence inside a triple-quoted string is used unmodified, not as an escape 009-six-quotes-are-an-empty-string pass pass pass pass pass pass pass the three opening quotes are followed at once by the closing run, so the string is empty numerically-indexed-objects-to-arrays7 casesJava 7/7 · hocon2 3/7 · pulse-pyhocon 3/7 · py.hocon 7/7 · pyhocon 3/7 · rs.hocon 7/7 · zig-hocon 7/7
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-object-with-numeric-keys-stays-object pass pass pass pass pass pass pass the conversion should be done lazily when required to avoid a type error, not eagerly anytime an object has numeric keys, so a bare object with numeric keys stays an object 003-concatenation-converts-numeric-object-to-array pass fail fail pass fail pass pass the conversion should be done in a concatenation when a list is expected and an object with numeric keys is found, so the trailing object concatenates onto the array as elements x, y 004-concatenation-ignores-non-integer-keys pass fail fail pass fail pass pass the conversion should ignore any keys which do not parse as positive integers, so key "foo" is dropped and only "0" contributes to the resulting array 005-empty-object-concatenation-not-converted pass pass pass pass pass pass pass the conversion should not occur if the object is empty, so an empty object cannot satisfy a list-expected concatenation and this is illegal 006-object-without-integer-keys-not-converted pass pass pass pass pass pass pass the conversion should not occur if the object has no keys which parse as positive integers, so this object cannot satisfy a list-expected concatenation and this is illegal 007-sparse-integer-keys-sorted-and-reindexed pass fail fail pass fail pass pass the conversion should sort by the integer value of each key and then build the array; missing indices such as "1" are eliminated rather than left as gaps, so keys "0" and "2" become a two-element array 008-negative-key-ignored-in-conversion pass fail fail pass fail pass pass the conversion ignores any keys which do not parse as positive integers, and "-1" is not a positive integer, so it is dropped and only key "0" contributes omit-root-braces14 casesJava 10/14 · hocon2 12/14 · pulse-pyhocon 14/14 · py.hocon 8/14 · pyhocon 14/14 · rs.hocon 8/14 · zig-hocon 14/14
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-implicit-root-object pass pass pass pass pass pass pass if the file does not begin with [ or {, it is parsed as if enclosed with {} curly braces 002-explicit-root-object-unaffected pass pass pass pass pass pass pass the implicit-wrap rule only applies when the file does not begin with [ or {; a file already beginning with { is parsed as ordinary JSON-style object 003-missing-open-brace-with-close-brace-illegal pass pass pass fail pass fail pass a HOCON file is invalid if it omits the opening { but still has a closing }, since the curly braces must be balanced 004-empty-file pass fail pass pass pass pass pass an empty file does not begin with a square bracket or a curly brace, so it is parsed as if enclosed in {}: the empty object 005-bare-string-root-illegal pass pass pass pass pass pass pass the file does not begin with a bracket or brace, so it is wrapped in {} and becomes a key with no separator and no value 006-array-root-unaffected fail pass pass fail pass fail pass the implicit {} wrap fires only when the file does not begin with a square bracket or curly brace, so a file starting with [ keeps its array root; the include section says so outright -- 'both JSON and HOCON allow arrays as root values in a document' (the reason an included file may not be one). typesafe/config has no array-rooted config at all and rejects the document as LIST rather than object.java: unsupported 008-empty-array-root fail pass pass fail pass fail pass an empty array is still an array root, and the wrap rule looks at the opening bracket, not at whether the document has contentjava: unsupported 009-comment-before-array-root fail pass pass fail pass fail pass the {} wrap fires only for a file that does not begin with a bracket or brace, and a leading comment does not make it one -- both typesafe/config and pyhocon read the first token, not the first byte. the spec's wording is loose here; this row records the consensus readingjava: unsupported 010-blank-line-before-array-root fail pass pass fail pass fail pass leading whitespace does not trigger the {} wrap either, for the same reason as a leading commentjava: unsupported 011-trailing-array-after-root-array pass pass pass pass pass pass pass two arrays side by side concatenate when only non-newline whitespace separates them, which would make this document [1,2,3]; but a document is one array or one object at the root, and nothing in the spec lets a root value be a concatenation of two. typesafe/config, pyhocon and hocon-rs all reject it 012-close-brace-after-value-on-one-line-illegal pass pass pass fail pass fail pass a close brace with no open brace is invalid wherever it stands, on the same line as a value as much as on its own 013-open-brace-never-closed-illegal pass pass pass pass pass pass pass an opening brace with no closing brace is unbalanced 014-comment-only-file pass fail pass pass pass pass pass a file holding only a comment does not begin with a bracket or brace, so it is the empty object 015-bare-number-root-illegal pass pass pass pass pass pass pass the file does not begin with { or [, so it is read as if wrapped in braces, and a bare number there is a key with no value path-expressions20 casesJava 20/20 · hocon2 19/20 · pulse-pyhocon 16/20 · py.hocon 19/20 · pyhocon 13/20 · rs.hocon 19/20 · zig-hocon 20/20
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-quoted-dot-has-no-meaning pass pass pass pass pass pass pass a dot inside a quoted string has no special meaning as a path separator, so a quoted "a.b" key is a single-element path 002-mixed-quoted-and-unquoted-path pass pass fail pass fail pass pass foo.bar."hello.world" is a path with three elements: foo, bar, and hello.world, since unquoted dots are path separators but a quoted dot is not 003-number-then-unquoted-string pass pass pass pass pass pass pass 10.0foo is a number then unquoted string foo, giving the two-element path 10 and 0foo, because a dot inside a number still counts as a path separator 004-unquoted-string-with-dot pass pass pass pass pass pass pass foo10.0 is an unquoted string with a dot in it, giving the two-element path foo10 and 0 005-unquoted-concatenated-with-quoted-number pass pass fail fail fail fail pass foo followed by quoted "10.0" is an unquoted then a quoted string which concatenate, giving a single-element path 006-all-numeric-path pass pass pass pass pass pass pass dots in numbers count as path separators, so 1.2.3 is the three-element path with elements 1, 2, 3 008-value-concatenation-keeps-boolean pass pass pass pass pass pass pass unlike a path expression, an array or element value consisting of the single value true is a value concatenation and retains its character as a boolean 009-empty-path-element-quoted-is-valid pass pass fail pass fail pass pass a path element that is an empty string must be quoted; a."".b is a valid three-element path whose middle element is the empty string 010-consecutive-dots-invalid pass pass pass pass fail pass pass an unquoted empty path element is invalid, so a..b must generate an error 011-path-starting-with-dot-invalid pass pass pass pass fail pass pass a path that starts with a dot is invalid and should generate an error 012-path-ending-with-dot-invalid pass fail pass pass fail pass pass a path that ends with a dot is invalid and should generate an error 015-dotted-path-in-substitution pass pass pass pass pass pass pass path expressions appear in substitutions like ${foo.bar}, where the unquoted dot separates foo and bar into two path elements, so the lookup walks key foo then key bar 016-number-keeps-original-text-in-path pass pass pass pass pass pass pass a number in a path expression must keep its original string representation as it appeared in the file, so 1.10 is the two-element path 1 and 10, not the 1 and 1 a generic number-to-string conversion would give 017-whitespace-concatenation-in-path pass pass pass pass pass pass pass path expressions are syntactically identical to a value concatenation, so the whitespace-separated a b concatenates into one element and the unquoted dot then splits off c, giving the two-element path a b and c 018-quoted-element-in-substitution-path pass pass pass pass pass pass pass path expressions appear in substitutions as well as keys, and inside quoted strings a dot has no special meaning there either, so ${foo."bar.baz"} looks up key foo then the single key bar.baz 019-empty-element-in-substitution-path-invalid pass pass fail pass fail pass pass an unquoted empty path element is invalid in a substitution just as in a key, so ${a..b} must generate an error rather than collapsing to ${a.b} (a.b is defined so a lenient implementation would produce a value and fail) 020-negative-leading-dot-key-is-a-path pass pass pass pass pass pass pass a dot in a key separates path elements whether or not the text around it would be a number, so -.33 names - then 33; numbers in paths keep their original text 021-quoted-dot-in-substitution-path-is-literal pass pass pass pass pass pass pass inside quotes a dot is not a separator, so ${"b.c"} names one field called b.c, which does not exist; it does not reach b's field c 022-keyword-in-substitution-path-is-a-string pass pass pass pass pass pass pass a path expression is always a string, so true in a substitution names the key true rather than a boolean 023-substitution-in-dotted-key-illegal pass pass pass pass pass pass pass a key is a path expression, and a path expression may not contain a substitution, not even as a later element of a dotted key paths-as-keys11 casesJava 10/11 · hocon2 10/11 · pulse-pyhocon 9/11 · py.hocon 11/11 · pyhocon 9/11 · rs.hocon 11/11 · zig-hocon 11/11
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-two-element-path-expands pass pass pass pass pass pass pass a key that is a multi-element path expands to a nested object per element, the last element combined with the value becomes a field in the most-nested object: foo.bar:42 is equivalent to foo{bar:42} 002-three-element-path-expands pass pass pass pass pass pass pass a three-element path key expands through two levels of nesting: foo.bar.baz:42 is equivalent to foo{bar{baz:42}} 003-merge-sibling-paths pass pass pass pass pass pass pass the objects created by expanding path keys are merged in the usual way, so a.x:42, a.y:43 is equivalent to a{x:42,y:43} 004-whitespace-in-key pass pass pass pass pass pass pass because path expressions work like value concatenations, whitespace is allowed in keys: a b c:42 is equivalent to "a b c":42 005-unquoted-true-key-becomes-string pass pass pass pass pass pass pass path expressions are always converted to strings, so the unquoted boolean-looking key true:42 is "true":42 006-unquoted-number-key-becomes-string pass pass pass pass pass pass pass path expressions are always converted to strings, so the unquoted numeric key 3:42 is "3":42 007-decimal-key-splits-on-dot pass pass pass pass pass pass pass a dot in an unquoted key is a path separator even when it looks like a decimal number, so 3.14:42 is "3":{"14":42} 008-include-cannot-begin-key fail pass fail pass fail pass pass the unquoted string include may not begin a path expression in a key: where an object key is expected, include is not read as a key at all and must be followed by a quoted resource name, so include.foo is an error. typesafe/config recognises the keyword only as a bare include token -- include.foo tokenizes as one unquoted string, so it is accepted as the path include.foo, while include = 42 is rejected under the very rule the spec states.java: lenient 009-include-not-at-start-is-ordinary pass fail fail pass fail pass pass only the unquoted string include that begins a path expression in a key is special: include later in a key (foo.include) and a quoted "include" beginning one are ordinary path elements 010-path-key-merges-with-brace-object pass pass pass pass pass pass pass the object created by expanding a path key is merged in the usual way with an object written in braces at the same key, whichever form comes first: a{x:1}, a.y:2 and b.x:1, b{y:2} are both equivalent to {x:1,y:2} 011-whitespace-around-a-dot-stays-in-the-elements pass pass pass pass pass pass pass whitespace inside a key is part of the key, and a dot splits the key into elements, so the elements here are 'a ' and ' b' with their spaces period-format7 casesJava 7/7 · hocon2 7/7 · pulse-pyhocon 7/7 · py.hocon 7/7 · pyhocon 3/7 · rs.hocon 7/7 · zig-hocon 7/7
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-bare-number-is-days pass pass pass pass pass pass pass for getPeriod(), bare numbers are taken to be in days, unlike getDuration() where bare numbers are milliseconds 002-uppercase-unit-is-illegal pass pass pass pass pass pass pass the supported unit strings for period are case-sensitive and must be lowercase, so an uppercase unit is not one of the supported strings 003-days-spellings pass pass pass pass fail pass pass exactly d, day, days are the supported unit strings for days in the period format 004-weeks-spellings pass pass pass pass fail pass pass exactly w, week, weeks are the supported unit strings for weeks 005-months-spellings pass pass pass pass fail pass pass exactly m, mo, month, months are the supported unit strings for months 006-years-spellings pass pass pass pass pass pass pass exactly y, year, years are the supported unit strings for years 007-month-m-ambiguous-with-duration-minutes pass pass pass pass fail pass pass the spec notes that getTemporal() callers should prefer mo over m for months, since m is also the duration unit for minutes and getTemporal() may return either a Duration or a Period plus-equals-field-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
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-plus-equals-desugars-to-optional-self-ref-array pass pass fail pass fail pass pass a += b transforms into a = ${?a} [b], and because the fallback is optional (${?a} not ${a}) this is legal even as the first mention of a in the file 002-plus-equals-appends-to-existing-array pass pass fail pass fail pass pass += appends an element to a previous array 003-plus-equals-errors-on-non-array-previous-value pass pass fail pass fail pass pass if the previous value was not an array, += results in an error just as the long form a = ${?a} [b] would with a non-array a 004-plus-equals-twice pass pass fail pass fail pass pass each += appends to the value the previous line left, so two of them append two elements 005-plus-equals-in-nested-object-with-substitution pass fail fail pass fail pass pass inside an object, += desugars to a self-reference by the field's full path o.a; the appended element may itself be a substitution 006-plus-equals-inside-a-list-element fail pass fail pass fail pass pass b has no previous value, so b += 1 is b = ${?b} [1] with the optional part undefined: [1]. typesafe/config refuses += inside a list because its desugared path cannot name a list elementjava: unsupported 007-plus-equals-under-a-dotted-key pass fail fail pass fail pass pass the self-reference += desugars to uses the full path of the field, o.p.a, even when the enclosing key was written as the path o.p 008-plus-equals-with-an-object-containing-a-merge fail pass fail pass fail pass pass l += v is l = ${?l} [v]; the appended object holds a merge of an object and a substitution, which resolves inside the new list. typesafe/config fails with an internal error (BugOrBroken) for the same reason as array-and-object-concatenation/018java: unsupported 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
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-isolated-self-reference-is-an-error pass pass pass pass pass pass pass in isolation, with no merges involved, a self-referential field is an error because the substitution cannot be resolved 002-self-reference-resolves-to-earlier-merged-value pass pass pass pass pass pass pass when foo:${foo} is merged with an earlier value for foo, the self-reference resolves to that overridden value, so foo ends up { a : 1 } 003-self-reference-before-any-value-is-undefined pass pass fail fail pass fail pass if the self-referential foo:${foo} comes before foo has any value, the reference is undefined, exactly as if it named a path not found in the document, so it is an error even though foo is given a value afterward 004-optional-self-reference-disappears-silently pass pass pass pass pass pass pass because the self-reference error is treated as undefined rather than an intractable cycle, the optional syntax foo:${?foo} makes the field disappear silently instead of erroring 005-substitution-hidden-by-later-non-mergeable-value-is-never-evaluated pass pass pass pass pass pass pass if a substitution is hidden by a later non-object value that could not be merged with it, it is never evaluated and no error is reported, no matter what it would have resolved to; foo ends up 42 006-self-reference-cycle-ignored-when-overridden-by-literal pass pass pass pass pass pass pass the same hiding rule applies to a self-reference cycle: once overridden by a non-mergeable literal, the initial foo:${foo} must simply be ignored, so foo ends up 42 with no error 007-self-reference-in-path-expression-resolves-to-value-below pass pass pass pass pass pass pass a self-reference resolves to the value below it even as part of a path expression, so ${foo.a} refers to { c : 1 } rather than 2, and the final merge is { a : 2, c : 1 } 008-object-may-refer-to-sibling-path-within-itself pass pass pass pass pass pass pass an implementation must allow an object to refer to a path within itself without treating it as a cycle, by resolving only the referenced field rather than recursing the whole enclosing object; bar.baz ends up 42 009-non-cycling-reference-looks-forward-across-merge pass pass pass pass pass pass pass because there is no inherent cycle here, the substitution must look forward including the field's own later merges, so bar.baz ends up 43 once foo is overridden to 43 010-mutually-referring-objects-are-not-self-referential pass pass pass pass pass pass pass mutually-referring objects should work and are not self-referential, so they look forward: bar.a ends up 4 and foo.c ends up 3 011-optional-self-reference-in-concatenation-looks-back pass pass pass pass pass pass pass an optional self-reference in a value concatenation has to look back to an undefined a, so a ends up "foo" rather than "foofoo" 012-mutual-non-self-cycle-is-unresolvable pass pass pass pass pass pass pass the spec gives bar:${foo}, foo:${bar} as an example that is not possible to resolve, since neither lazy evaluation nor looking backward breaks the cycle and neither field is optional 013-multi-step-cycle-is-invalid pass pass pass pass pass pass pass a multi-step loop across three fields (a:${b}, b:${c}, c:${a}) must also be detected as invalid, not just a direct two-field cycle 014-order-dependent-resolution-must-agree-on-one-value pass pass pass pass pass pass pass the order a and b resolve in is undefined: both 1, both 2 or an error are all allowed, but a and b must agree (a=1,b=2 is not permitted) 015-list-hidden-by-substituted-object-is-never-evaluated fail pass pass fail pass fail pass a list merges with nothing, so any later value hides it and it is never evaluated; that the hiding object arrives through ${replacement} changes nothing, since the substitution is required and so either resolves or fails on its own. typesafe/config evaluates the hidden list (lightbend/config#865)java: unsupported 016-object-hidden-by-substituted-scalar-is-never-evaluated pass pass pass pass fail pass pass ${replacement} resolves to 42, a non-object, so the earlier object is hidden and never evaluated, as with the literal in 005 (typesafe/config 1.4.5 evaluated it) 017-object-hidden-by-substituted-concatenation-is-never-evaluated pass pass pass pass fail pass pass a string concatenation is a non-object too, so it hides the earlier object (typesafe/config 1.4.5 evaluated it) 018-object-merged-with-substituted-object-is-evaluated pass pass fail pass pass pass pass a substituted object merges with the earlier object, so its fields are part of the result and ${does-not-exist} is an error 019-list-hidden-by-literal-scalar-is-never-evaluated pass pass pass pass pass pass pass the literal counterpart of 015: a literal number could not be merged with the list, so the list is never evaluated and there is no error 020-object-hidden-by-literal-list-is-never-evaluated pass pass pass pass pass pass pass a literal list is a non-object value, so it hides the earlier object and ${does-not-exist} is never evaluated 021-hidden-list-does-not-depend-on-key-order fail pass pass fail pass fail pass a reader of w.x must not change whether the hidden list is evaluated; only objects merge, so the object hides the list whatever else refers to it. typesafe/config fails here, yet resolves the same file with the reader named o24bbd (lightbend/config#865)java: unsupported 022-undefined-optional-does-not-hide pass pass fail pass pass pass pass an undefined optional that would override leaves the previous value in place, so the list is the result and ${does-not-exist} is evaluated: an error 023-append-does-not-hide pass pass fail pass fail pass pass w += 1 is w = ${?w} [1], which reads the earlier list, so ${does-not-exist} is evaluated: an error 024-substitution-target-is-fetched-lazily pass pass pass pass pass pass pass resolving ${b.c} fetches only b.c; resolving all of b first would reach ${a} and report a cycle that is not there 025-optional-two-field-cycle-is-undefined pass pass fail pass fail pass pass a genuine cycle is an error for required substitutions; with the optional syntax it is a missing value, so neither field is created 026-memoized-by-instance-not-by-path pass pass pass pass pass pass pass the text ${a} appears twice and resolves differently: to x in a's own self-reference and to the final xy in b; memoizing by path instead of by instance would give b the wrong value self-referential-substitutions5 casesJava 5/5 · hocon2 5/5 · pulse-pyhocon 5/5 · py.hocon 5/5 · pyhocon 5/5 · rs.hocon 5/5 · zig-hocon 5/5
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-path-builds-on-older-value pass pass pass pass pass pass pass a field may look up its own older value before being overridden with a new value based on it, letting path:${path}":d" extend the earlier path:"a:b:c" 002-self-referential-concatenation pass pass pass pass pass pass pass a value concatenation containing a substitution to its own field is self-referential, so ${a} looks back to the earlier x and the result is xbc 003-object-containing-self-reference-is-not-self-referential pass pass pass pass pass pass pass an object with a substitution inside it is not considered self-referential for this purpose, so a:{b:${a}} is an unbreakable cycle that must generate an error 004-array-containing-self-reference-is-not-self-referential pass pass pass pass pass pass pass an array with a substitution inside it is not considered self-referential for this purpose, so a:[${a}] is an unbreakable cycle that must generate an error 006-self-referential-array-concatenation pass pass pass pass pass pass pass the spec lists path:${path} [ /usr/bin ] among the examples of self-referential fields: a value concatenation of a substitution with an array literal is still self-referential size-in-bytes-format5 casesJava 5/5 · hocon2 5/5 · pulse-pyhocon 5/5 · py.hocon 5/5 · pyhocon 4/5 · rs.hocon 5/5 · zig-hocon 5/5
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-bare-number-is-bytes pass pass pass pass pass pass pass bare numbers are taken to be in bytes already 002-single-byte-spellings pass pass pass pass pass pass pass for single bytes, exactly B, b, byte, bytes are supported 003-powers-of-ten-suffixes pass pass pass pass pass pass pass every power-of-10 suffix and its long forms name the same unit family; one table, one rule 004-powers-of-two-suffixes pass pass pass pass fail pass pass every power-of-2 suffix and its long forms name the same unit family; one table, one rule. The E and e forms are quoted because an unquoted 5E is a number token that is not a number (see unquoted-strings/022) 019-single-letter-abbreviation-means-powers-of-two pass pass pass pass pass pass pass the single-letter abbreviations (like K) are ambiguous between powers of two and ten, and this spec follows the java -Xmx / GNU-tools precedent of mapping them to powers of two string-value-concatenation18 casesJava 13/18 · hocon2 18/18 · pulse-pyhocon 15/18 · py.hocon 18/18 · pyhocon 15/18 · rs.hocon 18/18 · zig-hocon 18/18
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-substitution-in-string-concatenation pass pass pass pass pass pass pass string value concatenation supports substitutions inside the concatenated string 002-only-simple-values-participate pass pass pass pass pass pass pass it is invalid for arrays or objects to appear in a string value concatenation, since only simple values participate 003-leading-and-trailing-whitespace-trimmed pass pass pass pass pass pass pass whitespace before the first and after the last simple value is discarded; only the whitespace between the values is kept 004-concatenation-never-spans-newline pass pass pass pass pass pass pass string value concatenations never span a newline, so a bare word on the following line is not appended to the value above it 005-array-element-string-concatenation pass pass pass pass pass pass pass a string value concatenation may appear in any place a string may appear, including array elements 006-boolean-becomes-string-in-concatenation pass pass fail pass fail pass pass true and false become the strings "true" and "false" when they participate in a concatenation 007-null-becomes-string-in-concatenation pass pass fail pass fail pass pass null becomes the string "null" when it participates in a concatenation 008-number-kept-as-written-in-concatenation pass pass pass pass pass pass pass numbers should be kept as they were originally written in the file when they participate in a concatenation, not renormalized 009-single-boolean-not-converted-to-string pass pass pass pass pass pass pass a single value is never converted to a string; true by itself must be parsed as a boolean-typed value, not a string 010-object-then-scalar-drops-the-scalar fail pass pass pass pass pass pass it is invalid for arrays or objects to appear in a string value concatenation; typesafe/config accepts this one silently and discards the trailing x, losing data with no diagnostic, while erroring on the same pair in the other orderjava: lenient 011-array-then-scalar-drops-the-scalar fail pass pass pass pass pass pass the same rule for arrays; typesafe/config again accepts and discards the trailing x, where it errors when the scalar comes firstjava: lenient 012-scalar-then-object-is-an-error pass pass pass pass pass pass pass the same rule with the scalar first, which typesafe/config does reject -- the half of the rule it enforces 014-array-then-unquoted-text-without-space fail pass pass pass pass pass pass an array may not appear in a string value concatenation, with or without whitespace between them; typesafe/config silently drops suffix (lightbend/config#862)java: lenient 015-object-then-unquoted-text-without-space fail pass pass pass pass pass pass the same rule for an object followed directly by text; typesafe/config silently drops suffix (lightbend/config#862)java: lenient 016-text-between-arrays-is-not-dropped fail pass pass pass pass pass pass text between two arrays makes a concatenation of array, string and array, which is invalid; typesafe/config drops the text and concatenates the arrays (lightbend/config#862)java: lenient 020-64-bit-number-kept-as-written pass pass pass pass pass pass pass a number in a concatenation is stringified as it appeared in the file, whatever its size 021-false-becomes-string-in-concatenation pass pass fail pass fail pass pass false becomes the string "false" in a concatenation, as true does 022-quoted-and-unquoted-strings-concatenate pass pass pass pass pass pass pass a quoted string is itself in a concatenation, and the whitespace between it and the unquoted string beside it is kept substitution-fallback-to-environment8 casesJava 8/8 · hocon2 8/8 · pulse-pyhocon 7/8 · py.hocon 8/8 · pyhocon 7/8 · rs.hocon 8/8 · zig-hocon 8/8
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-env-fallback-for-undefined-path pass pass pass pass pass pass pass a substitution whose path is not defined in the configuration falls back to an environment variable of the same name 002-required-substitution-resolved-from-env pass pass pass pass pass pass pass a required ${x} substitution that is not present in the configuration tree is searched for in the environment and resolves to the variable's value instead of being an error 003-config-value-wins-over-env pass pass pass pass pass pass pass the environment is only searched when the substitution is not present within the configuration tree, so a value set in the configuration wins over an environment variable of the same name 004-null-in-config-blocks-env-lookup pass pass fail pass fail pass pass setting a key to null in the configuration explicitly blocks looking that substitution up in the environment, so ${CONFORMANCE_X} resolves to null even though the variable is set 005-config-lookup-is-case-sensitive pass pass pass pass pass pass pass the lookup is case-sensitive all the way until the env variable fallback is reached, so a lowercase key does not shadow an uppercase substitution and the environment variable is used 006-empty-env-var-is-empty-string pass pass pass pass pass pass pass an env variable set to the empty string is kept as such, so a required substitution resolves to "" rather than being undefined 007-empty-env-var-creates-optional-field pass pass pass pass pass pass pass an env variable set to the empty string is set to empty string rather than undefined, so an optional ${?x} substitution creates the field with "" instead of dropping it 008-env-vars-always-become-strings pass pass pass pass pass pass pass environment variables always become a string value, so values that would parse as a number, a boolean or null in HOCON stay the strings "42", "true" and "null" substitutions37 casesJava 36/37 · hocon2 31/37 · pulse-pyhocon 32/37 · py.hocon 36/37 · pyhocon 28/37 · rs.hocon 36/37 · zig-hocon 37/37
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-basic-substitution pass pass pass pass pass pass pass the syntax ${pathexpression} refers to another part of the configuration tree 002-optional-substitution-syntax pass pass pass pass pass pass pass ${?pathexpression} is the optional substitution syntax; when the path is defined it resolves exactly like ${pathexpression}, keeping the value and its type 003-optional-substitution-whitespace-illegal pass pass pass pass pass pass pass the ? in ${?pathexpression} must not have whitespace before it; the three characters ${? must be exactly grouped together 004-substitution-not-parsed-in-quoted-string pass pass pass pass pass pass pass substitutions are not parsed inside quoted strings, so this stays the literal text 005-substitution-value-concatenation-unquoted pass pass pass pass pass pass pass worked example: to get a string containing a substitution, use value concatenation with the substitution in the unquoted portion 006-substitution-value-concatenation-quoted-tail pass pass pass pass pass pass pass worked example: quoting the non-substitution portion also forms a value concatenation with the substitution 007-substitution-path-absolute pass pass pass pass pass pass pass substitutions are resolved by looking up the path from the root configuration object, absolute rather than relative, so ${a} inside b finds the root a and not the sibling b.a 008-substitution-forward-reference pass pass pass pass pass pass pass substitution processing happens as the last parsing step, so a substitution can look forward in the configuration 009-substitution-latest-duplicate-key pass pass pass pass pass pass pass if a key has been specified more than once, a substitution referring to it always evaluates to its latest-assigned value 010-substitution-latest-duplicate-key-object-merge pass pass pass pass pass pass pass a substitution to a key set more than once evaluates to the merged object, not an earlier snapshot 011-undefined-required-substitution-is-error pass pass pass pass pass pass pass an undefined substitution with the ${foo} syntax is invalid and should generate an error 012-undefined-optional-substitution-field-not-created pass pass pass pass pass pass pass if an undefined ${?foo} substitution is the value of an object field, the field should not be created 013-undefined-optional-substitution-keeps-previous-value pass pass fail pass pass pass pass if an undefined ${?foo} would override a previously-set value for the same field, the previous value remains 014-undefined-optional-substitution-array-element-not-added pass pass pass pass pass pass pass if an undefined ${?foo} substitution is an array element, the element should not be added 015-undefined-optional-substitution-concatenation-string-empty pass pass pass pass pass pass pass an undefined ${?foo} in a value concatenation with another string becomes an empty string 016-undefined-optional-substitution-concatenation-object-empty pass pass pass pass pass pass pass an undefined ${?foo} in a value concatenation with an object becomes an empty object 017-undefined-optional-substitution-concatenation-array-empty pass pass pass pass pass pass pass an undefined ${?foo} in a value concatenation with an array becomes an empty array 018-two-optional-substitutions-both-undefined-field-not-created pass pass pass pass pass pass pass foo : ${?bar}${?baz} avoids creating the field only if both bar and baz are undefined 019-two-optional-substitutions-one-defined-field-created pass pass pass pass pass pass pass foo : ${?bar}${?baz} creates the field once at least one of bar or baz is defined 020-substitution-not-allowed-in-key pass pass pass pass pass pass pass substitutions are only allowed in field values and array elements, not in keys 021-substitution-not-allowed-nested-in-path-expression pass pass pass pass pass pass pass substitutions are not allowed nested inside other substitutions (path expressions) 022-substitution-preserves-type-array pass pass pass pass pass pass pass a substitution is replaced with any value type; if it is the only part of a value, the type (here an array) is preserved 024-substitution-concatenation-forms-string pass pass pass pass pass pass pass when a substitution is not the only part of a value, it is value-concatenated to form a string rather than keeping the substituted type 025-substitution-preserves-type-string-booleans-null pass pass fail pass fail pass pass a substitution is replaced with any value type and, when it is the only part of a value, the type is preserved: here string, true, false and null stay what they were rather than becoming strings 026-unterminated-substitution-is-error pass pass pass pass pass pass pass the syntax is ${pathexpression}: a substitution opened with ${ and never closed with } is not that syntax, and $ cannot start an unquoted string either, so it must be rejected 028-optional-substitution-to-null-value-is-defined pass pass fail pass fail pass pass a substitution is undefined only if it does not match any value present in the configuration, and null is among the value types a substitution is replaced with, so ${?a} with a = null is defined: the field is created with null rather than omitted 030-quoted-key-is-not-a-substitution pass pass pass pass pass pass pass substitutions are not parsed inside quoted strings, and a quoted key is a quoted string: "${a}" is the literal key ${a}, not a substitution in a key (which would be illegal) 031-whitespace-inside-braces-is-trimmed pass fail pass pass fail pass pass the path inside ${} has the syntax of an object key, and whitespace before the first and after the last element of a key is discarded, so ${ b } is ${b} 032-empty-substitution-illegal pass pass pass pass pass pass pass ${} contains no path expression at all, so it is neither a substitution nor anything else 033-path-through-a-non-object-is-undefined pass fail pass pass pass pass pass a is a number, so a.x names nothing; a required substitution to an undefined path is an error 034-optional-path-through-a-non-object-is-omitted pass fail pass pass fail pass pass the same path with the optional form: undefined, so the field is not created 035-quoted-path-element-with-space pass pass pass pass pass pass pass a substitution path has the syntax of an object key, so a quoted element may contain a space and names the field written the same way 036-empty-quoted-path-element pass pass fail pass fail pass pass an empty path element is legal when quoted, in a substitution as in a key 037-path-into-a-list-element-is-undefined pass fail pass pass fail pass pass a path names object fields only, so b.0 does not reach into the list b even though it has an element at index 0; the optional substitution is undefined and a is not created. The list is still unresolved when the lookup happens, which is what makes an implementation look inside it 038-whitespace-after-question-mark pass fail pass pass fail pass pass only whitespace before the ? is forbidden; after it the path follows and its leading whitespace is discarded as in any key 039-undefined-optional-with-number-is-a-string fail fail pass fail fail fail pass ${?x}42 is a concatenation of two parts, so the value is a string: the undefined optional becomes empty and the number keeps its written form. The spec states the empty-string rule for a concatenation 'with another string', so this row extends it to a number; typesafe/config instead drops the undefined part and keeps the lone number as a numberjava: diverges 040-null-blocks-environment-lookup pass pass fail pass fail pass pass the configuration sets CONFORMANCE_NULLED to null, so the substitution resolves to that null and the environment variable of the same name is never consulted unchanged-from-json27 casesJava 21/27 · hocon2 21/27 · pulse-pyhocon 13/27 · py.hocon 20/27 · pyhocon 13/27 · rs.hocon 18/27 · zig-hocon 27/27
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-quoted-string-json-format pass pass fail pass fail pass pass quoted strings are in the same format as JSON strings, so JSON escapes like \n, \t, \", \\ and \u unicode escapes are honored 002-string-value-type pass pass pass pass pass pass pass string is one of the possible value types 003-number-value-type pass pass pass pass pass pass pass number is one of the possible value types 004-object-value-type pass pass pass pass pass pass pass object is one of the possible value types 005-array-value-type pass pass pass pass pass pass pass array is one of the possible value types 006-boolean-value-type pass pass pass pass pass pass pass boolean is one of the possible value types 007-null-value-type pass pass pass pass pass pass pass null is one of the possible value types 008-number-format-decimal-and-exponent pass pass pass pass pass pass pass allowed number formats match JSON, including negative numbers, decimals, and exponent notation 009-number-leading-zero-illegal fail fail fail fail fail fail pass allowed number formats match JSON, and JSON forbids a zero followed by further digits; the initial number character plus the valid-in-JSON number characters after it must be parsed as a number value, so the whole token 01 has to be a number and is not one, with no fallback offered. typesafe/config parses it leniently as the number 1.java: lenient 010-negative-number-without-integer-part-illegal fail fail fail fail fail fail pass allowed number formats match JSON, and JSON requires a digit before the decimal point, so -.33 is a number token that is not a valid number. typesafe/config releases up to 1.4.9 parse it as -0.33; upstream main since lightbend/config#861 (unreleased) reads it as the unquoted string "-.33", like .33 -- still accepted, still not the error the hyphen rule asks forjava: lenient 011-negative-exponent-number-without-integer-part-illegal fail pass fail fail fail fail pass the same JSON rule with an exponent: -.33e+1 has no integer part. typesafe/config releases up to 1.4.9 parse it as -3.3; upstream main since lightbend/config#861 (unreleased) rejects it, on the reserved +java: lenient 012-number-with-trailing-dot-illegal fail fail fail fail fail fail pass JSON requires a digit after the decimal point; 1. is a number token that is not a valid number. typesafe/config reads it as 1, and lightbend/config#861 leaves it so on purposejava: lenient 013-trailing-dot-before-exponent-illegal fail fail fail fail fail fail pass the same rule with an exponent after the dot; typesafe/config reads it as -1000 (named out of scope in lightbend/config#861)java: lenient 014-negative-leading-zeros-illegal fail fail fail fail fail fail pass JSON forbids leading zeros after the minus sign as well; typesafe/config reads it as -33 (named out of scope in lightbend/config#861)java: lenient 015-remaining-json-escapes pass pass fail pass fail pass pass the JSON escapes \/ \b \f and \r are valid in a quoted string and mean slash, backspace, form feed and carriage return 016-unknown-escape-illegal pass pass fail pass fail pass pass only the JSON escape sequences exist; \q is not one of them, so the string is invalid 017-non-hex-unicode-escape-illegal pass pass fail pass fail pass pass \u must be followed by exactly four hex digits; zzzz are not hex digits 018-unterminated-quoted-string-illegal pass pass pass pass pass pass pass a quoted string that is still open at the end of the file is invalid 019-backslash-at-end-of-file-illegal pass pass pass pass pass pass pass a backslash with nothing after it is not an escape and the string is still open at the end of the file 020-unicode-escape-cut-off-by-end-of-file-illegal pass pass pass pass pass pass pass the file ends after two of the four hex digits a \u escape requires 021-raw-newline-in-quoted-string-illegal pass pass fail pass pass pass pass JSON forbids unescaped control characters in strings, and a raw newline is one; a quoted string cannot span lines 022-raw-tab-in-quoted-string-illegal pass pass fail fail fail fail pass a raw tab is a control character too, so it must be written as \t inside quotes 023-64-bit-integer pass pass pass pass pass pass pass a number beyond 32 bits is still a number and keeps its exact value 024-raw-utf8-in-quoted-string pass pass pass pass fail pass pass the file is UTF-8, so a multi-byte sequence is one character 025-nan-is-not-a-number pass fail pass pass pass pass pass NaN is not a JSON number, so the bare token is an unquoted string 026-surrogate-pair-escape pass pass fail pass fail fail pass JSON writes a character outside the BMP as two \u escapes forming a surrogate pair, which decode to one character 027-unpaired-surrogate-escape-is-kept pass pass fail pass fail fail pass quoted strings are JSON strings, and JSON's grammar accepts any \uXXXX escape, an unpaired surrogate included, so the string holds that one code unit units-format6 casesJava 6/6 · hocon2 6/6 · pulse-pyhocon 6/6 · py.hocon 6/6 · pyhocon 5/6 · rs.hocon 6/6 · zig-hocon 6/6
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-number-value-is-default-unit pass pass pass pass pass pass pass if the value is a number, it is taken to be a number in the default unit 002-quoted-string-with-no-unit-uses-default-unit pass pass pass pass pass pass pass a string value with no unit name should be interpreted with the default unit, as if it were a number 003-string-with-unit-name-specifies-interpretation pass pass pass pass fail pass pass a string value with a unit name has that name specify the value's interpretation 004-whitespace-optional-around-number-and-unit pass pass pass pass pass pass pass the units-format grammar is optional whitespace, a number, optional whitespace, an optional unit name, optional whitespace 005-unit-name-must-be-letters-only pass pass pass pass pass pass pass the unit name consists only of letters (Unicode L* categories, Java isLetter()), so a unit name with a trailing digit is not a legal units-format string 006-unit-before-number-is-illegal pass pass pass pass pass pass pass the units-format grammar puts the number before the unit name, not after, so a unit-then-number string is not a legal units-format value unquoted-strings25 casesJava 22/25 · hocon2 21/25 · pulse-pyhocon 11/25 · py.hocon 21/25 · pyhocon 16/25 · rs.hocon 21/25 · zig-hocon 25/25
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-simple-unquoted-string pass pass pass pass pass pass pass a sequence of characters outside a quoted string is a string value when it contains none of the forbidden characters 002-quoted-alternative-for-forbidden-char pass pass pass pass pass pass pass a quoted string may always be used as an alternative to write a character that is not permitted in an unquoted string, such as '@' 003-unquoted-cannot-contain-at-sign pass pass fail pass pass pass pass '@' is one of the listed forbidden characters, so it cannot appear inside an unquoted string 004-double-slash-starts-comment pass pass fail pass fail pass pass the two-character string '//' starts a comment, ending the unquoted string before it even mid-token 005-single-slash-allowed pass pass pass pass pass pass pass only the two-character string '//' is special for comments; a lone '/' is not a forbidden character and stays part of the unquoted string 006-truefoo-parses-as-true-then-foo pass fail pass pass pass pass pass worked example: truefoo parses as the boolean token true followed by the unquoted string foo, because its initial characters parse as true 007-footrue-is-one-unquoted-string pass pass pass pass pass pass pass worked example: footrue does not begin with true, false, null, or a number, so it parses as a single unquoted string 008-number-then-suffix-string pass pass pass pass pass pass pass worked example: 10.0bar is the number 10.0 followed by the unquoted string bar, since 10.0 parses as a number at the start 009-digits-not-at-start-stay-string pass pass pass pass pass pass pass worked example: bar10.0 does not begin with a digit, so the whole thing is the single unquoted string bar10.0, not a number 010-embedded-null-not-recognized pass pass pass pass pass pass pass embedded (non-initial) null, true, false, and numbers are not recognized as such: they are just part of the string, since only the leading characters are checked 011-unquoted-cannot-contain-backslash pass pass fail pass fail pass pass backslash is a forbidden character, and unquoted strings support no escaping at all, so a literal backslash requires a quoted string instead 012-unquoted-cannot-start-with-hyphen fail fail fail fail fail fail pass an unquoted string may not begin with a hyphen, and the initial number character plus any valid-in-JSON number characters that follow must be parsed as a number value -- here that is the lone '-', which is not a number. typesafe/config falls back to unquoted text and yields the string '-foo', the same leniency it shows for '1e'.java: lenient 013-negative-number-then-string pass pass pass pass pass pass pass a hyphen followed by digits begins number parsing (-1), after which foo continues as a separate unquoted string token, mirroring the 10.0bar example for negative numbers 014-hash-starts-comment-not-forbidden-char-in-string pass pass fail pass pass pass pass '#' is forbidden inside an unquoted string because it already has meaning in HOCON: it starts a comment 015-reserved-forbidden-char-no-current-meaning pass pass fail pass pass pass pass some forbidden characters, such as '!', have no meaning in HOCON today and are reserved as keywords for future extensions to the spec 016-control-char-allowed-unquoted pass pass pass pass pass pass pass unquoted strings place no restriction on control characters, other than the forbidden characters listed 017-control-char-forbidden-in-quoted pass pass fail fail fail fail pass quoted JSON strings may not contain control characters, per the JSON spec, unlike unquoted strings 018-leading-dot-is-not-a-number pass pass fail pass fail pass pass only 0-9 and - begin a number, so .33 is an ordinary unquoted string -- the reading lightbend/config#861 extends to -.33 019-dollar-must-start-a-substitution pass pass fail pass pass pass pass $ is a forbidden character in an unquoted string, and outside quotes it may only begin ${; $foo is neither 020-plus-must-be-followed-by-equals pass pass fail pass pass pass pass + is a forbidden character in an unquoted string and outside quotes only forms the += separator; a + with nothing after it, here at the end of the file, is invalid 021-plus-inside-a-number-illegal pass pass fail pass fail pass pass 1+2 begins like a number and is not one; + is a forbidden character, so the token cannot fall back to an unquoted string either 022-digit-then-exponent-letter-illegal fail fail fail fail fail fail pass 1e begins with a digit, so the run of number characters 1e must be parsed as a number, and it is not one; typesafe/config falls back to the unquoted string "1e", the same leniency it shows for -foojava: lenient 023-unquoted-string-ends-at-end-of-file pass pass pass pass pass pass pass an unquoted string runs to a forbidden character or whitespace, and the end of the file ends it as well; no final newline is needed 024-escape-sequence-in-unquoted-string-illegal pass pass fail pass fail pass pass unquoted strings support no escaping, so \u00e9 is not decoded, and the backslash it needs is forbidden in an unquoted string; the quoted "caf\u00e9" is how to write it 025-digit-initial-invalid-number fail fail fail fail fail fail pass an unquoted string may not begin with the digits 0-9, and the initial number character plus any valid-in-JSON number characters that follow it must be parsed as a number value -- 1.2.3 is that run, and it is not a number, so the input is rejected rather than read as a string (the everyday shape is an address like 127.0.0.1); typesafe/config and pyhocon both fall back to the unquoted string "1.2.3"java: lenient value-concatenation2 casesJava 2/2 · hocon2 2/2 · pulse-pyhocon 2/2 · py.hocon 2/2 · pyhocon 2/2 · rs.hocon 2/2 · zig-hocon 2/2
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-simple-values-concatenate-to-string pass pass pass pass pass pass pass if all the values are simple values they are concatenated into a string 004-string-concatenation-allowed-in-field-keys pass pass pass pass pass pass pass string value concatenation is allowed in field keys, in addition to field values and array elements whitespace13 casesJava 13/13 · hocon2 12/13 · pulse-pyhocon 3/13 · py.hocon 10/13 · pyhocon 4/13 · rs.hocon 10/13 · zig-hocon 13/13
case Java hocon2 pulse-pyhocon py.hocon pyhocon rs.hocon zig-hocon rule 001-nonbreaking-space-as-whitespace pass pass fail pass fail pass pass a nonbreaking space (0x00A0) is a Unicode Zs separator and must be treated as whitespace 002-bom-at-start-is-whitespace pass pass fail pass fail pass pass the BOM (0xFEFF) must be treated as whitespace, not as part of the key 003-unicode-line-paragraph-separator pass pass fail fail pass fail pass the line and paragraph separators U+2028 and U+2029 are whitespace but not newlines, so they do not separate two fields: 1 b concatenates and the second = is a stray token 004-vertical-tab-form-feed-whitespace pass pass fail pass fail pass pass vertical tab (0x000B) and form feed (0x000C) are whitespace characters 005-ascii-separator-control-chars pass pass pass fail pass fail pass the ASCII separator controls 0x1C-0x1F are whitespace but not newlines, so they do not separate two fields 006-special-nonbreaking-spaces pass pass fail pass fail pass pass the spec names figure space (0x2007) and narrow no-break space (0x202F) explicitly among the nonbreaking spaces treated as whitespace 007-tab-is-whitespace pass pass pass pass pass pass pass a tab is whitespace around a key and a separator 008-crlf-line-endings pass pass pass pass pass pass pass a carriage return is whitespace and the line feed beside it is the newline, so CRLF line endings separate fields; if the CR were text the first value would be "1\r" 009-lone-carriage-return-is-not-a-newline pass fail fail fail fail fail pass a lone carriage return is whitespace but not a newline, so 1 b concatenates and the second = is a stray token 010-line-separator-is-whitespace pass pass fail pass fail pass pass U+2028 around the separator is whitespace, not part of the key or the value 011-ascii-separator-controls-are-whitespace pass pass fail pass fail pass pass the file, group, record and unit separators 0x1C-0x1F are whitespace around keys and separators 012-ideographic-space-is-whitespace pass pass fail pass fail pass pass the ideographic space U+3000 is a Zs space separator and so whitespace, like every member of the category 013-bom-inside-the-document-is-whitespace pass pass fail pass fail pass pass the BOM is whitespace wherever it appears, not only as a file-start marker