Ron: Rusty Object Notation
github.com
github.com
The toughest competition in the universe is a "good enough" competitor.
Cool indeed. I wonder how you can supply it with types from the program that consumes the config.
My Gradle config is in Kotlin these days. Kotlin, besides being a full blown prog lang, has nice features for config specs (map/list literals, typed, eDSL syntax). Though it is an enormous dependency (way to big for a project that just needs a config file format).
If your language has an OpenAPI library, you can create a hello-world web app that exposes your configuration object as an HTTP endpoint, and generate Dhall types from that.
But this turns out to support OP's point. JSON is good enough, and there are many alternatives that we are debating and don't agree on.
So if you have to pick something, and I have to pick something, since we don't agree, we will fallback on JSON.
Where JSON mainly fails as config format is lack of comments. Comment provide help and structure when editing. While allowing to toggle functionality. Which is where trailing commas help.
Use a prettifier plugin in your editor or squint, silly human. Trailing-comma-on-write-annoyance-adjuster, bada-bing, bada-boom. Most of the peeves can be hidden with good tooling. This Ron thing makes sense because of Serde not because it is prettier.
Here's a rundown of HOCON's main features: https://github.com/lightbend/config#features-of-hocon
JSON is "secretly" being replaced by JSON5 in quite a lot of contexts. So I'm not sure that this is entirely accurate. The same was said about XML a long time ago and people claimed that XML will never be replaced. Yet today XML is barely used any more. Likewise TOML is showing up in more and more places were previously INI or YAML was the standard.
TOML for example is extensively used in the Rust world, but if you're doing PHP, you're much more likely to deal with XML or JSON than TOML on a daily basis.
Formats come and go over time, surely JSON will as well.
JSON5 parsing is unfortunately slower than JSON. Not intrinsically, but JSON.parse/stringify are both natively implemented and highly optimized. If browsers started supporting it, it would take off overnight. I can't tell you how often I've wanted to put comments or not worry about removing the last trailing comma from a list.
It would win for some of the same reasons HTML5 beat XHTML. Ease of use and implicit rules beat pedantic strictness any day of the week. "Programmers are lazy."
Kudos to Asheem and all JSON5 team for building a great product while ignoring the haters [1]
[1] https://aseemk.substack.com/p/ignore-the-f-ing-haters-json5
Big Tech is based mostly on 2 related scams: planned obsolescence and grotesquely and absurdely massive and complex open source software/standards/protocols.
To protect us against them, is to go to simple but able to do a good enough job and stable in time protocol/standard/open source software/etc. You will need lawyers.
This is common sense, but many seem to lose it... more or less sincerely...
The reason I think so is that so many JSON users are no longer actually using JSON. Although there are apparently several competing implementations still vying for the name "JSONC", one of those is the non-JSON format that allows comments used by VS Code (among many others), and it is spreading (often inadvertently).
This weakens JSON by diluting the meaning of the term (many users of this "JSON plus comments" think they are using JSON). The end result might be that we standardize on some "JSON+" type name, or that the meaning of "JSON" itself is diluted to include such superior mutations.
Either way is pain in the ass, though, because JSON is a human-readable format (I mean, not really, but in practice), but it is also a machine-to-machine format, so deviation from the spec is certainly unhelpful there.
But we got rid of Latin-1 and SJIS (for the most part), and I have hope that in the fullness of time, we'll get rid of JSON, too. (Probably not with RON, though.)
P.S. Nrwl guys, JSON config files are literally the most painful part of using Nx, and you should consider just using TypeScript, but if not, "VS Code style comments-enhanced JSON" would still be a massive improvement. :-D
But I have tried lots of alternatives and none have as good tooling except maybe XML. Especially for schemas, which I think is really important.
I want JSON5 to succeed but there's no good IDE support really.
I also tried Dhall, which looks promising except they've imported all the super weird stuff from functional languages that are just going to put people off (e.g. the weird leading commas, and very odd way of declaring functions).
I did actually use this in a project because it differentiates between nesting of Option. Particularly we were having some confusion of Some(None) and None and this helped our tests catch more issues. In production we used JSON. Serde makes it trivial for the same code to switch between formats.
This looks like a nice replacement because it looks like the debug logs of my types. Plus it lets me specify numbers as hex.
Suddenly sum types become conveniently easy to express and understand, which is always incredibly awkward in JSON.
Besides that: tuples, tuple structs, comments, properly handling NaN, optional.
Really hope something like this takes over the role of JSON/yaml soon.
But first a quiz of two YAML documents.
Yaml#1
:
Yaml#2 :
Is any of these two docs valid. If yes, why, if not, why not.Edit: To be succinct (assuming Yaml 1.2), what is wrong with YAML are string types, weird quoting rules, attempts to be JSON, really bizzare user friendliness rules causing really bizzare edge cases, lookahead & lookbehind being essential for Yaml, very context sensitive, and complicated to the Xth degree (where X approaches infinity).
Yaml feels like it can't decide whether it wants to be whitespace sensitive (human readable but not editable) or whitespace insensitive (mostly human readable and human editable).
Goes to show how surprising YAML is, which is it's worst property.
JS conflating the hashmap and record/struct is a big loss, probably fixed by TS. it does show JS's dedication to its radical dynamic typing discipline (or lack thereof).
Personally, I really hope Human JSON, https://github.com/tailscale/hujson , will get more popular!
https://www.freecodecamp.org/news/comments-in-json/
They were removed from the standard because some were using them for custom parser directives.
Who wants to type `{name: "pveierland"}` when you could just do `{name "pveierland"}`?!
`{num 5, val 4}` looks fine to me, but we can do even better! We already know objects/maps are always in pairs, so we don't really need that comma either. Just do `{num 5 val 4}` and we save yet another unnecessary characters.
Of course, I didn't come up with this format myself, what I actually want JSON to be is EDN (https://github.com/edn-format/edn) which is a standalone format but also directly used in Clojure, so it already exists inside a programming language and works very well. There keys are strings though, so you example would end up being `{"num" 5 "val" 5 "person" var}`, where commas are optional.
Personally, I much prefer the latter of these two:
JSON:
[
{
"id":"0003",
"type":"donut",
"name":"Old Fashioned",
"ppu":0.55,
"batters":{
"batter":[
{
"id":"1001",
"type":"Regular"
}
]
},
"topping":[
{
"id":"5004",
"type":"Maple"
}
]
}
]
EDN [{"id" "0003"
"type" "donut"
"name" "Old Fashioned"
"ppu" 0.55
"batters" {"batter" [{"id" "1001"
"type" "Regular"}]}
"topping" [{"id" "5004"
"type" "Maple"}]}]
Strings (like `"id"`) are usually keywords (like `:id`) in EDN, but I still find it much easier to parse and understand. I've read a lot more JSON over the years than I've read EDN, so I don't think it's a "used to" feeling I have either.EDN
[{ "0003"
"type" "donut"
"name" "Old Fashioned"
"ppu" 0.55
"batters" {"batter" [{"id" "1001"
"type" "Regular"}]}
"topping"
}]
This issue occurs even if you use the comma syntax: {:id 3, :type, :ppu 0.55, :topping}
parses to: {
:id 3,
:type :ppu,
0.55 :topping,
}
The worst thing a human-readable data format can have is syntax that looks like it's redundant (and capable of error-detection), but is actually ignored.Check this[0] out:
[
id [0003]
type [donut]
name [Old Fashioned]
ppu [0.55]
batters [
batter [
[
id [1001]
type [Regular]
]
]
]
topping [
[
id [5004]
type [Maple]
]
]
]
Now if we delete `id` we will get a syntax error. And yet no commas, no colons, no quotemarks! Only square brackets. Minimal redundancy.For a bit more error-checking-thru-redundancy we could analyze indentation (one reason why I recommend C-style rather than Lisp-style formatting) and warn if we detect any inconsistencies.
Ergo: through design magic we can get rid of a lot of the redundancy and increase desirable properties, without trading off much of the positive side.
NB for a machine we could compact the above into:
[id[0003]type[donut]name[Old Fashioned]ppu[0.55]batters[batter[[id[1001]type[Regular]]]]topping[[id[5004]type[Maple]]]]
[0] More on that here: https://news.ycombinator.com/item?id=35675811With your argument, JSON is equally bad because you could have {"name": "wizzwizz4", "age": 16} and ooops, some things got deleted and now the structure ends up being {"name": 16} which isn't really a sound argument against the format itself.
Another situation, due to the same defect:
{:name Jason Bourne, :age 320 000}
The JSON-equivalent mistake would be a syntax error: {"name": "Jason Bourne", "age": 320 000}> Note the following advantages of RON over JSON:
>trailing commas allowed
>single- and multi-line comments
>field names aren't quoted, so it's less verbose
>optional struct names improve readability
>enums are supported (and less verbose than their JSON representation)
I’m no fan of JSON but honestly, I think this is a very weak list of benefits if you’re trying to convince someone to switch.
> It's designed to support all of Serde's data model
One format based on that I've been conjuring up recently would represent the first example from the RON README like so:
GameConfig[ optional struct name
window size [[800] [600]]
window title [PAC-MAN]
fullscreen [false]
mouse sensitivity [1.4]
key bindings [
up [Up]
down [Down]
left [Left]
right [Right]
Uncomment to enable WASD controls
;[
W [Up]
A [Down]
S [Left]
D [Right]
]
]
difficulty options [
start difficulty [Easy]
adaptive [false]
]
]
I put a syntax-highlighted version and some more details in this gist[1].I wonder what you guys think about such a minimal alternative.
[1] https://gist.github.com/djedr/4eeac1de466512ec211ff17cfd1f5e...
e.g. Python Object Oriented Notation (POON) would have double quotes and True and False and "None" but otherwise be easily mistaken for JSON.
Scheme Object Notation Externtion (SCONE) would presumably just be a lot of brackets?
I'd argue that toml can be called "a Python(ic) object notation" in some sense.
Are there already parsers available for Scala, Python, Java, etc.?
And the customers of your api understand it as well, so you likely need to support json even if you may use something else internally.
So all JSON is valid (modern) JavaScript. [1]
Is RON valid Rust?
trailing commas allowed
single- and multi-line comments
field names aren't quoted, so it's less verboseAlso this is really cool when you want to upgrade your config file to a script!
It has comments! (looking at you JSON; pre-5 I know...)
Even though YAML was named "Yet Another Markup Language" in the beginning, it changed to "Yaml Aint a Markup Language" to emphasise that.
See, for example, https://github.com/ron-rs/ron/blob/484fcab0686dfd18c7e29b6c1..., where it (in a type-inferency way) says “parse as Config”.
The actual answer seems to be https://github.com/serde-rs/serde/issues/1087#issuecomment-3... - no security implications if no manually implemented Deserialize impls linked to by your application have security implications.
Security concerns in deserialising come about due to the input being able to control things, things like Python’s Pickle protocol where you get arbitrary object instantiation and function calls, even of things like os.system (pickle.loads(b"cos\nsystem\n(S'echo ACE'\ntR.")). Security cannot reasonably be retrofitted to such a model. With the likes of Serde (and indeed structural serialisers/deserialisers in practically every ahead-of-time-compiled language), this isn’t an issue: only the code controls things, and only types that declare a way of deserialising can be deserialised to.
Once you solve the various edge cases, this is basically what flatbuffers and capnproto are
Rust's in memory data structures don't have any meaning on their own, they only have meaning in the context of the running program.
This is, in fact, the whole point of the New Type Idiom. If I have two types Minutes(u32) and Years(u32) those are represented in memory the same way, an unsigned 32-bit integer, just four bytes. But because Rust is statically Type Checked, everywhere the programs knows which is which, you can't assign a Minutes to a Years, or a Years to a Minutes, for the same reason you can't assign "Hacker News" to an f64.
But this means a 32-bit value in your hypothetical file could be either a Minutes or a Years, and a program loading that file needs to know a priori which it is. Once you start coming up with rules about how you'd do that, you're inventing a serialisation format, and you're exactly back to the problem where your original question arose.
I've used the crate `savefile` for this in the past (https://docs.rs/savefile/latest/savefile/), which also supports migrating data between versions. So if you have a bunch of on-disk data of a struct and you add a new property, you can handle migrating the data on the fly to the new struct structure.
I'm also using `savefile` for saving HN items (comments and stories) to disk in my own HN application: https://codeberg.org/ditzes/ditzes/src/branch/master/src-tau... and it's very quick to load stuff from disk, haven't hit any bottlenecks really
Example: https://ditzes.com/item/35640002 (SpaceX Starship rocket explodes minutes after launch from Texas) has 1223 comments which are individually loaded from disk, takes around 100ms to load from disk, which considering how inefficient and unoptimized the code is, I'm pretty happy with.
Which is exactly what savefile does.
It is certainly possible to store data structures directly inside files (I think MS Word did this to save documents), but indeed you have to think carefully about type safety but also data alignment, offsetting and such, and perhaps cryptographic signatures to make it secure when needed, which is what library/language designers need to think about to make it easier for programmers to use this stuff. Sure, it won't be adequate in all situations, but that's not what I'm asking for.
While Windows enforces it, most Unix systems don't support it at all. Back when Linux had it (removed in 2021), it had to be enabled when a volume was mounted, and that was not default nor was it supported for all file system types.
Without it, even if you mmap() the file with the MAP_PRIVATE flag, portions of the file could be overwritten by other programs before you read them.
And there are the challenges with pointers and data structures others have mentioned. And supporting old files in the event of a Rust ABI change becomes incredibly hard.
There's a reason most serialization formats are either self-describing, have external schemas (json), or offload state to a specific state store engine.
It is possible to do explicitly with `#[repr(C)]` in Rust. This is still fragile:
- `usize` and `isize` are not guaranteed to be portable between machines.
- pointers (including vtables in trait objects) are not portable between processes
- structs and enums may change in source code
- file descriptors, mutexes/futexes and any kind of external resources in general are not portable between processes. Thread-local variables are not even portable between threads.
In short: memory layout in general depends on a large amount of ephemeral compiler metadata. It is possible to constrain memory layout to something portable (protobufs, flatbuffers, capnproto, thrift, etc), but this seems more like a serialization format, not an efficient memory layout for a compiled language.
I'd say this is a good thing to avoid ossification of memory layouts: if you need to unmarshal something, it is better to mark this explicitly. Although the C ABI can be convenient, it constrains optimizations that compilers are allowed to do.
are the second quotes optional?