TOML is a better format for configuration files IMO, if not for many reasons, primarily because TOML accepts comments.
However, one annoying thing for TOML was the lack of schema, and the reliance on JSON Schema for that. Which I decided to tackle years ago when I started the TOML Schema project. In the past few months I leveraged code agents to take to the finish line and got something compelling: tomlschema.org
Interesting. The website says that it's "validation-focused". I don't know how that plays out for TOML, but the lack of focus in json schema to define an interface in addition to validation can be very frustrating. Most of the time I would rather trade off some expressitivity in terms of validation in favor of having a defined typed interface into the validated data.
It would be too easy to have every option in a config file documented in comments in the default config file. Think of the poor tutorial industry. It may even make chatbots less useful.
JSON is obviously a poor choice. It's job is to interchange data, produced and parsed by computers.
ESR had the idea of writing configuration in English. It didn't gain traction at the time, but we have LLMs now. It might be a good idea to revisit the idea of accepting plain english. The LLM output could then be any format that's easy and unambiguous to parse.
Using an LLM to parse your application's config is a bit like using an F1 racecar to drive from your house to the bicycle in your attached garage. Getting config into the application should to be fast, light (lighter than the rest of the application), and deterministic. And if the LLM's output is easy and unambiguous to parse, it's easier to simply use that as the original config file.
I totally get that. The application shouldn't use an LLM to parse the config file, but it would be useful to write one. An LLM could supply a diff to make the changes you want without learning the config-language-du-jour.
1: Because JSON is a very easy serialization format to work with. I suspect these tools all have configuration classes / objects that are deserialized straight from the config file.
2: I suspect a lot of these tools are written in Javascript, and in Javascript JSON is very easy to work with.
In C#, I find "binary serialization" much easier to program with than JSON.
Then again, the resulting blobs require specialized tooling to read, and they're very hard to work with in other programming languages.
---
But, jokes aside:
I find CSV is great for "rows" because it doesn't repeat field names for every object.
I really like XML when each tag is an object, and fields are attributes. Most people don't understand this and end up making a hug mess; and some serializers do this by default too. IMO, this is why JSON became much more popular.
because JSON is in the following sense "universal":
every format/structure that has numbers, strings, booleans, null/none, finite lists of items, and string-indexed records of items already contains JSON
and that's pretty much the barebones you need for a configuration language
That is not quite true, because you have to define "numbers" and "strings" more specifically; JSON uses Unicode strings, and how numbers work depends on the implementation (but are generally finite 64-bit IEEE floating point; JSON does not have Infinity and NaN).
ASN.1 is almost a superset, but lacks a key/value list type; I had made some nonstadard extensions called ASN.1X and one of my new types is a key/value list type, so that makes the data types of ASN.1X a superset of JSON (although the format is different, it makes that all JSON data can be represented using DER if the nonstandard key/value list type of ASN.1X is used).
I don't like JSON that much because of its many problems (some are problems with syntax, others are problems with the data), so I use ASN.1X instead, for my own stuff (but I also deal with JSON because it is common enough).
Do you have the details of ASN.1X posted somewhere? Would be interesting to see what you've done with it, particularly after the ASN.1 standards folks came up with a mechanism that requires you rebuild your brain inside out in order to understand it.
I do not use the ASN.1 schema format, and have not written a specification for how the new ASN.1X features would be used in the ASN.1 schema format, although someone who is interested to do so might be able to help to write such a thing.
ASN.1X is mostly just a list of additional types, although there is also another serialization format called SDER which is between BER and DER (any valid DER is also valid SDER and any valid SDER is also valid BER), and is intended for when you do not quite need a canonical form but still want the simplicity of DER; one of the things that it allows is overlong length encodings (which is useful when the encoder wants to encode items to a file individually but then go back to encode the length afterward).
The additional types include:
- UTF-16 string: Same as BMP string but non-BMP characters are also allowed (as surrogate pairs). The type number is the same as BMP string.
- OBJECT IDENTIFIER RELATIVE TO: Either a absolute or a relative object identifier; what it is relative to might be either fixed or given elsewhere in the data, depending on the schema. This is equivalent to a type given in the appendix of the official specification of ASN.1, except that it is now a standardized type, and the canonical form (even in SDER, so that a reader does not need to check for both cases) is required to use the relative format if possible.
- BCD string (64): A string of 4-bit characters, with the high nybble first in each byte. The characters come from the character set 0 1 2 3 4 5 6 7 8 9 * # + - . space and it should be padded with a space on the end if necessary.
- PC string (65): A string of characters in the PC character set (or a related character set in some cases). Note that control characters can also be used as graphic characters.
- TRON string (66): A string of TRON characters, encoded as TRON-8.
- Key/value list (67): A set of keys (without duplicates) with associated values. The keys and values can be any type allowed by the schema. In canonical form, they must be sorted by keys in the same order that a SET is sorted (but the values are kept with the corresponding keys). (This is the only one of these nonstandard types which is used for representing JSON data; all of the other JSON types correspond to standard ASN.1 types.)
- Out of band (72): The format and usage of this type depends on the communication channel being used, and is intended for including things inside of the ASN.1X data which is separate from the ASN.1X data, such as file descriptors. This type is not intended for storage in files, and some programs that relay messages may need special handling of this type if it is used (for this reason, it should not be IMPLICIT).
- Reference (74): A reference to another node within the same file (schemas may restrict which nodes can be referenced). The encoding is like a relative OID but the first number is how many times to go to the parent node (0 means the reference itself), and then the rest of the numbers are the zero-based index into the node referenced by the previous number.
- Identified data (75): Contains a set, followed by the payload (of any type), followed by an optional key/value list where the keys are OIDs. The set is used to identify the format, and it can contain OIDs, object descriptors (only expected to be used in error messages and stuff like that), and sequences who first element is a OID; and should not have duplicates. This type may be used as the top-level type in a file in order to identify the file format, but can also be used inside of the file in case the existing types are considered to be insufficient for this purpose.
- Rational number (76): Contains two integers, being the numerator and denominator. The denominator must be greater than zero. If it is canonical form, then it must be lowest terms.
- Translation list (77): A key/value list where the keys specify the languages (null means the default), and is expected to be used where it could be replaced by the appropriate value according to the l10n.
A lot of that is already in ASN.1, for example for UTF-16 you've already got UTF-8 and given that even Microsoft have abandoned BMP strings I doubt any attempt to reintroduce it will get much traction, relative OIDs already exist, BCD strings are just constrained PrintableStrings and in any case UTF-8 won for all of the string types, Reference sounds like an EXTERNAL, OOB sounds like an ANY DEFINED BY, etc.
> for UTF-16 you've already got UTF-8 and given that even Microsoft have abandoned BMP strings I doubt any attempt to reintroduce it will get much traction
This is not reintroducing anything; the BMP type is already there, although its meaning is expanded (the existing BMP type is effectively a constrained subtype of UTF-16). Although most applications probably will not use UTF-16, it might sometimes be useful in some applications where it is more useful to store UTF-16 instead of converting to/from UTF-8.
> relative OIDs already exist
Yes, although I have given a standardized name and semantics to something that is allegedly already a common use (and is one that I often use in my own projects), which the official specification from ITU admits. It uses the existing OID and relative OID types (and the same type numbers as them), and is like a CHOICE between them (implementations may treat it as such).
> BCD strings are just constrained PrintableStrings
The abstract meaning matches that of constrained Visible (not Printable) strings, but the encoding is more compact.
> UTF-8 won for all of the string types
Although it is common (and some other formats don't support other string types), I disagree, and I think that one character set cannot be useful for all purposes, and furthermore that Unicode is not that good and has many problems.
> Reference sounds like an EXTERNAL, OOB sounds like an ANY DEFINED BY
Which is better than the all too common middle ground: writing something once and then not maintaining it. I've been burned many times reading some documentation and thinking I understood until I discovered the code has changed since and the documentation is now wrong.
I'm in favor of documentation. I write it. I get people pointing at what I've written as examples of what everybody should do. However it is a lot of work. I'm constantly looking things over to be sure it still makes sense. I often wonder if it is really worth it. I hope you follow my example and write documentation, but it better feel like a lot of work.
I don't know about others, but I use JSON because it's in the standard Go library, and most of the times, I rather use something weird than add a dependency.
For Caddy we chose JSON because it's fairly universal, maps nearly 1:1 with Go structs (useful for initializing an extensible server), and nearly everything else compiles to JSON one way or another, so you can choose your own config format, really: https://caddyserver.com/docs/config-adapters
I wish everyone would embrace Amazon's Ion format. Of the data serialization formats it seems the most reasonable with the exception that it can encode S-expressions (so like having data serialization within your data serialization), so it is a bit excessive.
> Why do so many tools have JSON config files‽ Commenting why options have been set the way they have is just such a basic thing to want to do… Why do tech people have such an aversion to writing things down⁇
That's the whole post. First I don't know why this is posted on HN. Second I don't see how "JSON config" and "writing things down" are the two opposite options.
> That's the whole post. First I don't know why this is posted on HN. Second I don't see how "JSON config" and "writing things down" are the two opposite options.
The "writing things down bit" is in the context of commenting. I mean, it's literally in the same paragraph.
I really like the EDN[1][2] (extensible data notation) format that is used mostly by Clojure for config and data transfer/exchange. It is so much more expressive than JSON and supports a well thought-out set of elements for the most common data structures.
In my opinion, JSON is not the best format and has some problems. Lack of comments is one of the reasons, as they mention in there. Another is the lack of trailing commas (optional trailing commas would be useful for manually written files). However, these are problems with the syntax, and there are also problems with the data, such as a lack of a proper integer type, lack of Infinity and NaN, lack of support for character sets other than Unicode (and ASCII), lack of proper octet string type, etc.
1. JSON parsers are available at your corner convenience store.
2. The format is too simple to have ambiguous behavior. No weird “yes” means true, 0 means false, odd rules about comments, blah blah blah. It’s hard to fuck up JSON (but obviously not impossible if you get creative).
3. It errors out early in the parsing if you mess it up.
4. Its data types are present in more or less any language.
5. Most configs are just key/value. JSON does this reasonably well.
6. It is easy to generate and validate JSON documents. For some use cases you don’t need a library (though you should use one).
7. There is only one way to do anything (sane).
8. Everyone is familiar with it.
9. It is dynamic. You do not need to pre-define your sections or keys ahead of time.
10. It can easily be auto formatted to look good with zero risk of changing semantics.
11. Data stores often natively support storing and querying JSON objects.
This is like asking why people use their keys to open packages: it might not be the right tool for the job but it’s hard to mess up, is the closest thing to you that can get the job done, and everyone (with functioning hands/fingers) can do it with little issue.
I am also certain there is some small but non-zero percentage of people who do it simply because everyone else moralizes about not doing it. Spite is a powerful thing.
41 comments
[ 0.23 ms ] story [ 4.5 ms ] threadHowever, one annoying thing for TOML was the lack of schema, and the reliance on JSON Schema for that. Which I decided to tackle years ago when I started the TOML Schema project. In the past few months I leveraged code agents to take to the finish line and got something compelling: tomlschema.org
It should really define sections as something different from dot-separated identifier groups.
ESR had the idea of writing configuration in English. It didn't gain traction at the time, but we have LLMs now. It might be a good idea to revisit the idea of accepting plain english. The LLM output could then be any format that's easy and unambiguous to parse.
1: Because JSON is a very easy serialization format to work with. I suspect these tools all have configuration classes / objects that are deserialized straight from the config file.
2: I suspect a lot of these tools are written in Javascript, and in Javascript JSON is very easy to work with.
In C#, I find "binary serialization" much easier to program with than JSON.
Then again, the resulting blobs require specialized tooling to read, and they're very hard to work with in other programming languages.
---
But, jokes aside:
I find CSV is great for "rows" because it doesn't repeat field names for every object.
I really like XML when each tag is an object, and fields are attributes. Most people don't understand this and end up making a hug mess; and some serializers do this by default too. IMO, this is why JSON became much more popular.
[section_name]
key=value
key=value
It fits into the poor choices we made, <-- Parse error
every format/structure that has numbers, strings, booleans, null/none, finite lists of items, and string-indexed records of items already contains JSON
and that's pretty much the barebones you need for a configuration language
(of course you can argue about the syntax)
ASN.1 is almost a superset, but lacks a key/value list type; I had made some nonstadard extensions called ASN.1X and one of my new types is a key/value list type, so that makes the data types of ASN.1X a superset of JSON (although the format is different, it makes that all JSON data can be represented using DER if the nonstandard key/value list type of ASN.1X is used).
I don't like JSON that much because of its many problems (some are problems with syntax, others are problems with the data), so I use ASN.1X instead, for my own stuff (but I also deal with JSON because it is common enough).
ASN.1X is mostly just a list of additional types, although there is also another serialization format called SDER which is between BER and DER (any valid DER is also valid SDER and any valid SDER is also valid BER), and is intended for when you do not quite need a canonical form but still want the simplicity of DER; one of the things that it allows is overlong length encodings (which is useful when the encoder wants to encode items to a file individually but then go back to encode the length afterward).
The additional types include:
- UTF-16 string: Same as BMP string but non-BMP characters are also allowed (as surrogate pairs). The type number is the same as BMP string.
- OBJECT IDENTIFIER RELATIVE TO: Either a absolute or a relative object identifier; what it is relative to might be either fixed or given elsewhere in the data, depending on the schema. This is equivalent to a type given in the appendix of the official specification of ASN.1, except that it is now a standardized type, and the canonical form (even in SDER, so that a reader does not need to check for both cases) is required to use the relative format if possible.
- BCD string (64): A string of 4-bit characters, with the high nybble first in each byte. The characters come from the character set 0 1 2 3 4 5 6 7 8 9 * # + - . space and it should be padded with a space on the end if necessary.
- PC string (65): A string of characters in the PC character set (or a related character set in some cases). Note that control characters can also be used as graphic characters.
- TRON string (66): A string of TRON characters, encoded as TRON-8.
- Key/value list (67): A set of keys (without duplicates) with associated values. The keys and values can be any type allowed by the schema. In canonical form, they must be sorted by keys in the same order that a SET is sorted (but the values are kept with the corresponding keys). (This is the only one of these nonstandard types which is used for representing JSON data; all of the other JSON types correspond to standard ASN.1 types.)
- Out of band (72): The format and usage of this type depends on the communication channel being used, and is intended for including things inside of the ASN.1X data which is separate from the ASN.1X data, such as file descriptors. This type is not intended for storage in files, and some programs that relay messages may need special handling of this type if it is used (for this reason, it should not be IMPLICIT).
- Reference (74): A reference to another node within the same file (schemas may restrict which nodes can be referenced). The encoding is like a relative OID but the first number is how many times to go to the parent node (0 means the reference itself), and then the rest of the numbers are the zero-based index into the node referenced by the previous number.
- Identified data (75): Contains a set, followed by the payload (of any type), followed by an optional key/value list where the keys are OIDs. The set is used to identify the format, and it can contain OIDs, object descriptors (only expected to be used in error messages and stuff like that), and sequences who first element is a OID; and should not have duplicates. This type may be used as the top-level type in a file in order to identify the file format, but can also be used inside of the file in case the existing types are considered to be insufficient for this purpose.
- Rational number (76): Contains two integers, being the numerator and denominator. The denominator must be greater than zero. If it is canonical form, then it must be lowest terms.
- Translation list (77): A key/value list where the keys specify the languages (null means the default), and is expected to be used where it could be replaced by the appropriate value according to the l10n.
- Scien...
This is not reintroducing anything; the BMP type is already there, although its meaning is expanded (the existing BMP type is effectively a constrained subtype of UTF-16). Although most applications probably will not use UTF-16, it might sometimes be useful in some applications where it is more useful to store UTF-16 instead of converting to/from UTF-8.
> relative OIDs already exist
Yes, although I have given a standardized name and semantics to something that is allegedly already a common use (and is one that I often use in my own projects), which the official specification from ITU admits. It uses the existing OID and relative OID types (and the same type numbers as them), and is like a CHOICE between them (implementations may treat it as such).
> BCD strings are just constrained PrintableStrings
The abstract meaning matches that of constrained Visible (not Printable) strings, but the encoding is more compact.
> UTF-8 won for all of the string types
Although it is common (and some other formats don't support other string types), I disagree, and I think that one character set cannot be useful for all purposes, and furthermore that Unicode is not that good and has many problems.
> Reference sounds like an EXTERNAL, OOB sounds like an ANY DEFINED BY
I don't think so. It seem like different to me.
Because XML hasn't been cool for about two decades. And suggesting .ini would you laughed out of the room into retirement.
No, we're just lazy.
I'm in favor of documentation. I write it. I get people pointing at what I've written as examples of what everybody should do. However it is a lot of work. I'm constantly looking things over to be sure it still makes sense. I often wonder if it is really worth it. I hope you follow my example and write documentation, but it better feel like a lot of work.
https://en.wikipedia.org/wiki/Ion_(serialization_format)
That's the whole post. First I don't know why this is posted on HN. Second I don't see how "JSON config" and "writing things down" are the two opposite options.
The "writing things down bit" is in the context of commenting. I mean, it's literally in the same paragraph.
[1]: https://github.com/edn-format/edn
[2]: https://en.wikipedia.org/wiki/Clojure#Extensible_Data_Notati...
2. The format is too simple to have ambiguous behavior. No weird “yes” means true, 0 means false, odd rules about comments, blah blah blah. It’s hard to fuck up JSON (but obviously not impossible if you get creative).
3. It errors out early in the parsing if you mess it up.
4. Its data types are present in more or less any language.
5. Most configs are just key/value. JSON does this reasonably well.
6. It is easy to generate and validate JSON documents. For some use cases you don’t need a library (though you should use one).
7. There is only one way to do anything (sane).
8. Everyone is familiar with it.
9. It is dynamic. You do not need to pre-define your sections or keys ahead of time.
10. It can easily be auto formatted to look good with zero risk of changing semantics.
11. Data stores often natively support storing and querying JSON objects.
This is like asking why people use their keys to open packages: it might not be the right tool for the job but it’s hard to mess up, is the closest thing to you that can get the job done, and everyone (with functioning hands/fingers) can do it with little issue.
I am also certain there is some small but non-zero percentage of people who do it simply because everyone else moralizes about not doing it. Spite is a powerful thing.