KSON and its tooling are designed to be a holistic interface layer for JSON-shaped data. KSON is equivalent to JSON: in addition to any RFC 8259 JSON document being a legal KSON document (KSON is an RFC 8259-compliant superset of JSON, see JSON Compatibility Notes), any KSON document is a single formatter pass away from its JSON representation. Some key ways KSON extends JSON's syntax:
- KSON includes a "plain" syntax for objects and lists allowing its default format to look a lot like YAML, without relying on significant whitespace
- KSON introduces the Embed Block, a structure for ergonomically embedding complex content such as code blocks
- KSON adds flexibility to Strings, allowing unquoted "simple" strings and supporting single-quotes as an alternative to double-quotes
A KSON document is a document containing exactly one KSON Value
KSON defines the following value types:
A KSON document representing null is simply:
null
Booleans are written true or false. Here is a KSON document representing a boolean value:
true
Numbers in KSON are identical to numbers in JSON, with the exception that they may have leading zeros. Here is a KSON document representing a number:
5
Here is a KSON list demonstrating a variety of supported numbers:
- 5
- 3.14
- 2.998e8
- 025
- -273.15
Strings in KSON are identical to strings in JSON, except:
- KSON strings may be unquoted if they are 'simple', i.e. they only contain letters (from any alphabet), numbers, underscores, or dashes. They do not start with a number or a dash.
an_unqu0t3d_striπg
One consequence of unquoted strings: a bare true, false or null is always its literal value, exactly as in JSON—quote it ('true') when the string is intended. Likewise, a string that is not 'simple' must be quoted: The Great Gatsby (spaces), v1.2 (a dot) and 2020-01-01 (a leading number) all require quotes—unlike in YAML, title: The Great Gatsby is not valid.
- KSON strings may be quoted using a single quote rather than a double quote. Here is a list of strings demonstrating the difference:
- 'A "string", enclosed in single quotes'
- "A 'string', enclosed in double quotes"
- 'use slash to escape \'internal\' quotes'
- "use slash to escape \"internal\" quotes"
- KSON strings may contain raw, unescaped whitespace (though an Embed Block will usually be a better choice for multiline strings due to their editing ergonomics and indent-stripping)
'A string with
a raw newline.
Tabs are also A-OK 👌'
A list in KSON is a sequence of KSON Values.
KSON supports three list styles:
- Plain Dash Lists, a clean YAML-like list style useful for clarity and readability
- Delimited Dash Lists, a
<>-delimited version of Plain Dash Lists useful when editing or refactoring complex nested lists - Bracket Lists, a JSON-like
[]-delimited list style useful for compacting lists and required for JSON compatibility
A Plain Dash List is a clean YAML-like un-delimited list useful for clarity and readability. This is the style used in KSON's plain format.
A plain dash list is un-delimited, denoting each list element with a dash followed by whitespace (- )—the whitespace is what distinguishes a list dash from a negative number like -5.
- element_one
- element_two
- element_three
Plain dash lists may be nested like so:
- outer_element
- - nested_element_1
- nested_element_2
Important: unlike YAML, whitespace is not significant in KSON, so to add more outer elements to the above list, we must explicitly end the nested list with =, the end-dash:
- outer_element
- - nested_element_1
- nested_element_2
=
- outer_element_2
The end-dash = explicitly declares the end of a Plain Dash List. Dash list parsing is greedy, absorbing every list item it can, so the end-dash = is only required when a nested Plain Dash List needs to explicitly unnest a subsequent list item:
- outer_element_1
- - nested_element_1
- nested_element_2
=
# note that without the end-dash, `outer_element_2` would belong to the nested list
- outer_element_2
See How Plain Structures End for more color on this.
The end-dot . works analogously for Plain Objects.
A Delimited Dash List is a delimited version of a Plain Dash List, useful when editing or refactoring complex nested lists—for instance, these lists can be safely and unambiguously copy/pasted around.
Delimited Dash Lists are delimited with angle brackets <...>:
<
- outer_element1
- <
- nested_element_1
- nested_element_2
>
- outer_element_2
>
This style is used in KSON's delimited format
A Bracket List is a JSON-like list useful for supporting compact formatted lists and required for JSON compatibility. Their data is naturally equivalent to the list types above.
Bracket Lists are delimited with square brackets [...]:
[
outer_element1
[
nested_element_1
nested_element_2
]
outer_element_2
]
Note
Commas are supported between elements, but they are unnecessary and generally formatted away. See Commas in KSON for detail
Empty lists may be expressed as either <> or []
Object properties in KSON are key: value pairs where key is a String and value is any KSON Value.
KSON supports two object styles:
- Plain Objects, a clean YAML-like un-delimited object style useful for clarity and readability
- Delimited Objects, a JSON-like delimited version of Plain Objects useful when editing or refactoring complex nested objects—for instance, these objects can be safely and unambiguously copy/pasted around
A plain object in KSON is a clean YAML-like un-delimited object style useful for clarity and readability.
property_one: 1
property_two: 2
property_three: 3
Plain objects may be nested like so:
outer_property:
nested_property_1: 1
nested_property_2: 2
Important: unlike YAML, whitespace is not significant in KSON, so to add more outer properties to the above object, we must explicitly end the nested object with ., the end-dot:
outer_property:
nested_property_1: 1
nested_property_2: 2
.
outer_property_2: 'not part of the nested object'
The end-dot . explicitly declares the end of a Plain Object. Object parsing is greedy, absorbing every property it can, so the end-dot . is only required when a nested Plain Object needs to explicitly unnest a subsequent property:
outer_property_1:
nested_property_1: x
nested_property_2: y
.
# note that without the end-dot, `outer_property_2` would belong to the nested object
outer_property_2: 'a value'
See How Plain Structures End for more color on this.
The end-dash = works analogously for Plain Dash Lists.
A Delimited Object is a JSON-like delimited version of Plain Objects useful when editing or refactoring complex nested objects.
Delimited Objects are delimited with curly braces {...}:
{
outer_property_1: {
nested_property_1: x
nested_property_2: y
}
outer_property_2: 'a value'
}
This style is used in KSON's delimited format
Note
Commas are supported between properties, but they are unnecessary and generally formatted away. See Commas in KSON for detail
An empty object is expressed as: {}
Plain Dash Lists and Plain Objects are un-delimited, and indentation/whitespace is not what determines their nesting, so it is important to internalize the rule that guides how they end: their parsing is greedy, absorbing every element/property they can.
Stated concretely for each structure:
- A plain dash list absorbs every
--denoted list element it can, ending at the first thing which is not a-element (or at an explicit end-dash=) - A plain object absorbs every
key: valueproperty it can, ending at the first thing which cannot be a property (or at an explicit end-dot.)
This rule means plain structures often end naturally with no terminator needed. For instance, a dash list given as a property value ends on its own, since a key: cannot be a list element:
lucky_numbers:
- 7
- 13
lucky_color: blue
A terminator is needed exactly when the greedy parse would otherwise absorb something meant for an enclosing structure. A common case: an object inside a dash list, followed by a property meant for an enclosing object. Here, the first book object ends naturally at the - (a dash cannot be a property), but the second book needs an end-dot . to keep favorite_movie: out of the book object:
favorite_books:
- title: Elements
author: Euclid
- title: Metaphysics
author: Aristotle
.
favorite_movie: 'The Rock'
Explicitly closing a plain object or list is always legal, even when not required. The formatter cleans up unneeded end-dots and end-dashes.
The KSON Embed Block is designed for ergonomically embedding complex content such as code blocks. See Embed Block JSON Compatibility for details on how the KSON/JSON equivalence is handled for Embed Blocks
An embed block opens with %, optionally followed on the same line by an Embed Tag. The block's content starts on the next line, runs all the way up to the first %%, and always strips its minimum indent. Blank lines do not count toward that minimum, so a paragraph break never changes how much indent is stripped (a blank line indented deeper than the minimum keeps its extra whitespace, since that is content). When %% is on its own line, the newline preceding it is not part of the content—this is a formatting choice that keeps the closing delimiter visually distinct without affecting the value:
embed_block: %
Free form,
multi-line,
indent-stripped
embedded text block!
%%
The line holding %% does count toward the minimum indent, which is how a block keeps indentation that belongs to its content: place %% to the left of the content, and everything to the right of the %% column stays in the value. Here the value is " indented":
embed_block: %
indented
%%
Embed blocks often feature an Embed Tag denoting the type of text being embedded. This is particularly useful for embedded code so that editors and tooling can key off this value and provide language-specific features for a given embed block
%typescript
function fibonacci(n: number): number {
if (n <= 0) return 0;
if (n === 1) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
%%
An Embed Tag adds metadata to an Embed Block. An embed tag is a KSON String closed by the first raw newline rather than by a quote: quotes, punctuation and trailing whitespace are all ordinary tag characters—even a %% is welcome in a tag, since content begins only after the tag's newline. The one thing a tag cannot contain is a raw newline, which always ends the tag.
%this is an "embed tag": it is arbitrary metadata related to this content. In this case, the content is empty.
%%
Embed Tags are often used to denote the embed content's type, which is particularly useful to enable tooling enhancements for the embedded content—for instance to properly highlight the embedded content, or to automatically inject a full-on embedded editor. Examples:
%sql
SELECT first_name, last_name, hire_date
FROM employees
WHERE department = 'Sales'
AND hire_date < '2020-01-01'
ORDER BY hire_date ASC;
%%
The whole line is the tag—here, the single string sql "server=10.0.1.174;uid=root;database=company", quotes and all:
%sql "server=10.0.1.174;uid=root;database=company"
SELECT first_name, last_name, hire_date
FROM employees
WHERE department = 'Sales'
AND hire_date < '2020-01-01'
ORDER BY hire_date ASC;
%%
%Captain's log, Stardate 4523.3
Deep Space Station K-7 has issued a priority one call. More than an emergency,
it signals near or total disaster. We can only assume the Klingons have attacked
the station. We're going in armed for battle.
%%
An embed block's content always ends at the first raw occurrence of the end-delimiter: %% ends it every time, without exception—even a naive regex search for the next %% is guaranteed to be correct. The two-character terminator, escaped by interrupting it with a slash, gives content a way to express the end-delimiter without ever containing it:
embed_escapes: %
Embed end-delimiters are escaped by putting a slash inside them: %\%
Note that this moves the escaping goalpost since we also need to allow "%\%"
literally inside of embeds. So: when evaluating escaped embed delimiters,
we allow arbitrary `\`s between `%`s, and consume one of them. Thus, %\\%
gives %\% in the output, %\\\% gives %\\% in the output, etc forever until
we hit an uninterrupted end-delimiter:
%%
The alternative embed delimiter $ works identically to % and can be used when convenient to minimize escaping
alternate_embed: $text
This embed block is equivalent to a %-delimited block, but here the
end-delimiter to escape is "$\$" rather than "%%"
$$
KSON supports line comments: a # begins a comment which runs to the end of the line. Comments may appear anywhere whitespace may appear (so a # inside a String or an Embed Block's content is just part of that value):
# a comment about this object
key: value
Note that comments trailing a value are supported, but discouraged. KSON's formatter moves trailing comments onto their own line above the item they document.
The KSON compiler preserves comments as metadata, which not only enables the formatter to be comment-aware, but also allows comments to be preserved in operations such as KSON's built-in YAML transpilation: the output YAML has the source comments properly embedded in it.
KSON has a built-in auto-formatter that supports three opinionated styles. These three "views" on the same data serve different roles:
- Plain format, a lean format reminiscent of YAML. This is KSON's default format, useful for clarity and readability
- Delimited format, an explicit format reminiscent of JSON, useful when editing or refactoring complex nested data
- Compact format, a minified format to facilitate compression and transport
person:
name: 'Leonardo Bonacci'
nickname: Fibonacci
favorite_books:
- title: Elements
author: Euclid
- title: Metaphysics
author: Aristotle
.
favorite_numbers:
- - 0
- 1
- 1
- 2
- '...'
=
- '(1 + √5)/2'
- π
# A KSON "embed block" containing Kotlin code
favorite_function: %kotlin
/**
* Calculates the nth number in the Fibonacci sequence using recursion
*/
fun fibonacci(n: Int): Long {
if (n < 0) throw IllegalArgumentException("Input must be non-negative")
return when (n) {
0 -> 0
1 -> 1
else -> fibonacci(n - 1) + fibonacci(n - 2)
}
}
%%
{
person: {
name: 'Leonardo Bonacci'
nickname: Fibonacci
favorite_books: <
- {
title: Elements
author: Euclid
}
- {
title: Metaphysics
author: Aristotle
}
>
favorite_numbers: <
- <
- 0
- 1
- 1
- 2
- '...'
>
- '(1 + √5)/2'
- π
>
# A KSON "embed block" containing Kotlin code
favorite_function: %kotlin
/**
* Calculates the nth number in the Fibonacci sequence using recursion
*/
fun fibonacci(n: Int): Long {
if (n < 0) throw IllegalArgumentException("Input must be non-negative")
return when (n) {
0 -> 0
1 -> 1
else -> fibonacci(n - 1) + fibonacci(n - 2)
}
}
%%
}
}
Compact format is fully data-faithful: everything KSON syntactically controls is compressed to the bare minimum, while the embed block's content—data, not syntax—is preserved byte-for-byte, whitespace and all:
person:name:'Leonardo Bonacci'nickname:Fibonacci favorite_books:[{title:Elements author:Euclid}title:Metaphysics author:Aristotle.]favorite_numbers:[[0 1 1 2 '...']'(1 + √5)/2' π]
# A KSON "embed block" containing Kotlin code
favorite_function:%kotlin
/**
* Calculates the nth number in the Fibonacci sequence using recursion
*/
fun fibonacci(n: Int): Long {
if (n < 0) throw IllegalArgumentException("Input must be non-negative")
return when (n) {
0 -> 0
1 -> 1
else -> fibonacci(n - 1) + fibonacci(n - 2)
}
}
%%
KSON's status as a superset of JSON is verified by JsonSuiteTest.kt, which is generated from JSONTestSuite
KSON is compatible with JsonSchema (Draft7, more to come), verified by the SchemaDraft7SuiteTest*.kt files here, generated from JSON-Schema-Test-Suite
Embed Blocks are a user- and tooling-centric formatting of objects with exactly two string properties, embedTag: and embedContent:, allowing embedded content ergonomics while still being fully equivalent to JSON.
When using KSON as an interface on legacy data by transpiling to JSON or YAML, there is an option to not "preserve" embed tags, transpiling embed blocks down to their embedContent: string value. This is useful for facilitating a better editing experience on legacy embedded code, allowing the user to tag the code with its type (say, sql, or typescript) to enable IDEs to inject better tooling, while still transpiling down to the bare string the underlying legacy data format requires.
JSON compatibility requires that Objects and Bracket Lists in KSON allow commas. However, since every KSON value has a definite beginning and ending, the commas are unnecessary and generally formatted away.