JONF
Json for cONF
Quick example
# Fictional supercomputer IaC
name - Deep Thought
answer - 42
hardware =
cores = 42
eyes =
left - green
right - violet
about -
indented, unquoted,
and raw - \no special chars
multiline string here
pets =
- cat
- dog
- turtle # or tortoise
friends =
=
name - Alice
age = null
=
name - Bob
age = 42
scripts =
check -
set -eu # No more && chains
DIRS="src tests"
lint $DIRS
test $DIRS
Equal JSON:
{
"name": "Deep Thought",
"answer": "42",
"hardware": {
"cores": 42,
"eyes": {
"left": "green",
"right": "violet"
}
},
"about": "indented, unquoted,\nand raw - \\no special chars\n\nmultiline string here",
"pets": [
"cat",
"dog",
"turtle"
],
"friends": [
{
"name": "Alice",
"age": null
},
{
"name": "Bob",
"age": 42
}
],
"scripts": {
"check": "set -eu\nDIRS=\"src tests\"\nlint $DIRS\ntest $DIRS"
}
}
Goals
- Make JONF more readable and suitable for configuration files and DSLs than JSON
- Keep JONF simple and flawless, unlike YAML
- Do not sacrifice JSON features to achieve previous goals, unlike StrictYAML and NestedText
Motivation
JSON is great for API, but not as great for config files and DSLs, that’s why alternatives are trying to fill the gap:
- Disclaimer: I excluded XML from the competition to match this XKCD image, also XML hardly competes for goal 1 anyway
- Competition is not a bad thing at all - this is how everything evolves, and it shows there is a market for the product
- Most alternatives above, especially the most popular YAML, are much more complex than JSON and/or have other flaws - please see the great analysis by StrictYAML
- The Norway Problem is a trap even so popular YAML falls into
- StrictYAML and NestedText solve this problem by assuming all scalars to be strings, and then using optional external schema to convert these strings to the intended type
- This means standard representation of numbers,
true
,false
, andnull
is no longer supported without external schema - Going this route we may start using just plain text format because the app should validate it anyway
- While JSON does not have standard representation of dates, times, and so on, to keep it unbloated, existing JSON scalar types are heavily used, so we’d better try to keep them
- JONF solves The Norway Problem and keeps JSON scalar types in an elegant and terse way
Cheatsheet
Key-value separators and array markers:
-
is for unquoted strings=
is for all other values
Bash memo:
-
is like single quote'
=
is like double quote"
Rules
-
leads to unquoted string on the same line- or to indented unquoted multiline string on the next lines
=
leads to double-quoted JSON string or another JSON value on the same line- or to indented JONF object on the next lines
- or to indented JONF array on the next lines
- It is possible to introduce values of custom types after
=
, e.g. numerous ISO 8601 dates and times formats, but this is a way either to growth of complexity of standard and each parser, or to interoperability hell with user-provided callbacks to parse custom types, so this feature should not be implemented - DSL, e.g. serverless variables, should be parsed after JONF is parsed, see example 10. DSL
- You can have any indentation style that you want so long as it is
black“two spaces”, because standardized indentation improves readability and helps to avoid bugs - For the same reason, exactly one space char ` ` is required:
- after array marker
- before key-value separator
- after key-value separator if the value is on the same line
JONF in 10 examples
1. Root value
One-line non-indented JSON value is a valid JONF root value, because in a one-line case, JSON is great:
{"some": "object", "key": "value"}
["some", "array", "here"]
"some string"
-3.14
true
false
null
JONF object, JONF array, JONF indented unquoted multiline string - are valid root values too, e.g:
indented unquoted
multiline
string
Equal JSON:
"indented unquoted\nmultiline\n\nstring"
More features of unquoted string are explained in the next examples
2. JONF array
- Alice in Wonderland
-
multiline
string
here
= "multiline\nstring\n\nhere"
= " explici\t whitespace \n"
- unquoted is raw - \no special chars"
- great for regex: [\n\r\t]+
- 42
= 42
- -3.14
= -3.14
- true
= true
- false
= false
- null
= null
- []
= []
- {}
= {}
- ""
= ""
Equal JSON:
[
"Alice in Wonderland",
"multiline\nstring\n\nhere",
"multiline\nstring\n\nhere",
" explici\t whitespace \n",
"unquoted is raw - \\no special chars\"",
"great for regex: [\\n\\r\\t]+",
"42",
42,
"-3.14",
-3.14,
"true",
true,
"false",
false,
"null",
null,
"[]",
[],
"{}",
{},
"\"\"",
""
]
3. JONF object
name - Deep Thought
answer - 42
cores = 42
"some - strange = key" - value
42 - keys are always strings
true = "even with =, it affects values only"
Equal JSON:
{
"name": "Deep Thought",
"answer": "42",
"cores": 42,
"some - strange = key": "value",
"42": "keys are always strings",
"true": "even with =, it affects values only"
}
4. Object in object
type - dragon
eyes =
left - green
right - violet
Equal JSON:
{
"type": "dragon",
"eyes": {
"left": "green",
"right": "violet"
}
}
5. Object in array
=
name - Alice
age = null
=
name - Bob
age = 42
Equal JSON:
[
{
"name": "Alice",
"age": null
},
{
"name": "Bob",
"age": 42
}
]
Please note =
should be used as an array marker, unless you want unquoted strings:
- unquoted string
-
multiline - unquoted
string = here
Equal JSON:
[
"unquoted string",
"multiline - unquoted\nstring = here"
]
6. Depth and boundaries
JONF is arguably more readable than YAML below, let alone YAML’s typing flaw:
person:
name: abcd
nick: efgh
friends:
- name: hijk
nick: lmno
- name: pqrs
nick: tuvw
It looks like all names here are on the same depth in YAML, which is misleading, and it takes some effort to notice the boundaries of each friend
JONF shows the depth and boundaries correctly, improving readability:
person =
name - abcd
nick - efgh
friends =
=
name - hijk
nick - lmno
=
name - pqrs
nick - tuvw
7. Array in object
name - Bob
kids =
- Charlie
- Dave
- Eve
Equal JSON:
{
"name": "Bob",
"kids": [
"Charlie",
"Dave",
"Eve"
]
}
8. Array in array
=
- We
- are
=
- almost
=
- done!
Equal JSON:
[
[
"We",
"are"
], [
"almost",
[
"done!"
]
]
]
JONF is obviously better than JSON in a multi-line case
Again, JSON is the best for one-liners, that’s why they are valid in JONF:
[["We", "are"], ["almost", ["done!"]]]
9. Comment
Text from #
to the end of line is a comment if #
is the first character in the line or there is a whitespace before it and it is not inside of JSON value:
# Full-line comment
name - Alice # Inline comment
url - https://example.org/#alice
location = "Wonderland # 42"
Equal JSON:
{
"name": "Alice",
"url": "https://example.org/#alice",
"location": "Wonderland # 42",
}
JONF uses #
and never //
or /* */
for the same reason JSON uses double-quotes and never single-quotes: to keep it simple
10. DSL
In serverless.jonf
variables are parsed after JONF is parsed:
custom =
debug = true
verbose - ${self:custom.debug}
Equal JSON:
{
"custom": {
"debug": true,
"verbose": "${self:custom.debug}"
}
}
Equal JSON, parsed by DSL:
{
"custom": {
"debug": true,
"verbose": true
}
}
Note that DSL decided to change type of verbose
to the type of the variable it referenced. JONF never does that, it is just a container format, strictly mapped to JSON only
Roadmap
- Add JONF to github/linguist and Rouge highlighters to simplify the reviews
- Reviews and fixes of this draft specification
- Formal grammar importing JSON grammar but excluding newlines from its
ws
rule - Apply this grammar to the reference JONF parser/formatter in Python
- Bump to version 1.0.0
- Get contributed JONF parsers/formatters for other languages
JONF as JONF
Name - JONF
Version - 0.0.17
Filename extension - .jonf
Internet media type - application/jonf # TODO
Website - jonf.app
Email - support@jonf.app
Maintainer - whyolet.com
License - MIT
Open format? - Yes
Type of format - Data interchange
Extended from - JSON
That’s all!
Simple and valuable, right?
Thank you for reading
Please post an issue or idea or contribute otherwise: