@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 +61 -23
- package/package.json +1 -1
- package/tests/fixtures/decode/arrays-nested.json +17 -11
- package/tests/fixtures/decode/arrays-tabular.json +1 -1
- package/tests/fixtures/decode/delimiters.json +6 -6
- package/tests/fixtures/decode/indentation-errors.json +9 -9
- package/tests/fixtures/decode/root-form.json +1 -1
- package/tests/fixtures/decode/validation-errors.json +2 -2
- package/tests/fixtures/decode/whitespace.json +1 -1
- package/tests/fixtures/encode/arrays-nested.json +10 -4
- package/tests/fixtures/encode/arrays-objects.json +7 -7
- package/tests/fixtures/encode/arrays-tabular.json +1 -1
- package/tests/fixtures/encode/delimiters.json +4 -4
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# TOON Format Specification
|
|
2
2
|
|
|
3
3
|
[](./SPEC.md)
|
|
4
|
-
[](./tests/fixtures/)
|
|
5
5
|
[](./LICENSE)
|
|
6
6
|
|
|
7
|
-
This repository contains the official specification for **Token-Oriented Object Notation (TOON)**, a compact, human-readable
|
|
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
|
-
|
|
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
|
-
|
|
24
|
+
## Serialization Example
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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.
|
|
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:
|
|
72
|
+
"input": "items[1]:\n - name: Ada\n data[0]:",
|
|
73
73
|
"expected": {
|
|
74
74
|
"items": [
|
|
75
|
-
{ "name": "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
142
|
-
"input": "[
|
|
143
|
-
"expected": [],
|
|
144
|
-
"specSection": "9.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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,
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
"specSection": "14.1"
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
|
-
"name": "throws
|
|
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
|
|
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,
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"specSection": "9.4"
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
|
-
"name": "encodes
|
|
58
|
-
"input": [],
|
|
59
|
-
"expected": "[
|
|
60
|
-
"specSection": "9.
|
|
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": "
|
|
30
|
+
"items": [{ "nums": [1, 2, 3], "name": "Ada" }]
|
|
31
31
|
},
|
|
32
|
-
"expected": "items[1]:\n - nums[3]: 1,2,3\n name:
|
|
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": "
|
|
38
|
+
"items": [{ "name": "Ada", "nums": [1, 2, 3] }]
|
|
39
39
|
},
|
|
40
|
-
"expected": "items[1]:\n - name:
|
|
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": "
|
|
93
|
+
{ "name": "Ada", "data": [] }
|
|
94
94
|
]
|
|
95
95
|
},
|
|
96
|
-
"expected": "items[1]:\n - name:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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": {
|