YAY β€” Yet Another YAML

13 min read Original article β†—

Yet Another YAML: more than JSON, less than YAML, and better for it

Ints don’t Float πŸ¦†

Arbitrary-precision integers are a first-class type, distinct from floats.

Byte Arrays πŸ“¦

Binary data with inline <c0fe> and block > c0fe forms.

Clean Diffs 🧹

Changes to one value never cascade into adjacent lines.

No Surprises 🚫

No bare words. No surrogates or control codes. Strict whitespace.

The yay formatter realigns your comments, wraps your sentences, and makes your data legit. It can also translate to lots of other formats and languages.*

*Neither a floor wax nor a dessert topping.

At a Glance πŸ‘€

roses-are-red: true      # There is no "yes" or "on".
violets-are-blue: false  # Violets are violet.
arrays:
  - "may"
  - "have"
  - "many"
  - "values"
and-objects-too:
  integers-are-distinct: 42
  from-their-floating-friends: 6.283 185 307 179 586  # digit grouping
inline:
  string: "is concise"
  array: [infinity, -infinity, nan]
  object: {bigint: 1, float64: 2.0}
  bytes: <f33d face>
block:
  string: `
    This is a string.
    There are many like it.
  array:
    - "But"
    - "this"
    - "one's"
  object:
    mine: null
  bytes: >
    b0 b5  c0 ff  # Bob's Coffee
    fe fa  ca de  # Facade.
concatenated:
  "I'm not dead yet. "
  "I feel happy!"
unicode-code-point: "\u{1F600}"  # UTF-16 surrogates are inexpressible
"name with spaces": 'works too'
and-objects-too:
  from-their-floating-friends: 6.283185307179586
  integers-are-distinct: 42
arrays:
- may
- have
- many
- values
block:
  array:
  - But
  - this
  - one's
  bytes: !binary sLXA//76yt4=
  object:
    mine: null
  string: |
    This is a string.
    There are many like it.
concatenated: I'm not dead yet. I feel happy!
inline:
  array:
  - .inf
  - -.inf
  - .nan
  bytes: !binary 8z36zg==
  object:
    bigint: 1
    float64: 2.0
  string: is concise
name with spaces: works too
roses-are-red: true
unicode-code-point: πŸ˜€
violets-are-blue: false
{
  "and-objects-too": {
    "from-their-floating-friends": 6.283185307179586,
    "integers-are-distinct": "#42"
  },
  "arrays": [
    "may",
    "have",
    "many",
    "values"
  ],
  "block": {
    "array": [
      "But",
      "this",
      "one's"
    ],
    "bytes": "*b0b5c0fffefacade",
    "object": {
      "mine": null
    },
    "string": "This is a string.\nThere are many like it.\n"
  },
  "concatenated": "I'm not dead yet. I feel happy!",
  "inline": {
    "array": [
      "#Infinity",
      "#-Infinity",
      "#NaN"
    ],
    "bytes": "*f33dface",
    "object": {
      "bigint": "#1",
      "float64": 2
    },
    "string": "is concise"
  },
  "name with spaces": "works too",
  "roses-are-red": true,
  "unicode-code-point": "πŸ˜€",
  "violets-are-blue": false
}
{
  "and-objects-too": {
    "from-their-floating-friends": 6.283185307179586,
    "integers-are-distinct": 42
  },
  "arrays": ["may", "have", "many", "values"],
  "block": {
    "array": ["But", "this", "one's"],
    "bytes": h'b0b5c0fffefacade',
    "object": {
      "mine": null
    },
    "string": "This is a string.\nThere are many like it.\n"
  },
  "concatenated": "I'm not dead yet. I feel happy!",
  "inline": {
    "array": [Infinity, -Infinity, NaN],
    "bytes": h'f33dface',
    "object": {
      "bigint": 1,
      "float64": 2.0
    },
    "string": "is concise"
  },
  "name with spaces": "works too",
  "roses-are-red": true,
  "unicode-code-point": "πŸ˜€",
  "violets-are-blue": false
}
00000000  a9 6f 61 6e 64 2d 6f 62  6a 65 63 74 73 2d 74 6f  .oand-objects-to
00000010  6f a2 78 1b 66 72 6f 6d  2d 74 68 65 69 72 2d 66  o.x.from-their-f
00000020  6c 6f 61 74 69 6e 67 2d  66 72 69 65 6e 64 73 fb  loating-friends.
00000030  40 19 21 fb 54 44 2d 18  75 69 6e 74 65 67 65 72  @.!.TD-.uinteger
00000040  73 2d 61 72 65 2d 64 69  73 74 69 6e 63 74 18 2a  s-are-distinct.*
00000050  66 61 72 72 61 79 73 84  63 6d 61 79 64 68 61 76  farrays.cmaydhav
00000060  65 64 6d 61 6e 79 66 76  61 6c 75 65 73 65 62 6c  edmanyfvaluesebl
00000070  6f 63 6b a4 65 61 72 72  61 79 83 63 42 75 74 64  ock.earray.cButd
00000080  74 68 69 73 65 6f 6e 65  27 73 65 62 79 74 65 73  thiseone'sebytes
00000090  48 b0 b5 c0 ff fe fa ca  de 66 6f 62 6a 65 63 74  H........fobject
000000a0  a1 64 6d 69 6e 65 f6 66  73 74 72 69 6e 67 78 2a  .dmine.fstringx*
000000b0  54 68 69 73 20 69 73 20  61 20 73 74 72 69 6e 67  This is a string
000000c0  2e 0a 54 68 65 72 65 20  61 72 65 20 6d 61 6e 79  ..There are many
000000d0  20 6c 69 6b 65 20 69 74  2e 0a 6c 63 6f 6e 63 61   like it..lconca
000000e0  74 65 6e 61 74 65 64 78  1f 49 27 6d 20 6e 6f 74  tenatedx.I'm not
000000f0  20 64 65 61 64 20 79 65  74 2e 20 49 20 66 65 65   dead yet. I fee
00000100  6c 20 68 61 70 70 79 21  66 69 6e 6c 69 6e 65 a4  l happy!finline.
00000110  65 61 72 72 61 79 83 fb  7f f0 00 00 00 00 00 00  earray..........
00000120  fb ff f0 00 00 00 00 00  00 fb 7f f8 00 00 00 00  ................
00000130  00 00 65 62 79 74 65 73  44 f3 3d fa ce 66 6f 62  ..ebytesD.=..fob
00000140  6a 65 63 74 a2 66 62 69  67 69 6e 74 01 67 66 6c  ject.fbigint.gfl
00000150  6f 61 74 36 34 fb 40 00  00 00 00 00 00 00 66 73  oat64.@.......fs
00000160  74 72 69 6e 67 6a 69 73  20 63 6f 6e 63 69 73 65  tringjis concise
00000170  70 6e 61 6d 65 20 77 69  74 68 20 73 70 61 63 65  pname with space
00000180  73 69 77 6f 72 6b 73 20  74 6f 6f 6d 72 6f 73 65  siworks toomrose
00000190  73 2d 61 72 65 2d 72 65  64 f5 72 75 6e 69 63 6f  s-are-red.runico
000001a0  64 65 2d 63 6f 64 65 2d  70 6f 69 6e 74 64 f0 9f  de-code-pointd..
000001b0  98 80 70 76 69 6f 6c 65  74 73 2d 61 72 65 2d 62  ..pviolets-are-b
000001c0  6c 75 65 f4                                      lue.
({
  "and-objects-too": {
    "from-their-floating-friends": 6.283185307179586,
    "integers-are-distinct": 42n,
  },
  "arrays": ["may", "have", "many", "values"],
  "block": {
    "array": ["But", "this", "one's"],
    "bytes": Uint8Array.from([0xb0, 0xb5, 0xc0, 0xff, 0xfe, 0xfa, 0xca, 0xde]),
    "object": { "mine": null },
    "string": "This is a string.\nThere are many like it.\n",
  },
  "concatenated": "I'm not dead yet. I feel happy!",
  "inline": {
    "array": [Infinity, -Infinity, NaN],
    "bytes": Uint8Array.from([0xf3, 0x3d, 0xfa, 0xce]),
    "object": { "bigint": 1n, "float64": 2 },
    "string": "is concise",
  },
  "name with spaces": "works too",
  "roses-are-red": true,
  "unicode-code-point": "πŸ˜€",
  "violets-are-blue": false,
})
map[string]any{
	"and-objects-too": map[string]any{
		"from-their-floating-friends": 6.283185307179586,
		"integers-are-distinct": big.NewInt(42),
	},
	"arrays": []any{"may", "have", "many", "values"},
	"block": map[string]any{
		"array": []any{"But", "this", "one's"},
		"bytes": []byte{0xb0, 0xb5, 0xc0, 0xff, 0xfe, 0xfa, 0xca, 0xde},
		"object": map[string]any{"mine": nil},
		"string": "This is a string.\nThere are many like it.\n",
	},
	"concatenated": "I'm not dead yet. I feel happy!",
	"inline": map[string]any{
		"array": []any{math.Inf(1), math.Inf(-1), math.NaN()},
		"bytes": []byte{0xf3, 0x3d, 0xfa, 0xce},
		"object": map[string]any{"bigint": big.NewInt(1), "float64": 2.0},
		"string": "is concise",
	},
	"name with spaces": "works too",
	"roses-are-red": true,
	"unicode-code-point": "πŸ˜€",
	"violets-are-blue": false,
}
{"and-objects-too": {"from-their-floating-friends": 6.283185307179586,
"integers-are-distinct": 42}, "arrays": ["may", "have", "many", "values"],
"block": {"array": ["But", "this", "one's"],
"bytes": bytes.fromhex("b0b5c0fffefacade"), "object": {"mine": None},
"string": "This is a string.\nThere are many like it.\n"},
"concatenated": "I'm not dead yet. I feel happy!", "inline": {"array":
[float("inf"), float("-inf"), float("nan")],
"bytes": bytes.fromhex("f33dface"), "object": {"bigint": 1, "float64": 2.0},
"string": "is concise"}, "name with spaces": "works too",
"roses-are-red": True, "unicode-code-point": "πŸ˜€",
"violets-are-blue": False}
Value::Object(HashMap::from([
    ("and-objects-too".into(), Value::Object(HashMap::from([
        ("from-their-floating-friends".into(), Value::Float(6.283185307179586)),
        ("integers-are-distinct".into(), Value::Integer(42.into())),
    ]))),
    ("arrays".into(), Value::Array(vec![
        Value::String("may".into()),
        Value::String("have".into()),
        Value::String("many".into()),
        Value::String("values".into()),
    ])),
    ("block".into(), Value::Object(HashMap::from([
        ("array".into(), Value::Array(vec![
            Value::String("But".into()),
            Value::String("this".into()),
            Value::String("one's".into()),
        ])),
        ("bytes".into(), Value::Bytes(vec![0xb0, 0xb5, 0xc0, 0xff, 0xfe, 0xfa, 0xca, 0xde])),
        ("object".into(), Value::Object(HashMap::from([
            ("mine".into(), Value::Null),
        ]))),
        ("string".into(), Value::String("This is a string.\nThere are many like it.\n".into())),
    ]))),
    ("concatenated".into(), Value::String("I'm not dead yet. I feel happy!".into())),
    ("inline".into(), Value::Object(HashMap::from([
        ("array".into(), Value::Array(vec![
            Value::Float(f64::INFINITY),
            Value::Float(f64::NEG_INFINITY),
            Value::Float(f64::NAN),
        ])),
        ("bytes".into(), Value::Bytes(vec![0xf3, 0x3d, 0xfa, 0xce])),
        ("object".into(), Value::Object(HashMap::from([
            ("bigint".into(), Value::Integer(1.into())),
            ("float64".into(), Value::Float(2.0)),
        ]))),
        ("string".into(), Value::String("is concise".into())),
    ]))),
    ("name with spaces".into(), Value::String("works too".into())),
    ("roses-are-red".into(), Value::Bool(true)),
    ("unicode-code-point".into(), Value::String("πŸ˜€".into())),
    ("violets-are-blue".into(), Value::Bool(false)),
]))
YAY_OBJECT(
    "and-objects-too", YAY_OBJECT(
        "from-their-floating-friends", yay_float(6.283185307179586),
        "integers-are-distinct", yay_int(42)
    ),
    "arrays", YAY_ARRAY(
        yay_string("may"),
        yay_string("have"),
        yay_string("many"),
        yay_string("values")
    ),
    "block", YAY_OBJECT(
        "array", YAY_ARRAY(
            yay_string("But"),
            yay_string("this"),
            yay_string("one's")
        ),
        "bytes", yay_bytes_from_hex("b0b5c0fffefacade"),
        "object", YAY_OBJECT("mine", yay_null()),
        "string", yay_string("This is a string.\nThere are many like it.\n")
    ),
    "concatenated", yay_string("I'm not dead yet. I feel happy!"),
    "inline", YAY_OBJECT(
        "array", YAY_ARRAY(
            yay_float(INFINITY),
            yay_float(-INFINITY),
            yay_float(NAN)
        ),
        "bytes", yay_bytes_from_hex("f33dface"),
        "object", YAY_OBJECT("bigint", yay_int(1), "float64", yay_float(2.0)),
        "string", yay_string("is concise")
    ),
    "name with spaces", yay_string("works too"),
    "roses-are-red", yay_bool(true),
    "unicode-code-point", yay_string("πŸ˜€"),
    "violets-are-blue", yay_bool(false)
)
(("and-objects-too" . (("from-their-floating-friends" . 6.283185307179586)
  ("integers-are-distinct" . 42)))
 ("arrays" . #("may" "have" "many" "values"))
 ("block" . (("array" . #("But" "this" "one's"))
  ("bytes" . (bytevector 176 181 192 255 254 250 202 222))
  ("object" . (("mine" . 'null)))
  ("string" . "This is a string.\nThere are many like it.\n")))
 ("concatenated" . "I'm not dead yet. I feel happy!")
 ("inline" . (("array" . #(+inf.0 -inf.0 +nan.0))
  ("bytes" . (bytevector 243 61 250 206))
  ("object" . (("bigint" . 1) ("float64" . 2.0)))
  ("string" . "is concise")))
 ("name with spaces" . "works too")
 ("roses-are-red" . #t)
 ("unicode-code-point" . "πŸ˜€")
 ("violets-are-blue" . #f))
Map.of(
  "and-objects-too", Map.of(
    "from-their-floating-friends", 6.283185307179586,
    "integers-are-distinct", BigInteger.valueOf(42)),
  "arrays", List.of("may", "have", "many", "values"),
  "block", Map.of(
    "array", List.of("But", "this", "one's"),
    "bytes", new byte[] {
      (byte) 0xb0, (byte) 0xb5, (byte) 0xc0, (byte) 0xff,
      (byte) 0xfe, (byte) 0xfa, (byte) 0xca, (byte) 0xde},
    "object", Map.of("mine", null),
    "string", "This is a string.\nThere are many like it.\n"),
  "concatenated", "I'm not dead yet. I feel happy!",
  "inline", Map.of(
    "array", List.of(
      Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN),
    "bytes", new byte[] {(byte) 0xf3, (byte) 0x3d, (byte) 0xfa, (byte) 0xce},
    "object", Map.of("bigint", BigInteger.valueOf(1), "float64", 2.0),
    "string", "is concise"),
  "name with spaces", "works too",
  "roses-are-red", true,
  "unicode-code-point", "πŸ˜€",
  "violets-are-blue", false
)

YOUR SPONSORSHIP COULD BE RIGHT HERE!

THE SOFTWARE IS FREE†, BUT AIN'T CHEAP!
inquire within: agile.lion63541@fastmail.com

Command Line Tool πŸͺ–

The yay CLI parses, validates, formats, and converts YAY files. It reads any supported format and writes to any supported target. Available as binyay on npm and crates.io, or via Homebrew from the kriskowal/yippee tap.

yay [OPTIONS] [FILE|DIR]

Format & Validate ✨

# Reformat a file to canonical YAY
yay config.yay

# Validate without output
yay --check config.yay

# Validate all .yay files in a directory
yay --check .

Convert Between Formats πŸ”„

# JSON to YAY
echo '{"a": 1, "b": 2}' | yay -f json
# a: 1.0
# b: 2.0

# YAY to YAML
yay -t yaml config.yay

# YAY to CBOR (binary)
yay -t cbor config.yay -o config.cbor

# CBOR to YAY
yay -f cbor config.cbor

# YAY to CBOR diagnostic notation
yay -t diag config.yay

Code Generation πŸ—οΈ

yay -t js config.yay      # JS 🟨
yay -t go config.yay      # Go 🐭
yay -t python config.yay  # Python 🐍
yay -t rust config.yay    # Rust πŸ¦€
yay -t c config.yay       # C πŸͺš
yay -t scheme config.yay  # Scheme πŸ˜›
yay -t java config.yay    # Java β˜•

Pipe & Stdin ↔️

# Read from stdin (any format)
echo '42' | yay -t js
# 42n

# Explicit stdin with -
yay -f json - < data.json

# YSON preserves big integers and bytes
echo '<cafe>' | yay -t yson
# "*cafe"

Data Model πŸ€–

YAY defines eight value types.

null
The null keyword
boolean
true or false β€” no "yes", "no", "on", "off"
integer
Arbitrary-precision big integer
float
IEEE 754 binary64, including nan, infinity, -infinity
string
Double-quoted (with escapes), single-quoted (literal), or block (backtick)
bytes
Inline <hex> or block > with hex lines
array
Inline [a, b] or block with - items
object
Inline {k: v} or block with key: value properties

Scalars 🐟

# Null
null

# Booleans
true
false

# Big integers (arbitrary precision)
42
-10
867 5309                       # digit grouping with spaces

# Floats (IEEE 754 binary64)
6.283185307179586
.5
1.
-0.0
6.022e23
nan
infinity
-infinity

Strings 🧡

# Double-quoted (with escape sequences)
"Hello, world\n"
"\"\\\/\b\f\n\r\t\u{263A}"

# Single-quoted (literal, no escapes)
'Are you suggesting coconuts migrate?'

# Block string (backtick introducer)
message: `
  By Grabthar's hammer,
  we live to tell the tale.

# Concatenated quoted strings
confession:
  "I'm not dead yet. "
  "I feel happy!"

Objects 🎁

# Inline
{name: 'Marvin', mood: 'depressed'}

# Block (the root document is an object)
parrot:
  status: "pining for the fjords"
  plumage: "beautiful"

# Quoted keys
"key name": 1
empty: {}

Arrays πŸ“š

# Inline
[42, 404, 418]
["And there was much rejoicing.", "yay."]

# Block
complaints:
- "I didn't vote for you."
- "Help, help, I'm being repressed!"

# Nested
- - "a"
  - "b"
- - 1
  - 2

Byte Arrays 🌈

# Inline
<f33d face>
<>                              # empty

# Block (with comments)
data: >
  b0 b5  c0 ff  # Bob's Coffee
  fe fa  ca de  # Facade.

Comments πŸ’¬

# Top-level comments appear before the root value.
# They extend to the end of the line.
answer: 42  # inline comments work too

YSON 🟑

YSON is a dialect of JSON that preserves all YAY value types. Standard JSON cannot represent big integers, byte arrays, or the special float values infinity, -infinity, and nan. YSON fills the gap by encoding these as prefixed strings:

# Big integers
"#42"         # β†’ 42 (big integer)
"#-7"         # β†’ -7

# Special floats
"#Infinity"   # β†’ infinity
"#-Infinity"  # β†’ -infinity
"#NaN"        # β†’ nan

# Byte arrays
"*cafe"       # β†’ <cafe>
"*"           # β†’ <> (empty)

# Strings starting with # or * are escaped with !
"!#hashtag"   # β†’ "#hashtag" (string)
"!*star"      # β†’ "*star" (string)

A YSON document is always valid JSON, so any JSON parser can read it. A YSON-aware reader recovers the original YAY types. YSON is itself a subset of the Endo SmallCaps encoding.

yay -t yson config.yay        # YAY β†’ YSON
yay -f yson config.yson       # YSON β†’ YAY

SHON 🐚

SHON (Shell Object Notation) lets you construct structured data directly from command-line arguments, without writing a file or piping stdin.

Objects

yay [ --name hello --count 42 ]
{count: 42, name: "hello"}

Arrays

Nesting

yay [ --servers [ localhost:8080 localhost:8081 ] --options [ --verbose -t ] ]
options: {verbose: true}
servers: ["localhost:8080", "localhost:8081"]

Convert to JSON

yay -t json [ --x 1.0 --y 2.0 ]
{
  "x": 1,
  "y": 2
}

Convert to YSON

yay -t yson [ --name hello --values [ 1 2 3 ] ]
{
  "name": "hello",
  "values": [
    "#1",
    "#2",
    "#3"
  ]
}

Hex Bytes

yay -t yson -x cafe
"*cafe"

String Escaping

yay [ -- 42 -- -t -- [ ]
["42", "-t", "["]

Inside brackets, all YAY value types are available: -n (null), -t (true), -f (false), -I (∞), -i (βˆ’βˆž), -N (NaN), -x (hex bytes), -b (fileβ†’bytes), -s (fileβ†’string), -- (string escape), and --key (object key). Bare words are strings. Bare numbers are integers or floats. See SHON.md for the full specification.

Get Started πŸš€

The file extension is .yay.

GitHub Β· Source, grammar, implementations in Rust πŸ¦€, Go 🐭, JS 🟨, Python 🐍, C 🌊, Scheme πŸ“, and Java πŸ™„.

Use the library:

npm install libyay     # JavaScript / TypeScript 🟨
cargo add libyay       # Rust πŸ¦€
pip install libyay     # Python 🐍

Install the CLI (binyay):

brew install kriskowal/yippee/yay   # Homebrew (via tap)
npm install -g binyay               # npm
cargo install binyay                # Rust

Editor support: Vim Β· VS Code