slimjson 1.0.3 → 1.1.0
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/.claude/settings.local.json +11 -0
- package/.idea/git_toolbox_prj.xml +15 -0
- package/.idea/modules.xml +8 -0
- package/.idea/slimjson.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/README.md +510 -310
- package/README_EN.md +499 -309
- package/compress-file.js +41 -41
- package/compress-ratio.js +70 -70
- package/compress-test.js +436 -410
- package/compress.js +544 -410
- package/decompress-file.js +42 -42
- package/esm.mjs +4 -4
- package/package.json +24 -24
- package/test.js +975 -886
package/README.md
CHANGED
|
@@ -1,310 +1,510 @@
|
|
|
1
|
-
# slimjson
|
|
2
|
-
|
|
3
|
-
中文 | [English](./README_EN.md)
|
|
4
|
-
|
|
5
|
-
轻量级对象数组压缩工具 — 将重复 key 的 JSON 对象数组转换为 `{
|
|
6
|
-
|
|
7
|
-
## 适用场景
|
|
8
|
-
|
|
9
|
-
- **API 列表接口**:后端返回列表接口时,每个对象都携带相同的 key 名,大量冗余
|
|
10
|
-
- **异构字段**:不同对象可能拥有不同的字段(后端按需 omit null 字段)
|
|
11
|
-
- **网络传输压缩**:需要在网络传输中极致压缩 JSON 文本体积
|
|
12
|
-
- **大模型上下文压缩**:将大量结构化数据(如数据库查询结果、API 响应、知识库条目)压缩后送入 prompt,减少 token 消耗,降低调用成本
|
|
13
|
-
- **大模型工具调用**:function calling / tool_use 返回的结果往往是结构化的对象数组,压缩后再回传给模型,可显著减少上下文窗口占用,让模型在有限 token 内处理更复杂的数据
|
|
14
|
-
- **大模型识别友好**:压缩后的 `{
|
|
15
|
-
|
|
16
|
-
## 安装
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npm install slimjson
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## API
|
|
23
|
-
|
|
24
|
-
### `compress(source)`
|
|
25
|
-
|
|
26
|
-
将对象数组压缩为 `{
|
|
27
|
-
|
|
28
|
-
```js
|
|
29
|
-
import { compress } from 'slimjson';
|
|
30
|
-
|
|
31
|
-
const users = [
|
|
32
|
-
{ name: 'Alice', age: 25, city: 'NYC' },
|
|
33
|
-
{ name: 'Bob', age: 30, city: 'LA' },
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const compressed = compress(users);
|
|
37
|
-
// {
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
compress(
|
|
84
|
-
// {
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
// ['
|
|
88
|
-
// ['
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
{
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
]
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
compress(
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
1
|
+
# slimjson
|
|
2
|
+
|
|
3
|
+
中文 | [English](./README_EN.md)
|
|
4
|
+
|
|
5
|
+
轻量级对象数组压缩工具 — 将重复 key 的 JSON 对象数组转换为 `{ schema, data }` 紧凑格式,并支持序列化时省略 `null` 以进一步减小体积。
|
|
6
|
+
|
|
7
|
+
## 适用场景
|
|
8
|
+
|
|
9
|
+
- **API 列表接口**:后端返回列表接口时,每个对象都携带相同的 key 名,大量冗余
|
|
10
|
+
- **异构字段**:不同对象可能拥有不同的字段(后端按需 omit null 字段)
|
|
11
|
+
- **网络传输压缩**:需要在网络传输中极致压缩 JSON 文本体积
|
|
12
|
+
- **大模型上下文压缩**:将大量结构化数据(如数据库查询结果、API 响应、知识库条目)压缩后送入 prompt,减少 token 消耗,降低调用成本
|
|
13
|
+
- **大模型工具调用**:function calling / tool_use 返回的结果往往是结构化的对象数组,压缩后再回传给模型,可显著减少上下文窗口占用,让模型在有限 token 内处理更复杂的数据
|
|
14
|
+
- **大模型识别友好**:压缩后的 `{ schema, data }` 格式将 schema(字段定义)与数据分离,key 只出现一次,模型能更准确地理解数据结构、按字段名提取信息,比重复 key 的原始 JSON 更不容易混淆
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install slimjson
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## API
|
|
23
|
+
|
|
24
|
+
### `compress(source, opts?)`
|
|
25
|
+
|
|
26
|
+
将对象数组压缩为 `{ schema, data }` 结构:
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
import { compress } from 'slimjson';
|
|
30
|
+
|
|
31
|
+
const users = [
|
|
32
|
+
{ name: 'Alice', age: 25, city: 'NYC' },
|
|
33
|
+
{ name: 'Bob', age: 30, city: 'LA' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const compressed = compress(users);
|
|
37
|
+
// {
|
|
38
|
+
// schema: [['name', 'age', 'city']],
|
|
39
|
+
// data: [['Alice', 25, 'NYC'], ['Bob', 30, 'LA']]
|
|
40
|
+
// }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**参数:**
|
|
44
|
+
|
|
45
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
46
|
+
|------|------|--------|------|
|
|
47
|
+
| `source` | `Object[]` 或 `Object` | — | 待压缩的对象数组(单个对象会自动包裹为数组) |
|
|
48
|
+
| `opts` | `Object` | — | 可选配置 |
|
|
49
|
+
| `opts.trimTrailingNulls` | `boolean` | `false` | 是否去除行尾的 `null` 值 |
|
|
50
|
+
|
|
51
|
+
**特点:**
|
|
52
|
+
- `schema` 取所有对象的 key 并集,按首次出现顺序排列
|
|
53
|
+
- 某对象缺失某字段 → 对应 data 位置填充 `null`
|
|
54
|
+
- 嵌套对象递归处理:`schema` 中表示为 `{ "fieldName": [childKeys] }`
|
|
55
|
+
- 对象数组(如订单条目)同样递归压缩
|
|
56
|
+
- 当传入的是对象时,会当成数组中只有一个对象处理
|
|
57
|
+
|
|
58
|
+
#### 嵌套对象示例
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
const data = [
|
|
62
|
+
{ name: '张三', age: 28, profile: { avatar: 'a.jpg', bio: 'Hello' } },
|
|
63
|
+
{ name: '李四', age: 35, profile: { avatar: 'b.jpg', file: null } },
|
|
64
|
+
{ name: '王五' },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
compress(data);
|
|
68
|
+
// {
|
|
69
|
+
// schema: [['name', 'age', { profile: ['avatar', 'bio', 'file'] }]],
|
|
70
|
+
// data: [
|
|
71
|
+
// ['张三', 28, ['a.jpg', 'Hello', null]],
|
|
72
|
+
// ['李四', 35, ['b.jpg', null, null]],
|
|
73
|
+
// ['王五', null, null]
|
|
74
|
+
// ]
|
|
75
|
+
// }
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### `trimTrailingNulls`:去除尾部 null
|
|
79
|
+
|
|
80
|
+
启用后,每行及嵌套子行尾部的 `null` 会被去除,进一步压缩体积:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
compress(data, { trimTrailingNulls: true });
|
|
84
|
+
// {
|
|
85
|
+
// schema: [['name', 'age', { profile: ['avatar', 'bio', 'file'] }]],
|
|
86
|
+
// data: [
|
|
87
|
+
// ['张三', 28, ['a.jpg', 'Hello']],
|
|
88
|
+
// ['李四', 35, ['b.jpg']],
|
|
89
|
+
// ['王五']
|
|
90
|
+
// ]
|
|
91
|
+
// }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`decompress` 会自动将缺失的尾部值补回 `null`,roundtrip 还原结果一致:
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
decompress(compress(data, { trimTrailingNulls: true }));
|
|
98
|
+
// [
|
|
99
|
+
// { name: '张三', age: 28, profile: { avatar: 'a.jpg', bio: 'Hello', file: null } },
|
|
100
|
+
// { name: '李四', age: 35, profile: { avatar: 'b.jpg', bio: null, file: null } },
|
|
101
|
+
// { name: '王五', age: null, profile: null }
|
|
102
|
+
// ]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### 对象数组示例(订单场景)
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
const orders = [
|
|
109
|
+
{ orderId: 'A001', items: [{ name: '键盘', price: 299 }, { name: '鼠标', price: 99 }] },
|
|
110
|
+
{ orderId: 'A002', items: [{ name: '显示器', price: 1999 }] },
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
compress(orders);
|
|
114
|
+
// {
|
|
115
|
+
// schema: [['orderId', { items: [['name', 'price']] }]],
|
|
116
|
+
// data: [['A001', [['键盘', 299], ['鼠标', 99]]], ['A002', [['显示器', 1999]]]]
|
|
117
|
+
// }
|
|
118
|
+
|
|
119
|
+
stringify(compress(orders));
|
|
120
|
+
// {schema:[[orderId,{items:[[name,price]]}]],data:[[A001,[[键盘,299],[鼠标,99]]],[A002,[[显示器,1999]]]]}
|
|
121
|
+
// ^^^^^ 嵌套对象 key 无引号 ^^^^ 安全字符串 value 无引号
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### 三层嵌套示例(订单 → 商品 → 规格)
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
const orders = [
|
|
128
|
+
{
|
|
129
|
+
orderId: 'A001',
|
|
130
|
+
customer: '张三',
|
|
131
|
+
items: [
|
|
132
|
+
{ name: '键盘', price: 299, specs: { color: '黑色', layout: '104键' } },
|
|
133
|
+
{ name: '鼠标', price: 99, specs: { color: '白色', dpi: '4000' } },
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
orderId: 'A002',
|
|
138
|
+
customer: '李四',
|
|
139
|
+
items: [
|
|
140
|
+
{ name: '显示器', price: 1999, specs: { color: '银色', size: '27寸' } },
|
|
141
|
+
]
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
compress(orders);
|
|
146
|
+
// {
|
|
147
|
+
// schema: [[
|
|
148
|
+
// 'orderId',
|
|
149
|
+
// 'customer',
|
|
150
|
+
// { items: [['name', 'price', { specs: ['color', 'layout', 'dpi', 'size'] }]] }
|
|
151
|
+
// ]],
|
|
152
|
+
// data: [
|
|
153
|
+
// ['A001', '张三', [
|
|
154
|
+
// ['键盘', 299, ['黑色', '104键', null, null]],
|
|
155
|
+
// ['鼠标', 99, ['白色', null, '4000', null]]
|
|
156
|
+
// ]],
|
|
157
|
+
// ['A002', '李四', [
|
|
158
|
+
// ['显示器', 1999, ['银色', null, null, '27寸']]
|
|
159
|
+
// ]]
|
|
160
|
+
// ]
|
|
161
|
+
// }
|
|
162
|
+
// specs 的 key 取并集:第一单有 layout,第二单有 size → 都保留,缺失的填 null
|
|
163
|
+
|
|
164
|
+
compress(orders, { trimTrailingNulls: true });
|
|
165
|
+
// data 变为:
|
|
166
|
+
// [
|
|
167
|
+
// ['A001', '张三', [
|
|
168
|
+
// ['键盘', 299, ['黑色', '104键']],
|
|
169
|
+
// ['鼠标', 99, ['白色', null, '4000']]
|
|
170
|
+
// ]],
|
|
171
|
+
// ['A002', '李四', [
|
|
172
|
+
// ['显示器', 1999, ['银色', null, null, '27寸']]
|
|
173
|
+
// ]]
|
|
174
|
+
// ]
|
|
175
|
+
|
|
176
|
+
stringify(compress(orders, { trimTrailingNulls: true }));
|
|
177
|
+
// {schema:[[orderId,customer,{items:[[name,price,{specs:[color,layout,dpi,size]}]]}]],data:[[
|
|
178
|
+
// A001,张三,[[键盘,299,[黑色,"104键"]],[鼠标,99,[白色,,4000]]]],[A002,李四,[[显示器,1999,[银色,,,"27寸"]]]]]}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### 单对象示例
|
|
182
|
+
```js
|
|
183
|
+
compress({ name: 'Alice', age: 25 });
|
|
184
|
+
// 等价于 compress([{ name: 'Alice', age: 25 }])
|
|
185
|
+
// { schema: ['name', 'age'], data: ['Alice', 25] }
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### `decompress(compressed)`
|
|
189
|
+
|
|
190
|
+
将 `{ schema, data }` 还原为原始对象数组。缺失的尾部值会自动补回 `null`:
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
const restored = decompress(compressed);
|
|
194
|
+
// deep-equal 原数组
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### `stringify(compressed)`
|
|
198
|
+
|
|
199
|
+
将 compress 结果序列化为紧凑文本。相比 `JSON.stringify`,应用了以下优化规则:
|
|
200
|
+
|
|
201
|
+
```js
|
|
202
|
+
const data = [
|
|
203
|
+
{ name: 'Alice', age: 25 },
|
|
204
|
+
{ name: 'Bob', age: 30 },
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
const text = stringify(compress(data));
|
|
208
|
+
// {schema:[[name,age]],data:[[Alice,25],[Bob,30]]}
|
|
209
|
+
|
|
210
|
+
JSON.stringify(compress(data));
|
|
211
|
+
// {"schema":[["name","age"]],"data":[["Alice",25],["Bob",30]]}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### 序列化规则一览
|
|
215
|
+
|
|
216
|
+
| 值类型 | 序列化结果 | 说明 |
|
|
217
|
+
|--------|-----------|------|
|
|
218
|
+
| `null` / `undefined` | `null` | — |
|
|
219
|
+
| 有限数字 | `25` | 直接输出,无引号 |
|
|
220
|
+
| `NaN` / `Infinity` | `null` | 非有限数统一输出 null |
|
|
221
|
+
| `true` / `false` | `true` / `false` | — |
|
|
222
|
+
| 安全字符串 | `Alice` | 省略引号(见下方规则) |
|
|
223
|
+
| 非安全字符串 | `"hello world"` | 保留 JSON 引号和转义 |
|
|
224
|
+
| 嵌套对象 `{k: v}` | `{k:v}` | key 同样区分安全/非安全 |
|
|
225
|
+
| 数组 | 见下方 null 省略规则 | — |
|
|
226
|
+
|
|
227
|
+
#### 安全字符串(可省略引号的条件)
|
|
228
|
+
|
|
229
|
+
满足以下**全部**条件的字符串可省略引号,否则保留 `JSON.stringify` 转义:
|
|
230
|
+
|
|
231
|
+
1. 非空字符串
|
|
232
|
+
2. 不是关键字字面量:`null`、`true`、`false`
|
|
233
|
+
3. 不匹配数字模式:`/^-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?$/`(如 `"123"`、`"-3.14"`、`"1e10"` 均保留引号)
|
|
234
|
+
4. 不以数字或减号 `-` 开头
|
|
235
|
+
5. 不含空白、`[`、`]`、`{`、`}`、`,`、`:`、`"` 等字符
|
|
236
|
+
|
|
237
|
+
| 字符串 | 结果 | 原因 |
|
|
238
|
+
|--------|------|------|
|
|
239
|
+
| `"Alice"` | `Alice` | 安全,省略引号 |
|
|
240
|
+
| `"hello world"` | `"hello world"` | 含空格 |
|
|
241
|
+
| `"123"` | `"123"` | 看起来像数字 |
|
|
242
|
+
| `"-3.14"` | `"-3.14"` | 看起来像数字 |
|
|
243
|
+
| `"null"` | `"null"` | 关键字 |
|
|
244
|
+
| `""` | `""` | 空字符串 |
|
|
245
|
+
| `"-abc"` | `"-abc"` | 以减号开头 |
|
|
246
|
+
| `"a:b"` | `"a:b"` | 含冒号 |
|
|
247
|
+
|
|
248
|
+
#### 对象 key 引号规则
|
|
249
|
+
|
|
250
|
+
`schema` 中的嵌套对象 key 同样适用安全字符串判断:
|
|
251
|
+
|
|
252
|
+
```js
|
|
253
|
+
stringify({ schema: [{ profile: ['name', 'age'] }], data: [...] });
|
|
254
|
+
// {schema:[{profile:[name,age]}],data:[...]} ← profile 是安全 key,省略引号
|
|
255
|
+
|
|
256
|
+
stringify({ schema: [{ "my key": ['name'] }], data: [...] });
|
|
257
|
+
// {schema:[{"my key":[name]}],data:[...]} ← my key 含空格,保留引号
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### 数组 null 省略规则
|
|
261
|
+
|
|
262
|
+
数组中的 `null` / `undefined` 被省略为逗号空槽,不占文字体积:
|
|
263
|
+
|
|
264
|
+
| 原始数组 | 序列化结果 | 说明 |
|
|
265
|
+
|----------------------|------------|------|
|
|
266
|
+
| `["a", null, null]` | `[a,,]` | 尾部两个空槽 |
|
|
267
|
+
| `[null, 1, null]` | `[,1,]` | 前后空槽 |
|
|
268
|
+
| `[]` | `[]` | 空数组 |
|
|
269
|
+
| `[null]` | `[null]` | **特殊**:`[,]` 代表 2 个 null,因此单 null 保留文字 |
|
|
270
|
+
|
|
271
|
+
### `parse(text)`
|
|
272
|
+
|
|
273
|
+
解析 `stringify` 产生的文本,将省略的 `null` 恢复:
|
|
274
|
+
|
|
275
|
+
```js
|
|
276
|
+
const parsed = parse(text);
|
|
277
|
+
// deep-equal compressed
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
支持完整 JSON 类型的解析(字符串、数字、布尔、null、嵌套对象/数组),兼容转义字符和 Unicode。
|
|
281
|
+
|
|
282
|
+
## 完整使用示例
|
|
283
|
+
|
|
284
|
+
```js
|
|
285
|
+
import { compress, decompress, stringify, parse } from 'slimjson';
|
|
286
|
+
|
|
287
|
+
const data = [
|
|
288
|
+
{ name: '张三', age: 28, profile: { avatar: 'a.jpg', bio: 'Hello' } },
|
|
289
|
+
{ name: '李四', age: 35, profile: { avatar: 'b.jpg' } }, // 缺失 bio
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
// 压缩 → 文本化 → 解析 → 还原
|
|
293
|
+
const compressed = compress(data);
|
|
294
|
+
const text = stringify(compressed);
|
|
295
|
+
const parsed = parse(text);
|
|
296
|
+
const restored = decompress(parsed);
|
|
297
|
+
|
|
298
|
+
// restored 与 data 深度相等
|
|
299
|
+
|
|
300
|
+
// 启用 trimTrailingNulls 进一步压缩
|
|
301
|
+
const compressedTrim = compress(data, { trimTrailingNulls: true });
|
|
302
|
+
const textTrim = stringify(compressedTrim);
|
|
303
|
+
// textTrim 比 text 更短
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### 压缩率计算
|
|
307
|
+
|
|
308
|
+
```js
|
|
309
|
+
const originalSize = Buffer.byteLength(JSON.stringify(data));
|
|
310
|
+
const compressedSize = Buffer.byteLength(stringify(compress(data)));
|
|
311
|
+
const ratio = ((originalSize - compressedSize) / originalSize * 100).toFixed(1);
|
|
312
|
+
console.log(`压缩率: ${ratio}%`);
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## 压缩效果
|
|
316
|
+
|
|
317
|
+
基于 `compress-test.js` 基准测试的实际数据(18 组测试,所有 roundtrip 解压正确):
|
|
318
|
+
|
|
319
|
+
| 数据类型 | 对象数 | 原始大小 | 不 trim | 压缩率 | trim | 压缩率 | 差值 |
|
|
320
|
+
|---------|-------|---------|---------|-------|------|-------|------|
|
|
321
|
+
| 简单用户 | 100 | 14.69 KB | 8.69 KB | 40.82% | 8.69 KB | 40.82% | — |
|
|
322
|
+
| 简单用户 | 1,000 | 147.74 KB | 87.25 KB | 40.94% | 87.25 KB | 40.94% | — |
|
|
323
|
+
| 简单用户 | 10,000 | 1.45 MB | 881.58 KB | 40.71% | 881.58 KB | 40.71% | — |
|
|
324
|
+
| 嵌套用户(含 profile.social) | 100 | 23.41 KB | 15.28 KB | 34.74% | 15.24 KB | 34.87% | -33 B |
|
|
325
|
+
| 嵌套用户(含 profile.social) | 1,000 | 236.03 KB | 153.93 KB | 34.78% | 153.64 KB | 34.91% | -301 B |
|
|
326
|
+
| 嵌套用户(含 profile.social) | 5,000 | 1.16 MB | 777.89 KB | 34.58% | 776.42 KB | 34.70% | -1.47 KB |
|
|
327
|
+
| 订单(每单1-5商品) | 100 | 31.28 KB | 13.65 KB | 56.38% | 13.65 KB | 56.38% | — |
|
|
328
|
+
| 订单(每单1-5商品) | 500 | 163.18 KB | 70.83 KB | 56.59% | 70.83 KB | 56.59% | — |
|
|
329
|
+
| 订单(每单1-5商品) | 2,000 | 655.99 KB | 284.29 KB | 56.66% | 284.29 KB | 56.66% | — |
|
|
330
|
+
| 学校数据(2年级×2班×10生) | 4 | 12.26 KB | 5.25 KB | 57.20% | 5.23 KB | 57.36% | -21 B |
|
|
331
|
+
| 学校数据(6年级×4班×30生) | 24 | 217.73 KB | 89.71 KB | 58.80% | 89.31 KB | 58.98% | -406 B |
|
|
332
|
+
| 学校数据(6年级×6班×50生) | 36 | 539.64 KB | 222.56 KB | 58.76% | 221.66 KB | 58.92% | -923 B |
|
|
333
|
+
| 稀疏字段(100条×20字段) | 100 | 19.50 KB | 6.34 KB | 67.46% | 6.28 KB | 67.78% | -64 B |
|
|
334
|
+
| 稀疏字段(500条×30字段) | 500 | 143.26 KB | 45.09 KB | 68.52% | 44.78 KB | 68.74% | -326 B |
|
|
335
|
+
| 稀疏字段(2000条×50字段) | 2,000 | 957.96 KB | 294.69 KB | 69.24% | 293.54 KB | 69.36% | -1.15 KB |
|
|
336
|
+
| 深层嵌套(小) | 2 | 17.47 KB | 8.08 KB | 53.73% | 8.08 KB | 53.73% | — |
|
|
337
|
+
| 深层嵌套(中) | 3 | 141.89 KB | 64.55 KB | 54.50% | 64.55 KB | 54.50% | — |
|
|
338
|
+
| 深层嵌套(大) | 5 | 629.42 KB | 286.40 KB | 54.50% | 286.40 KB | 54.50% | — |
|
|
339
|
+
|
|
340
|
+
**结论:**
|
|
341
|
+
1. 字段名越长、数量越多,压缩效果越好
|
|
342
|
+
2. 对象数组(订单条目、学生列表)压缩效果显著(55–59%)
|
|
343
|
+
3. 稀疏字段压缩率最高 — 缺失字段的 null 通过空槽省略(67–69%)
|
|
344
|
+
4. `trimTrailingNulls` 在数据有缺失尾部字段时额外节省体积(最高 1.48 KB / 5000 条)
|
|
345
|
+
5. 数据完整无缺失字段时,trim 无额外收益
|
|
346
|
+
6. 深层嵌套结构能获得更好的压缩效果
|
|
347
|
+
7. `stringify` 省略引号进一步减少文本体积
|
|
348
|
+
|
|
349
|
+
## Token 效率对比
|
|
350
|
+
|
|
351
|
+
与其他格式的 token 消耗对比(基于 6 个数据集的实际测试)。
|
|
352
|
+
|
|
353
|
+
#### 混合结构赛道
|
|
354
|
+
|
|
355
|
+
含嵌套或半均匀结构的数据集。CSV 无法表示此类结构,已排除。
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
🛒 电商订单(嵌套结构) ┊ 表格化程度: 33%
|
|
359
|
+
│
|
|
360
|
+
slimjson ████████░░░░░░░░░░░░ 46,233 tokens
|
|
361
|
+
├─ vs JSON (−57.8%) 109,574 tokens
|
|
362
|
+
├─ vs JSON compact (−33.5%) 69,528 tokens
|
|
363
|
+
├─ vs TOON (−36.9%) 73,246 tokens
|
|
364
|
+
├─ vs YAML (−45.9%) 85,451 tokens
|
|
365
|
+
└─ vs XML (−62.5%) 123,272 tokens
|
|
366
|
+
|
|
367
|
+
📃 半均匀事件日志 ┊ 表格化程度: 50%
|
|
368
|
+
│
|
|
369
|
+
slimjson ██████████░░░░░░░░░░ 91,630 tokens
|
|
370
|
+
├─ vs JSON (−49.4%) 181,141 tokens
|
|
371
|
+
├─ vs JSON compact (−28.7%) 128,480 tokens
|
|
372
|
+
├─ vs TOON (−40.5%) 154,032 tokens
|
|
373
|
+
├─ vs YAML (−41.0%) 155,346 tokens
|
|
374
|
+
└─ vs XML (−55.5%) 205,796 tokens
|
|
375
|
+
|
|
376
|
+
🧩 深层嵌套配置 ┊ 表格化程度: 0%
|
|
377
|
+
│
|
|
378
|
+
slimjson ████████████░░░░░░░░ 547 tokens
|
|
379
|
+
├─ vs JSON (−39.6%) 905 tokens
|
|
380
|
+
├─ vs JSON compact (−0.9%) 552 tokens
|
|
381
|
+
├─ vs TOON (−11.5%) 618 tokens
|
|
382
|
+
├─ vs YAML (−17.4%) 662 tokens
|
|
383
|
+
└─ vs XML (−45.1%) 997 tokens
|
|
384
|
+
|
|
385
|
+
──────────────────────────────────── 合计 ────────────────────────────────────
|
|
386
|
+
slimjson █████████░░░░░░░░░░░ 138,410 tokens
|
|
387
|
+
├─ vs JSON (−52.5%) 291,620 tokens
|
|
388
|
+
├─ vs JSON compact (−30.3%) 198,560 tokens
|
|
389
|
+
├─ vs TOON (−39.3%) 227,896 tokens
|
|
390
|
+
├─ vs YAML (−42.7%) 241,459 tokens
|
|
391
|
+
└─ vs XML (−58.1%) 330,065 tokens
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
#### 纯表格赛道
|
|
395
|
+
|
|
396
|
+
扁平表格结构数据集,CSV 可适用。
|
|
397
|
+
|
|
398
|
+
```
|
|
399
|
+
👥 均匀员工记录 ┊ 表格化程度: 100%
|
|
400
|
+
│
|
|
401
|
+
CSV ████████████████████ 47,137 tokens
|
|
402
|
+
slimjson ████████████████████ 47,067 tokens (-0.1% vs CSV)
|
|
403
|
+
├─ vs JSON (−63.0%) 127,050 tokens
|
|
404
|
+
├─ vs JSON compact (−40.5%) 79,046 tokens
|
|
405
|
+
├─ vs TOON (−5.8%) 49,966 tokens
|
|
406
|
+
├─ vs YAML (−52.9%) 100,033 tokens
|
|
407
|
+
└─ vs XML (−67.9%) 146,596 tokens
|
|
408
|
+
|
|
409
|
+
📈 时间序列分析数据 ┊ 表格化程度: 100%
|
|
410
|
+
│
|
|
411
|
+
CSV ███████████████████░ 8,392 tokens
|
|
412
|
+
slimjson ████████████████████ 8,767 tokens (+4.5% vs CSV)
|
|
413
|
+
├─ vs JSON (−60.6%) 22,254 tokens
|
|
414
|
+
├─ vs JSON compact (−38.3%) 14,220 tokens
|
|
415
|
+
├─ vs TOON (−3.9%) 9,124 tokens
|
|
416
|
+
├─ vs YAML (−50.9%) 17,867 tokens
|
|
417
|
+
└─ vs XML (−67.1%) 26,625 tokens
|
|
418
|
+
|
|
419
|
+
⭐ Top 100 GitHub 仓库 ┊ 表格化程度: 100%
|
|
420
|
+
│
|
|
421
|
+
CSV ████████████████████ 8,512 tokens
|
|
422
|
+
slimjson ████████████████████ 8,550 tokens (+0.4% vs CSV)
|
|
423
|
+
├─ vs JSON (−43.5%) 15,144 tokens
|
|
424
|
+
├─ vs JSON compact (−25.4%) 11,454 tokens
|
|
425
|
+
├─ vs TOON (−2.2%) 8,744 tokens
|
|
426
|
+
├─ vs YAML (−34.9%) 13,128 tokens
|
|
427
|
+
└─ vs XML (−50.0%) 17,095 tokens
|
|
428
|
+
|
|
429
|
+
──────────────────────────────────── 合计 ────────────────────────────────────
|
|
430
|
+
CSV ████████████████████ 64,041 tokens
|
|
431
|
+
slimjson ████████████████████ 64,384 tokens (+0.5% vs CSV)
|
|
432
|
+
├─ vs JSON (−60.8%) 164,448 tokens
|
|
433
|
+
├─ vs JSON compact (−38.5%) 104,720 tokens
|
|
434
|
+
├─ vs TOON (−5.1%) 67,834 tokens
|
|
435
|
+
├─ vs YAML (−50.9%) 131,028 tokens
|
|
436
|
+
└─ vs XML (−66.2%) 190,316 tokens
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
> 在混合结构数据上,slimjson 比 JSON 节省 **52.5%** tokens;在纯表格数据上,与 CSV 基本持平(仅多 0.5%)。
|
|
440
|
+
|
|
441
|
+
## LLM 数据检索准确率
|
|
442
|
+
|
|
443
|
+
使用 209 道数据检索题测试不同格式下 LLM 的理解准确率。
|
|
444
|
+
|
|
445
|
+
#### 效率排名(每 1K tokens 的准确率)
|
|
446
|
+
|
|
447
|
+
```
|
|
448
|
+
slimjson ████████████████████ 44.4 acc%/1K tok │ 94.7% acc │ 2,134 tokens
|
|
449
|
+
TOON ███████████████░░░░░ 34.0 acc%/1K tok │ 92.8% acc │ 2,734 tokens
|
|
450
|
+
JSON compact ██████████████░░░░░░ 31.0 acc%/1K tok │ 95.2% acc │ 3,072 tokens
|
|
451
|
+
YAML ███████████░░░░░░░░░ 25.4 acc%/1K tok │ 94.3% acc │ 3,716 tokens
|
|
452
|
+
JSON ██████████░░░░░░░░░░ 21.1 acc%/1K tok │ 95.7% acc │ 4,538 tokens
|
|
453
|
+
XML ████████░░░░░░░░░░░░ 18.5 acc%/1K tok │ 95.7% acc │ 5,162 tokens
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
*效率分数 = (准确率% ÷ tokens) × 1,000,越高越好。*
|
|
457
|
+
|
|
458
|
+
> slimjson 准确率 **94.7%**(vs JSON 的 95.7%),同时节省 **53.0%** tokens。
|
|
459
|
+
|
|
460
|
+
#### 各模型准确率
|
|
461
|
+
|
|
462
|
+
```
|
|
463
|
+
deepseek-v4-flash
|
|
464
|
+
JSON ███████████████████░ 95.7% (200/209)
|
|
465
|
+
XML ███████████████████░ 95.7% (200/209)
|
|
466
|
+
JSON compact ███████████████████░ 95.2% (199/209)
|
|
467
|
+
→ slimjson ███████████████████░ 94.7% (198/209)
|
|
468
|
+
YAML ███████████████████░ 94.3% (197/209)
|
|
469
|
+
TOON ███████████████████░ 92.8% (194/209)
|
|
470
|
+
CSV ██████████████████░░ 91.7% (100/109)
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
#### 按题型准确率
|
|
474
|
+
|
|
475
|
+
| 题型 | JSON | XML | JSON compact | slimjson | YAML | TOON | CSV |
|
|
476
|
+
|------|------|-----|-------------|----------|------|------|-----|
|
|
477
|
+
| 字段检索 | 98.5% | 97.1% | 98.5% | 95.6% | 97.1% | 91.2% | 96.9% |
|
|
478
|
+
| 聚合计算 | 98.4% | 96.8% | 95.2% | 95.2% | 93.7% | 95.2% | 86.2% |
|
|
479
|
+
| 条件筛选 | 97.9% | 97.9% | 100.0% | 100.0% | 100.0% | 100.0% | 96.3% |
|
|
480
|
+
| 结构感知 | 88.0% | 92.0% | 84.0% | 92.0% | 88.0% | 88.0% | 87.5% |
|
|
481
|
+
| 结构验证 | 40.0% | 60.0% | 60.0% | 40.0% | 40.0% | 40.0% | 80.0% |
|
|
482
|
+
|
|
483
|
+
#### 测试数据集
|
|
484
|
+
|
|
485
|
+
| 数据集 | 行数 | 结构类型 | CSV 支持 |
|
|
486
|
+
|--------|------|----------|----------|
|
|
487
|
+
| 均匀员工记录 | 100 | 均匀 | ✓ |
|
|
488
|
+
| 电商订单(嵌套结构) | 50 | 嵌套 | ✗ |
|
|
489
|
+
| 时间序列分析数据 | 60 | 均匀 | ✓ |
|
|
490
|
+
| Top 100 GitHub 仓库 | 100 | 均匀 | ✓ |
|
|
491
|
+
| 半均匀事件日志 | 75 | 半均匀 | ✗ |
|
|
492
|
+
| 深层嵌套配置 | 11 | 深层 | ✗ |
|
|
493
|
+
|
|
494
|
+
## 开发
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
# 运行测试(192 个用例,100% 覆盖率)
|
|
498
|
+
npm test
|
|
499
|
+
|
|
500
|
+
# 运行压缩率基准测试(含 trim 对比)
|
|
501
|
+
node compress-test.js
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
## GitHub
|
|
505
|
+
|
|
506
|
+
[https://github.com/LastHeaven/slimjson](https://github.com/LastHeaven/slimjson)
|
|
507
|
+
|
|
508
|
+
## License
|
|
509
|
+
|
|
510
|
+
MIT
|