@toon-format/spec 2.0.0 โ†’ 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # TOON Format Specification
2
2
 
3
3
  [![SPEC v2.0](https://img.shields.io/badge/spec-v2.0-lightgrey)](./SPEC.md)
4
- [![Tests](https://img.shields.io/badge/tests-340-green)](./tests/fixtures/)
4
+ [![Tests](https://img.shields.io/badge/tests-342-green)](./tests/fixtures/)
5
5
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
6
6
 
7
- This repository contains the official specification for **Token-Oriented Object Notation (TOON)**, a compact, human-readable serialization format designed for passing structured data to Large Language Models with significantly reduced token usage.
7
+ This repository contains the official specification for **Token-Oriented Object Notation (TOON)**, a compact, human-readable encoding of the JSON data model for LLM prompts. It provides a lossless serialization of the same objects, arrays, and primitives as JSON, but in a syntax that minimizes tokens and makes structure easy for models to follow.
8
8
 
9
9
  ## ๐Ÿ“‹ Specification
10
10
 
@@ -18,38 +18,76 @@ The specification includes complete grammar (ABNF), encoding rules, validation r
18
18
 
19
19
  ## What is TOON?
20
20
 
21
- **Token-Oriented Object Notation** is a compact, human-readable serialization format designed for passing structured data to Large Language Models with significantly reduced token usage. It's intended for LLM input, not output.
21
+ > [!IMPORTANT]
22
+ > For a high-level overview of TOON, its features and benefits, design goals, and comparisons to other formats, see the [`toon-format/toon` repository](https://github.com/toon-format/toon).
22
23
 
23
- TOON's sweet spot is **uniform arrays of objects** โ€“ multiple fields per row, same structure across items. It borrows YAML's indentation-based structure for nested objects and CSV's tabular format for uniform data rows, then optimizes both for token efficiency in LLM contexts. For deeply nested or non-uniform data, JSON may be more efficient.
24
+ ## Serialization Example
24
25
 
25
- **Key Features:**
26
-
27
- - ๐Ÿ’ธ **Token-efficient:** typically 30โ€“60% fewer tokens than JSON
28
- - ๐Ÿคฟ **LLM-friendly guardrails:** explicit lengths and fields enable validation
29
- - ๐Ÿฑ **Minimal syntax:** removes redundant punctuation (braces, brackets, most quotes)
30
- - ๐Ÿ“ **Indentation-based structure:** like YAML, uses whitespace instead of braces
31
- - ๐Ÿงบ **Tabular arrays:** declare keys once, stream data as rows
32
-
33
- ## Quick Example
34
-
35
- **JSON:**
26
+ <table>
27
+ <tr>
28
+ <th>JSON</th>
29
+ <th>TOON</th>
30
+ </tr>
31
+ <tr>
32
+ <td>
36
33
 
37
34
  ```json
38
35
  {
39
- "users": [
40
- { "id": 1, "name": "Alice", "role": "admin" },
41
- { "id": 2, "name": "Bob", "role": "user" }
36
+ "context": {
37
+ "task": "Our favorite hikes together",
38
+ "location": "Boulder",
39
+ "season": "spring_2025"
40
+ },
41
+ "friends": ["ana", "luis", "sam"],
42
+ "hikes": [
43
+ {
44
+ "id": 1,
45
+ "name": "Blue Lake Trail",
46
+ "distanceKm": 7.5,
47
+ "elevationGain": 320,
48
+ "companion": "ana",
49
+ "wasSunny": true
50
+ },
51
+ {
52
+ "id": 2,
53
+ "name": "Ridge Overlook",
54
+ "distanceKm": 9.2,
55
+ "elevationGain": 540,
56
+ "companion": "luis",
57
+ "wasSunny": false
58
+ },
59
+ {
60
+ "id": 3,
61
+ "name": "Wildflower Loop",
62
+ "distanceKm": 5.1,
63
+ "elevationGain": 180,
64
+ "companion": "sam",
65
+ "wasSunny": true
66
+ }
42
67
  ]
43
68
  }
44
69
  ```
45
70
 
46
- **TOON:**
71
+ </td>
72
+ <td>
47
73
 
74
+ ```toon
75
+ context:
76
+ task: Our favorite hikes together
77
+ location: Boulder
78
+ season: spring_2025
79
+
80
+ friends[3]: ana,luis,sam
81
+
82
+ hikes[3]{id,name,distanceKm,elevationGain,companion,wasSunny}:
83
+ 1,Blue Lake Trail,7.5,320,ana,true
84
+ 2,Ridge Overlook,9.2,540,luis,false
85
+ 3,Wildflower Loop,5.1,180,sam,true
48
86
  ```
49
- users[2]{id,name,role}:
50
- 1,Alice,admin
51
- 2,Bob,user
52
- ```
87
+
88
+ </td>
89
+ </tr>
90
+ </table>
53
91
 
54
92
  ## Reference Implementation
55
93
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@toon-format/spec",
3
3
  "type": "module",
4
- "version": "2.0.0",
4
+ "version": "2.0.1",
5
5
  "packageManager": "pnpm@10.19.0",
6
6
  "description": "Official specification for Token-Oriented Object Notation (TOON)",
7
7
  "author": "Johann Schopplich <hello@johannschopplich.com>",
@@ -69,10 +69,10 @@
69
69
  },
70
70
  {
71
71
  "name": "parses objects containing arrays (including empty arrays) in list format",
72
- "input": "items[1]:\n - name: test\n data[0]:",
72
+ "input": "items[1]:\n - name: Ada\n data[0]:",
73
73
  "expected": {
74
74
  "items": [
75
- { "name": "test", "data": [] }
75
+ { "name": "Ada", "data": [] }
76
76
  ]
77
77
  },
78
78
  "specSection": "9.4"
@@ -120,35 +120,41 @@
120
120
  "specSection": "9.2"
121
121
  },
122
122
  {
123
- "name": "parses root arrays of primitives (inline)",
123
+ "name": "parses root-level primitive array inline",
124
124
  "input": "[5]: x,y,\"true\",true,10",
125
125
  "expected": ["x", "y", "true", true, 10],
126
126
  "specSection": "9.1"
127
127
  },
128
128
  {
129
- "name": "parses root arrays of uniform objects in tabular format",
129
+ "name": "parses root-level array of uniform objects in tabular format",
130
130
  "input": "[2]{id}:\n 1\n 2",
131
131
  "expected": [{ "id": 1 }, { "id": 2 }],
132
132
  "specSection": "9.3"
133
133
  },
134
134
  {
135
- "name": "parses root arrays of non-uniform objects in list format",
135
+ "name": "parses root-level array of non-uniform objects in list format",
136
136
  "input": "[2]:\n - id: 1\n - id: 2\n name: Ada",
137
137
  "expected": [{ "id": 1 }, { "id": 2, "name": "Ada" }],
138
138
  "specSection": "9.4"
139
139
  },
140
140
  {
141
- "name": "parses empty root arrays",
142
- "input": "[0]:",
143
- "expected": [],
144
- "specSection": "9.1"
141
+ "name": "parses root-level array mixing primitive, object, and array of objects in list format",
142
+ "input": "[3]:\n - summary\n - id: 1\n name: Ada\n - [2]:\n - id: 2\n - status: draft",
143
+ "expected": ["summary", { "id": 1, "name": "Ada" }, [{ "id": 2 }, { "status": "draft" }]],
144
+ "specSection": "9.4"
145
145
  },
146
146
  {
147
- "name": "parses root arrays of arrays",
147
+ "name": "parses root-level array of arrays",
148
148
  "input": "[2]:\n - [2]: 1,2\n - [0]:",
149
149
  "expected": [[1, 2], []],
150
150
  "specSection": "9.2"
151
151
  },
152
+ {
153
+ "name": "parses empty root-level array",
154
+ "input": "[0]:",
155
+ "expected": [],
156
+ "specSection": "9.1"
157
+ },
152
158
  {
153
159
  "name": "parses complex mixed object with arrays and nested objects",
154
160
  "input": "user:\n id: 123\n name: Ada\n tags[2]: reading,gaming\n active: true\n prefs[0]:",
@@ -164,7 +170,7 @@
164
170
  "specSection": "8"
165
171
  },
166
172
  {
167
- "name": "parses arrays mixing primitives, objects and strings (list format)",
173
+ "name": "parses arrays mixing primitives, objects, and strings in list format",
168
174
  "input": "items[3]:\n - 1\n - a: 1\n - text",
169
175
  "expected": {
170
176
  "items": [1, { "a": 1 }, "text"]
@@ -59,7 +59,7 @@
59
59
  "specSection": "9.3"
60
60
  },
61
61
  {
62
- "name": "unquoted colon terminates tabular rows and starts key-value pair",
62
+ "name": "treats unquoted colon as terminator for tabular rows and start of key-value pair",
63
63
  "input": "items[2]{id,name}:\n 1,Alice\n 2,Bob\ncount: 2",
64
64
  "expected": {
65
65
  "items": [
@@ -66,7 +66,7 @@
66
66
  "specSection": "11"
67
67
  },
68
68
  {
69
- "name": "nested arrays inside list items default to comma delimiter",
69
+ "name": "parses nested arrays inside list items with default comma delimiter",
70
70
  "input": "items[1\t]:\n - tags[3]: a,b,c",
71
71
  "expected": {
72
72
  "items": [{ "tags": ["a", "b", "c"] }]
@@ -75,7 +75,7 @@
75
75
  "note": "Parent uses tab, nested defaults to comma"
76
76
  },
77
77
  {
78
- "name": "nested arrays inside list items default to comma with pipe parent",
78
+ "name": "parses nested arrays inside list items with default comma delimiter when parent uses pipe",
79
79
  "input": "items[1|]:\n - tags[3]: a,b,c",
80
80
  "expected": {
81
81
  "items": [{ "tags": ["a", "b", "c"] }]
@@ -83,25 +83,25 @@
83
83
  "specSection": "11"
84
84
  },
85
85
  {
86
- "name": "parses root arrays with tab delimiter",
86
+ "name": "parses root-level array with tab delimiter",
87
87
  "input": "[3\t]: x\ty\tz",
88
88
  "expected": ["x", "y", "z"],
89
89
  "specSection": "11"
90
90
  },
91
91
  {
92
- "name": "parses root arrays with pipe delimiter",
92
+ "name": "parses root-level array with pipe delimiter",
93
93
  "input": "[3|]: x|y|z",
94
94
  "expected": ["x", "y", "z"],
95
95
  "specSection": "11"
96
96
  },
97
97
  {
98
- "name": "parses root arrays of objects with tab delimiter",
98
+ "name": "parses root-level array of objects with tab delimiter",
99
99
  "input": "[2\t]{id}:\n 1\n 2",
100
100
  "expected": [{ "id": 1 }, { "id": 2 }],
101
101
  "specSection": "11"
102
102
  },
103
103
  {
104
- "name": "parses root arrays of objects with pipe delimiter",
104
+ "name": "parses root-level array of objects with pipe delimiter",
105
105
  "input": "[2|]{id}:\n 1\n 2",
106
106
  "expected": [{ "id": 1 }, { "id": 2 }],
107
107
  "specSection": "11"
@@ -4,7 +4,7 @@
4
4
  "description": "Strict mode indentation validation - non-multiple indentation, tab characters, custom indent sizes",
5
5
  "tests": [
6
6
  {
7
- "name": "throws when object field has non-multiple indentation (3 spaces with indent=2)",
7
+ "name": "throws on object field with non-multiple indentation (3 spaces with indent=2)",
8
8
  "input": "a:\n b: 1",
9
9
  "expected": null,
10
10
  "shouldError": true,
@@ -15,7 +15,7 @@
15
15
  "specSection": "14.3"
16
16
  },
17
17
  {
18
- "name": "throws when list item has non-multiple indentation (3 spaces with indent=2)",
18
+ "name": "throws on list item with non-multiple indentation (3 spaces with indent=2)",
19
19
  "input": "items[2]:\n - id: 1\n - id: 2",
20
20
  "expected": null,
21
21
  "shouldError": true,
@@ -26,7 +26,7 @@
26
26
  "specSection": "14.3"
27
27
  },
28
28
  {
29
- "name": "throws with custom indent size when non-multiple (3 spaces with indent=4)",
29
+ "name": "throws on non-multiple indentation with custom indent=4 (3 spaces)",
30
30
  "input": "a:\n b: 1",
31
31
  "expected": null,
32
32
  "shouldError": true,
@@ -51,7 +51,7 @@
51
51
  "specSection": "12"
52
52
  },
53
53
  {
54
- "name": "throws when tab character used in indentation",
54
+ "name": "throws on tab character used in indentation",
55
55
  "input": "a:\n\tb: 1",
56
56
  "expected": null,
57
57
  "shouldError": true,
@@ -61,7 +61,7 @@
61
61
  "specSection": "14.3"
62
62
  },
63
63
  {
64
- "name": "throws when mixed tabs and spaces in indentation",
64
+ "name": "throws on mixed tabs and spaces in indentation",
65
65
  "input": "a:\n \tb: 1",
66
66
  "expected": null,
67
67
  "shouldError": true,
@@ -71,7 +71,7 @@
71
71
  "specSection": "14.3"
72
72
  },
73
73
  {
74
- "name": "throws when tab at start of line",
74
+ "name": "throws on tab at start of line",
75
75
  "input": "\ta: 1",
76
76
  "expected": null,
77
77
  "shouldError": true,
@@ -144,7 +144,7 @@
144
144
  "specSection": "12"
145
145
  },
146
146
  {
147
- "name": "empty lines do not trigger validation errors",
147
+ "name": "parses empty lines without validation errors",
148
148
  "input": "a: 1\n\nb: 2",
149
149
  "expected": {
150
150
  "a": 1,
@@ -156,7 +156,7 @@
156
156
  "specSection": "12"
157
157
  },
158
158
  {
159
- "name": "root-level content (0 indentation) is always valid",
159
+ "name": "parses root-level content (0 indentation) as always valid",
160
160
  "input": "a: 1\nb: 2\nc: 3",
161
161
  "expected": {
162
162
  "a": 1,
@@ -169,7 +169,7 @@
169
169
  "specSection": "12"
170
170
  },
171
171
  {
172
- "name": "lines with only spaces are not validated if empty",
172
+ "name": "parses lines with only spaces without validation if empty",
173
173
  "input": "a: 1\n \nb: 2",
174
174
  "expected": {
175
175
  "a": 1,
@@ -4,7 +4,7 @@
4
4
  "description": "Root form detection - empty document, single primitive, multiple primitives",
5
5
  "tests": [
6
6
  {
7
- "name": "empty document decodes to empty object",
7
+ "name": "parses empty document as empty object",
8
8
  "input": "",
9
9
  "expected": {},
10
10
  "options": {
@@ -18,14 +18,14 @@
18
18
  "specSection": "14.1"
19
19
  },
20
20
  {
21
- "name": "throws when tabular row value count does not match header field count",
21
+ "name": "throws on tabular row value count mismatch with header field count",
22
22
  "input": "items[2]{id,name}:\n 1,Ada\n 2",
23
23
  "expected": null,
24
24
  "shouldError": true,
25
25
  "specSection": "14.1"
26
26
  },
27
27
  {
28
- "name": "throws when tabular row count does not match header length",
28
+ "name": "throws on tabular row count mismatch with header length",
29
29
  "input": "[1]{id}:\n 1\n 2",
30
30
  "expected": null,
31
31
  "shouldError": true,
@@ -49,7 +49,7 @@
49
49
  "specSection": "12"
50
50
  },
51
51
  {
52
- "name": "empty tokens decode to empty string",
52
+ "name": "parses empty tokens as empty string",
53
53
  "input": "items[3]: a,,c",
54
54
  "expected": {
55
55
  "items": ["a", "", "c"]
@@ -54,10 +54,10 @@
54
54
  "specSection": "9.4"
55
55
  },
56
56
  {
57
- "name": "encodes empty root-level array",
58
- "input": [],
59
- "expected": "[0]:",
60
- "specSection": "9.1"
57
+ "name": "encodes root-level array mixing primitive, object, and array of objects in list format",
58
+ "input": ["summary", { "id": 1, "name": "Ada" }, [{ "id": 2 }, { "status": "draft" }]],
59
+ "expected": "[3]:\n - summary\n - id: 1\n name: Ada\n - [2]:\n - id: 2\n - status: draft",
60
+ "specSection": "9.4"
61
61
  },
62
62
  {
63
63
  "name": "encodes root-level arrays of arrays",
@@ -65,6 +65,12 @@
65
65
  "expected": "[2]:\n - [2]: 1,2\n - [0]:",
66
66
  "specSection": "9.2"
67
67
  },
68
+ {
69
+ "name": "encodes empty root-level array",
70
+ "input": [],
71
+ "expected": "[0]:",
72
+ "specSection": "9.1"
73
+ },
68
74
  {
69
75
  "name": "encodes complex nested structure",
70
76
  "input": {
@@ -27,17 +27,17 @@
27
27
  {
28
28
  "name": "preserves field order in list items - array first",
29
29
  "input": {
30
- "items": [{ "nums": [1, 2, 3], "name": "test" }]
30
+ "items": [{ "nums": [1, 2, 3], "name": "Ada" }]
31
31
  },
32
- "expected": "items[1]:\n - nums[3]: 1,2,3\n name: test",
32
+ "expected": "items[1]:\n - nums[3]: 1,2,3\n name: Ada",
33
33
  "specSection": "10"
34
34
  },
35
35
  {
36
36
  "name": "preserves field order in list items - primitive first",
37
37
  "input": {
38
- "items": [{ "name": "test", "nums": [1, 2, 3] }]
38
+ "items": [{ "name": "Ada", "nums": [1, 2, 3] }]
39
39
  },
40
- "expected": "items[1]:\n - name: test\n nums[3]: 1,2,3",
40
+ "expected": "items[1]:\n - name: Ada\n nums[3]: 1,2,3",
41
41
  "specSection": "10"
42
42
  },
43
43
  {
@@ -90,10 +90,10 @@
90
90
  "name": "encodes objects with empty arrays in list format",
91
91
  "input": {
92
92
  "items": [
93
- { "name": "test", "data": [] }
93
+ { "name": "Ada", "data": [] }
94
94
  ]
95
95
  },
96
- "expected": "items[1]:\n - name: test\n data[0]:",
96
+ "expected": "items[1]:\n - name: Ada\n data[0]:",
97
97
  "specSection": "10"
98
98
  },
99
99
  {
@@ -124,7 +124,7 @@
124
124
  "specSection": "9.3"
125
125
  },
126
126
  {
127
- "name": "uses list format when one object has nested column",
127
+ "name": "uses list format when one object has nested field",
128
128
  "input": {
129
129
  "items": [
130
130
  { "id": 1, "data": "string" },
@@ -4,7 +4,7 @@
4
4
  "description": "Tabular array encoding - arrays of uniform objects with primitive values",
5
5
  "tests": [
6
6
  {
7
- "name": "encodes arrays of similar objects in tabular format",
7
+ "name": "encodes arrays of uniform objects in tabular format",
8
8
  "input": {
9
9
  "items": [
10
10
  { "sku": "A1", "qty": 2, "price": 9.99 },
@@ -87,7 +87,7 @@
87
87
  "specSection": "11"
88
88
  },
89
89
  {
90
- "name": "encodes root arrays with tab delimiter",
90
+ "name": "encodes root-level array with tab delimiter",
91
91
  "input": ["x", "y", "z"],
92
92
  "expected": "[3\t]: x\ty\tz",
93
93
  "options": {
@@ -96,7 +96,7 @@
96
96
  "specSection": "11"
97
97
  },
98
98
  {
99
- "name": "encodes root arrays with pipe delimiter",
99
+ "name": "encodes root-level array with pipe delimiter",
100
100
  "input": ["x", "y", "z"],
101
101
  "expected": "[3|]: x|y|z",
102
102
  "options": {
@@ -105,7 +105,7 @@
105
105
  "specSection": "11"
106
106
  },
107
107
  {
108
- "name": "encodes root arrays of objects with tab delimiter",
108
+ "name": "encodes root-level array of objects with tab delimiter",
109
109
  "input": [{ "id": 1 }, { "id": 2 }],
110
110
  "expected": "[2\t]{id}:\n 1\n 2",
111
111
  "options": {
@@ -114,7 +114,7 @@
114
114
  "specSection": "11"
115
115
  },
116
116
  {
117
- "name": "encodes root arrays of objects with pipe delimiter",
117
+ "name": "encodes root-level array of objects with pipe delimiter",
118
118
  "input": [{ "id": 1 }, { "id": 2 }],
119
119
  "expected": "[2|]{id}:\n 1\n 2",
120
120
  "options": {