slimjson 1.0.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/test.js ADDED
@@ -0,0 +1,886 @@
1
+ /**
2
+ * 测试:compress / decompress v2 —— Jest 版
3
+ * 运行:npm test
4
+ */
5
+ const { compress, decompress, stringify, parse } = require('./compress');
6
+
7
+ // ---------- 测试数据 ----------
8
+
9
+ // 样例1
10
+ const src1 = [
11
+ {
12
+ "姓名": "张三", "年龄": 19,
13
+ "家人": [{ "姓名": "张四", "年龄": 40 }, { "姓名": "李五", "年龄": 41 }],
14
+ "伴侣": { "姓名": "王六", "年龄": 18 }
15
+ },
16
+ {
17
+ "姓名": "李小花", "年龄": 28,
18
+ "家人": [{ "姓名": "李大国", "年龄": 55 }, { "姓名": "王淑芬", "年龄": 53 }],
19
+ "伴侣": { "姓名": "赵明", "年龄": 30 }
20
+ }
21
+ ];
22
+
23
+ // 样例2
24
+ const src2 = [
25
+ { "姓名": ["张三", "李四", "王五"], "班级": "23班" },
26
+ { "姓名": ["李小花", "张晓", "李旺", "张思"], "班级": "24班" }
27
+ ];
28
+
29
+ // 场景3:第1个元素缺少"伴侣"字段(后端省略 null)
30
+ const src3 = [
31
+ { "姓名": "张三", "年龄": 19, "家人": [{ "姓名": "张四", "年龄": 40 }] },
32
+ { "姓名": "李小花", "年龄": 28, "家人": [{ "姓名": "李大国", "年龄": 55 }], "伴侣": { "姓名": "赵明", "年龄": 30 } }
33
+ ];
34
+ const src3Normalized = [ // 所有对象补齐缺失 key 为 null
35
+ { "姓名": "张三", "年龄": 19, "家人": [{ "姓名": "张四", "年龄": 40 }], "伴侣": null },
36
+ { "姓名": "李小花", "年龄": 28, "家人": [{ "姓名": "李大国", "年龄": 55 }], "伴侣": { "姓名": "赵明", "年龄": 30 } }
37
+ ];
38
+
39
+ // 场景4:嵌套对象内部子 key 不全(第1个家人中缺少"关系")
40
+ const src4 = [
41
+ {
42
+ "姓名": "张三", "年龄": 19,
43
+ "家人": [
44
+ { "姓名": "张四", "年龄": 40 },
45
+ { "姓名": "李五", "年龄": 41, "关系": "母亲" }
46
+ ]
47
+ }
48
+ ];
49
+ const src4Normalized = [
50
+ {
51
+ "姓名": "张三", "年龄": 19,
52
+ "家人": [
53
+ { "姓名": "张四", "年龄": 40, "关系": null },
54
+ { "姓名": "李五", "年龄": 41, "关系": "母亲" }
55
+ ]
56
+ }
57
+ ];
58
+
59
+ // 场景5:复杂嵌套 — 年级,班级,班主任,其他老师,学生(学生含成绩子对象)
60
+ const src5 = [
61
+ {
62
+ "年级": "一年级",
63
+ "班级": "1班",
64
+ "班主任": { "姓名": "王老师", "年龄": 35, "科目": "语文" },
65
+ "其他老师": [
66
+ { "姓名": "李老师", "年龄": 28, "科目": "数学" },
67
+ { "姓名": "张老师", "年龄": 40, "科目": "英语" }
68
+ ],
69
+ "学生": [
70
+ { "姓名": "小明", "年龄": 7, "性别": "男", "成绩": { "语文": 95, "数学": 88, "英语": 92 } },
71
+ { "姓名": "小红", "年龄": 7, "性别": "女", "成绩": { "语文": 90, "数学": 96, "英语": 89 } }
72
+ ]
73
+ },
74
+ {
75
+ "年级": "一年级",
76
+ "班级": "2班",
77
+ "班主任": { "姓名": "赵老师", "年龄": 42, "科目": "数学" },
78
+ "其他老师": [
79
+ { "姓名": "钱老师", "年龄": 31, "科目": "语文" },
80
+ { "姓名": "孙老师", "年龄": 33, "科目": "英语" },
81
+ { "姓名": "周老师", "年龄": 26, "科目": "体育" }
82
+ ],
83
+ "学生": [
84
+ { "姓名": "小刚", "年龄": 7, "性别": "男", "成绩": { "语文": 78, "数学": 85 } },
85
+ { "姓名": "小丽", "年龄": 7, "性别": "女", "成绩": { "语文": 99, "数学": 100, "英语": 97 } },
86
+ { "姓名": "小强", "年龄": 8, "性别": "男", "成绩": { "数学": 60 } }
87
+ ]
88
+ }
89
+ ];
90
+
91
+ const src5Normalized = [
92
+ {
93
+ "年级": "一年级",
94
+ "班级": "1班",
95
+ "班主任": { "姓名": "王老师", "年龄": 35, "科目": "语文" },
96
+ "其他老师": [
97
+ { "姓名": "李老师", "年龄": 28, "科目": "数学" },
98
+ { "姓名": "张老师", "年龄": 40, "科目": "英语" }
99
+ ],
100
+ "学生": [
101
+ { "姓名": "小明", "年龄": 7, "性别": "男", "成绩": { "语文": 95, "数学": 88, "英语": 92 } },
102
+ { "姓名": "小红", "年龄": 7, "性别": "女", "成绩": { "语文": 90, "数学": 96, "英语": 89 } }
103
+ ]
104
+ },
105
+ {
106
+ "年级": "一年级",
107
+ "班级": "2班",
108
+ "班主任": { "姓名": "赵老师", "年龄": 42, "科目": "数学" },
109
+ "其他老师": [
110
+ { "姓名": "钱老师", "年龄": 31, "科目": "语文" },
111
+ { "姓名": "孙老师", "年龄": 33, "科目": "英语" },
112
+ { "姓名": "周老师", "年龄": 26, "科目": "体育" }
113
+ ],
114
+ "学生": [
115
+ { "姓名": "小刚", "年龄": 7, "性别": "男", "成绩": { "语文": 78, "数学": 85, "英语": null } },
116
+ { "姓名": "小丽", "年龄": 7, "性别": "女", "成绩": { "语文": 99, "数学": 100, "英语": 97 } },
117
+ { "姓名": "小强", "年龄": 8, "性别": "男", "成绩": { "语文": null, "数学": 60, "英语": null } }
118
+ ]
119
+ }
120
+ ];
121
+
122
+ // ============================================================
123
+ // 测试
124
+ // ============================================================
125
+
126
+ describe('compress / decompress', () => {
127
+
128
+ describe('样例1:基础嵌套', () => {
129
+ const r1 = compress(src1);
130
+
131
+ test('compress 不出错', () => {
132
+ expect(r1.keys).toBeDefined();
133
+ expect(r1.rows).toBeDefined();
134
+ });
135
+
136
+ test('与预期 keys 一致', () => {
137
+ expect(r1.keys).toEqual([
138
+ "姓名", "年龄", { "家人": ["姓名", "年龄"] }, { "伴侣": ["姓名", "年龄"] }
139
+ ]);
140
+ });
141
+
142
+ test('还原一致', () => {
143
+ expect(decompress(r1)).toEqual(src1);
144
+ });
145
+ });
146
+
147
+ describe('样例2:原始类型数组字段', () => {
148
+ const r2 = compress(src2);
149
+
150
+ test('与预期 keys 一致', () => {
151
+ expect(r2.keys).toEqual(["姓名", "班级"]);
152
+ });
153
+
154
+ test('与预期 rows 一致', () => {
155
+ expect(r2.rows).toEqual([
156
+ [["张三", "李四", "王五"], "23班"],
157
+ [["李小花", "张晓", "李旺", "张思"], "24班"]
158
+ ]);
159
+ });
160
+
161
+ test('还原一致', () => {
162
+ expect(decompress(r2)).toEqual(src2);
163
+ });
164
+ });
165
+
166
+ describe('场景3:顶层缺失字段(后端省略 null)', () => {
167
+ const r3 = compress(src3);
168
+
169
+ test('keys 包含伴侣', () => {
170
+ expect(r3.keys).toEqual(["姓名", "年龄", { "家人": ["姓名", "年龄"] }, { "伴侣": ["姓名", "年龄"] }]);
171
+ });
172
+
173
+ test('row[0] 伴侣位置为 null', () => {
174
+ expect(r3.rows[0][3]).toBeNull();
175
+ });
176
+
177
+ test('row[1] 伴侣正常', () => {
178
+ expect(r3.rows[1][3]).toEqual(["赵明", 30]);
179
+ });
180
+
181
+ test('还原(null 补齐)', () => {
182
+ expect(decompress(r3)).toEqual(src3Normalized);
183
+ });
184
+
185
+ test('张三的伴侣为 null', () => {
186
+ expect(decompress(r3)[0].伴侣).toBeNull();
187
+ });
188
+
189
+ test('李小花的伴侣正常', () => {
190
+ expect(decompress(r3)[1].伴侣.姓名).toBe("赵明");
191
+ });
192
+ });
193
+
194
+ describe('场景4:嵌套对象子 key 不全', () => {
195
+ const r4 = compress(src4);
196
+
197
+ test('keys 中家人包含"关系"', () => {
198
+ expect(r4.keys).toEqual(["姓名", "年龄", { "家人": ["姓名", "年龄", "关系"] }]);
199
+ });
200
+
201
+ test('家人[0] 关系为 null', () => {
202
+ expect(r4.rows[0][2][0][2]).toBeNull();
203
+ });
204
+
205
+ test('家人[1] 关系正常', () => {
206
+ expect(r4.rows[0][2][1][2]).toBe("母亲");
207
+ });
208
+
209
+ test('还原(null 补齐)', () => {
210
+ expect(decompress(r4)).toEqual(src4Normalized);
211
+ });
212
+
213
+ test('第1个家人关系为 null', () => {
214
+ expect(decompress(r4)[0].家人[0].关系).toBeNull();
215
+ });
216
+
217
+ test('第2个家人关系正常', () => {
218
+ expect(decompress(r4)[0].家人[1].关系).toBe("母亲");
219
+ });
220
+ });
221
+
222
+ describe('场景5:复杂嵌套(年级/班级/班主任/其他老师/学生+成绩)', () => {
223
+ const r5 = compress(src5);
224
+
225
+ // —— keys ——
226
+ test('keys 顶层包含年级/班级', () => {
227
+ expect(r5.keys.slice(0, 2)).toEqual(["年级", "班级"]);
228
+ });
229
+
230
+ test('keys 包含嵌套班主任', () => {
231
+ expect(JSON.stringify(r5.keys[2])).toContain('班主任');
232
+ });
233
+
234
+ test('keys 包含嵌套其他老师', () => {
235
+ expect(JSON.stringify(r5.keys[3])).toContain('其他老师');
236
+ });
237
+
238
+ test('keys 包含嵌套学生', () => {
239
+ expect(JSON.stringify(r5.keys[4])).toContain('学生');
240
+ });
241
+
242
+ test('keys 完整结构', () => {
243
+ expect(r5.keys).toEqual([
244
+ "年级",
245
+ "班级",
246
+ { "班主任": ["姓名", "年龄", "科目"] },
247
+ { "其他老师": ["姓名", "年龄", "科目"] },
248
+ { "学生": ["姓名", "年龄", "性别", { "成绩": ["语文", "数学", "英语"] }] }
249
+ ]);
250
+ });
251
+
252
+ // —— rows 数量 ——
253
+ test('1班班主任正常', () => {
254
+ expect(r5.rows[0][2]).toEqual(["王老师", 35, "语文"]);
255
+ });
256
+
257
+ test('1班其他老师数量=2', () => {
258
+ expect(r5.rows[0][3].length).toBe(2);
259
+ });
260
+
261
+ test('1班学生数量=2', () => {
262
+ expect(r5.rows[0][4].length).toBe(2);
263
+ });
264
+
265
+ test('2班班主任正常', () => {
266
+ expect(r5.rows[1][2]).toEqual(["赵老师", 42, "数学"]);
267
+ });
268
+
269
+ test('2班其他老师数量=3', () => {
270
+ expect(r5.rows[1][3].length).toBe(3);
271
+ });
272
+
273
+ test('2班学生数量=3', () => {
274
+ expect(r5.rows[1][4].length).toBe(3);
275
+ });
276
+
277
+ // —— 成绩子对象缺失 key 补 null ——
278
+ // 学生 keys: ["姓名", "年龄", "性别", { "成绩": ["语文", "数学", "英语"] }]
279
+ // 学生 row: [姓名, 年龄, 性别, [语文, 数学, 英语]]
280
+ test('小刚英语成绩为 null(缺英语字段)', () => {
281
+ expect(r5.rows[1][4][0][3][2]).toBeNull();
282
+ });
283
+
284
+ test('小刚语文成绩正常', () => {
285
+ expect(r5.rows[1][4][0][3][0]).toBe(78);
286
+ });
287
+
288
+ test('小强语文成绩为 null(缺语文字段)', () => {
289
+ expect(r5.rows[1][4][2][3][0]).toBeNull();
290
+ });
291
+
292
+ test('小强数学成绩正常', () => {
293
+ expect(r5.rows[1][4][2][3][1]).toBe(60);
294
+ });
295
+
296
+ test('小强英语成绩为 null(缺英语字段)', () => {
297
+ expect(r5.rows[1][4][2][3][2]).toBeNull();
298
+ });
299
+
300
+ // —— 还原验证 ——
301
+ test('还原后与 normalized 一致', () => {
302
+ expect(decompress(r5)).toEqual(src5Normalized);
303
+ });
304
+
305
+ test('1班年级正确', () => {
306
+ expect(decompress(r5)[0]["年级"]).toBe("一年级");
307
+ });
308
+
309
+ test('2班第1个学生英语为 null', () => {
310
+ expect(decompress(r5)[1]["学生"][0]["成绩"]["英语"]).toBeNull();
311
+ });
312
+
313
+ test('2班第3个学生语文为 null', () => {
314
+ expect(decompress(r5)[1]["学生"][2]["成绩"]["语文"]).toBeNull();
315
+ });
316
+ });
317
+
318
+ /* =========================================================
319
+ 边界 / 异常 — 冲击 100% 覆盖率
320
+ ========================================================= */
321
+ describe('边界 / 异常', () => {
322
+
323
+ test('compress 空数组 → 返回原值', () => {
324
+ expect(compress([])).toEqual([]);
325
+ });
326
+
327
+ test('compress(null) → 返回原值', () => {
328
+ expect(compress(null)).toBeNull();
329
+ });
330
+
331
+ test('compress 非数组 → 返回原值', () => {
332
+ expect(compress('hello')).toBe('hello');
333
+ });
334
+
335
+ test('源数组含 null 元素', () => {
336
+ const src = [{ name: 'a', age: 10 }, null, { name: 'b' }];
337
+ const r = compress(src);
338
+ expect(r.keys).toEqual(['name', 'age']);
339
+ expect(r.rows[1]).toEqual([null, null]);
340
+ expect(r.rows[0]).toEqual(['a', 10]);
341
+ expect(r.rows[2]).toEqual(['b', null]);
342
+ });
343
+
344
+ test('空对象数组字段', () => {
345
+ const src = [{ name: 'test', items: [] }];
346
+ const r = compress(src);
347
+ expect(r.keys).toEqual(['name', 'items']);
348
+ expect(r.rows[0]).toEqual(['test', []]);
349
+ expect(decompress(r)).toEqual(src);
350
+ });
351
+
352
+ test('嵌套对象数组中含 null 元素(首元 null→退化为 primitive-array)', () => {
353
+ const src = [{ name: 'a', kids: [null, { name: 'child' }] }];
354
+ const r = compress(src);
355
+ // v[0]===null → 走 primitive-array 分支,不拆解子 key
356
+ expect(r.keys).toEqual(['name', 'kids']);
357
+ expect(r.rows[0]).toEqual(['a', [null, { name: 'child' }]]);
358
+ expect(decompress(r)).toEqual(src);
359
+ });
360
+
361
+ test('字段值为 undefined 转 null', () => {
362
+ const src = [{ a: undefined, b: 1 }];
363
+ const r = compress(src);
364
+ expect(r.keys).toEqual(['a', 'b']);
365
+ expect(r.rows[0]).toEqual([null, 1]);
366
+ });
367
+
368
+ test('字段值为 null 保留 null', () => {
369
+ const src = [{ a: 1, b: null }];
370
+ const r = compress(src);
371
+ expect(r.rows[0][1]).toBeNull();
372
+ expect(decompress(r)[0].b).toBeNull();
373
+ });
374
+
375
+ test('对象数组中含非对象元素(item typeof!=="object" 分支)', () => {
376
+ const src = [{ name: 'x', kids: [{ name: 'kid' }, 'string', null] }];
377
+ const r = compress(src);
378
+ // 首元素是对象 → object-array;非对象元素在 buildKeys 收集时被跳过
379
+ expect(r.keys).toEqual(['name', { kids: ['name'] }]);
380
+ // 非对象元素 → buildRow 返回 [null],所以 null 元素也是 [null] 而非 null
381
+ expect(r.rows[0][1]).toEqual([['kid'], [null], [null]]);
382
+ });
383
+
384
+ test('原数组含数字元素(typeof obj!=="object" 各分支)', () => {
385
+ const src = [42, { name: 'a' }, true, { name: 'b' }];
386
+ const r = compress(src);
387
+ expect(r.keys).toEqual(['name']);
388
+ // 数字/布尔不是对象 → buildRow 返回 [null]
389
+ expect(r.rows[0]).toEqual([null]);
390
+ expect(r.rows[1]).toEqual(['a']);
391
+ expect(r.rows[2]).toEqual([null]);
392
+ expect(r.rows[3]).toEqual(['b']);
393
+ });
394
+
395
+ test('getValueKind:数组首元素也是数组 → primitive-array', () => {
396
+ const src = [{ name: 'x', matrix: [[1, 2], [3, 4]] }];
397
+ const r = compress(src);
398
+ expect(r.keys).toEqual(['name', 'matrix']);
399
+ expect(r.rows[0]).toEqual(['x', [[1, 2], [3, 4]]]);
400
+ expect(decompress(r)).toEqual(src);
401
+ });
402
+
403
+ test('普通原始类型数组 [1,2,3] 不拆解', () => {
404
+ const src = [{ name: 'x', scores: [1, 2, 3] }];
405
+ const r = compress(src);
406
+ expect(r.keys).toEqual(['name', 'scores']);
407
+ expect(r.rows[0]).toEqual(['x', [1, 2, 3]]);
408
+ expect(decompress(r)).toEqual(src);
409
+ });
410
+
411
+ test('非对象元素 + 嵌套 key(buildRow line 108 三元 false 分支)', () => {
412
+ const src = [42, { name: 'a', detail: { x: 1 } }];
413
+ const r = compress(src);
414
+ expect(r.keys).toEqual(['name', { detail: ['x'] }]);
415
+ // 42 不是对象 → 所有 key(包括嵌套)都取 undefined → null
416
+ expect(r.rows[0]).toEqual([null, null]);
417
+ expect(r.rows[1]).toEqual(['a', [1]]);
418
+ });
419
+
420
+ test('非对象元素 + 对象数组(buildKeys line 73 typeof false 分支)', () => {
421
+ const src = [42, { items: [{ name: 'a' }] }];
422
+ const r = compress(src);
423
+ expect(r.keys).toEqual([{ items: ['name'] }]);
424
+ // 42 不是对象 → buildRow 返回 [null]
425
+ expect(r.rows[0]).toEqual([null]);
426
+ expect(r.rows[1]).toEqual([[['a']]]);
427
+ });
428
+
429
+ test('字段值为 null 且 repValue 为 object-array(line 75 Array.isArray false 分支)', () => {
430
+ const src = [
431
+ { items: [{ name: 'a' }] }, // items 是 object-array
432
+ { items: null } // items 为 null → !Array.isArray
433
+ ];
434
+ const r = compress(src);
435
+ expect(r.keys).toEqual([{ items: ['name'] }]);
436
+ expect(r.rows[0]).toEqual([[['a']]]);
437
+ expect(r.rows[1]).toEqual([null]);
438
+ });
439
+
440
+ });
441
+
442
+ /* =========================================================
443
+ stringify / parse — 省略 null 文本化 与 还原
444
+ ========================================================= */
445
+ describe('stringify / parse', () => {
446
+
447
+ // ---- 基础数组序列化 ----
448
+ describe('基础数组规则', () => {
449
+ test('全 null 数组 → [,,]', () => {
450
+ expect(stringify([null, null, null])).toBe('[,,]');
451
+ });
452
+
453
+ test('["null", null, null] → ["null",,]', () => {
454
+ expect(stringify(['null', null, null])).toBe('["null",,]');
455
+ });
456
+
457
+ test('[null, 1, null] → [,1,]', () => {
458
+ expect(stringify([null, 1, null])).toBe('[,1,]');
459
+ });
460
+
461
+ test('[null, "a", null, 2] → [,a,,2]', () => {
462
+ expect(stringify([null, 'a', null, 2])).toBe('[,a,,2]');
463
+ });
464
+
465
+ test('无 null → 正常', () => {
466
+ expect(stringify([1, 2, 3])).toBe('[1,2,3]');
467
+ expect(stringify(['a', 'b', 'c'])).toBe('[a,b,c]');
468
+ });
469
+
470
+ test('空数组 → []', () => {
471
+ expect(stringify([])).toBe('[]');
472
+ });
473
+
474
+ test('单元素 null → [null](无法用逗号表示)', () => {
475
+ expect(stringify([null])).toBe('[null]');
476
+ });
477
+ });
478
+
479
+ // ---- 基础数组解析 ----
480
+ describe('基础数组解析', () => {
481
+ test('[,,] → [null, null, null]', () => {
482
+ expect(parse('[,,]')).toEqual([null, null, null]);
483
+ });
484
+
485
+ test('["null",,] → ["null", null, null]', () => {
486
+ expect(parse('["null",,]')).toEqual(['null', null, null]);
487
+ });
488
+
489
+ test('[,1,] → [null, 1, null]', () => {
490
+ expect(parse('[,1,]')).toEqual([null, 1, null]);
491
+ });
492
+
493
+ test('[,"a",,2] → [null, "a", null, 2]', () => {
494
+ expect(parse('[,"a",,2]')).toEqual([null, 'a', null, 2]);
495
+ });
496
+
497
+ test('无 null → 原样', () => {
498
+ expect(parse('[1,2,3]')).toEqual([1, 2, 3]);
499
+ expect(parse('["a","b","c"]')).toEqual(['a', 'b', 'c']);
500
+ });
501
+
502
+ test('空数组', () => {
503
+ expect(parse('[]')).toEqual([]);
504
+ });
505
+
506
+ test('[,] → [null, null](两 null)', () => {
507
+ expect(parse('[,]')).toEqual([null, null]);
508
+ });
509
+
510
+ test('[null] → [null](单 null 保留文字)', () => {
511
+ expect(parse('[null]')).toEqual([null]);
512
+ });
513
+ });
514
+
515
+ // ---- 嵌套数组 ----
516
+ describe('嵌套数组', () => {
517
+ test('[[1,2],,] → [[1,2], null, null]', () => {
518
+ expect(stringify([[1, 2], null, null])).toBe('[[1,2],,]');
519
+ expect(parse('[[1,2],,]')).toEqual([[1, 2], null, null]);
520
+ });
521
+
522
+ test('[[null,"a"],["b",null]] 往返', () => {
523
+ const arr = [[null, 'a'], ['b', null]];
524
+ expect(parse(stringify(arr))).toEqual(arr);
525
+ });
526
+
527
+ test('[[null,null,null]] → [[,,]]', () => {
528
+ expect(stringify([[null, null, null]])).toBe('[[,,]]');
529
+ expect(parse('[[,,]]')).toEqual([[null, null, null]]);
530
+ });
531
+
532
+ test('三层嵌套 [,[,],] 往返', () => {
533
+ const arr = [null, [null], null];
534
+ expect(parse(stringify(arr))).toEqual(arr);
535
+ });
536
+ });
537
+
538
+ // ---- 对象序列化 ----
539
+ describe('对象', () => {
540
+ test('简单对象', () => {
541
+ expect(stringify({ a: 1, b: null, c: 'x' }))
542
+ .toBe('{a:1,b:null,c:x}');
543
+ expect(parse('{a:1,b:null,c:x}')).toEqual({ a: 1, b: null, c: 'x' });
544
+ });
545
+
546
+ test('对象中数组字段含 null → 省略', () => {
547
+ const obj = { name: 'test', items: [1, null, 3] };
548
+ const str = stringify(obj);
549
+ expect(str).toBe('{name:test,items:[1,,3]}');
550
+ expect(parse(str)).toEqual(obj);
551
+ });
552
+
553
+ test('对象中嵌套数组全部 null(2个)', () => {
554
+ const obj = { a: [null, null] };
555
+ expect(stringify(obj)).toBe('{a:[,]}');
556
+ expect(parse(stringify(obj))).toEqual(obj);
557
+ });
558
+
559
+ test('空对象', () => {
560
+ expect(stringify({})).toBe('{}');
561
+ expect(parse('{}')).toEqual({});
562
+ });
563
+ });
564
+
565
+ // ---- 与 compress/decompress 联用 ----
566
+ describe('与 compress/decompress 联用', () => {
567
+ test('样例1 往返:compress → stringify → parse → decompress', () => {
568
+ const cmp = compress(src1);
569
+ const str = stringify(cmp);
570
+ const restored = parse(str);
571
+ expect(restored).toEqual(cmp);
572
+ expect(decompress(restored)).toEqual(src1);
573
+ });
574
+
575
+ test('样例2 往返', () => {
576
+ const cmp = compress(src2);
577
+ const str = stringify(cmp);
578
+ const restored = parse(str);
579
+ expect(restored).toEqual(cmp);
580
+ expect(decompress(restored)).toEqual(src2);
581
+ });
582
+
583
+ test('场景3(缺失字段→null)往返', () => {
584
+ const cmp = compress(src3);
585
+ const str = stringify(cmp);
586
+ const restored = parse(str);
587
+ expect(restored).toEqual(cmp);
588
+ expect(decompress(restored)).toEqual(src3Normalized);
589
+ });
590
+
591
+ test('场景4(嵌套子 key 不全)往返', () => {
592
+ const cmp = compress(src4);
593
+ const str = stringify(cmp);
594
+ const restored = parse(str);
595
+ expect(restored).toEqual(cmp);
596
+ expect(decompress(restored)).toEqual(src4Normalized);
597
+ });
598
+
599
+ test('场景5(复杂嵌套+缺失成绩)往返', () => {
600
+ const cmp = compress(src5);
601
+ const str = stringify(cmp);
602
+ const restored = parse(str);
603
+ expect(restored).toEqual(cmp);
604
+ expect(decompress(restored)).toEqual(src5Normalized);
605
+ });
606
+
607
+ test('stringify 输出不含 null 关键字的实例(验证省略效果)', () => {
608
+ // 构造一个有大量 null 的压缩结构,验证文本化后不含 "null"
609
+ const src = [
610
+ { name: 'a', extra: null },
611
+ { name: 'b', extra: null }
612
+ ];
613
+ const cmp = compress(src);
614
+ const str = stringify(cmp);
615
+ // rows 中的 null 应该被省略
616
+ // rows: [["a",null],["b",null]] → [["a",],["b",]]
617
+ expect(str).not.toContain('null');
618
+ });
619
+ });
620
+
621
+ // ---- 边界 / 特殊值 ----
622
+ describe('边界 / 特殊值', () => {
623
+ test('含有布尔值', () => {
624
+ const obj = { a: true, b: false, c: [true, null, false] };
625
+ expect(parse(stringify(obj))).toEqual(obj);
626
+ });
627
+
628
+ test('含有数字(含负数、小数、科学计数法)', () => {
629
+ const obj = { nums: [-1, 0, 3.14, 1e10, null] };
630
+ expect(parse(stringify(obj))).toEqual(obj);
631
+ });
632
+
633
+ test('含有转义字符串', () => {
634
+ const obj = { msg: 'hello "world"\n\t\\' };
635
+ expect(parse(stringify(obj))).toEqual(obj);
636
+ });
637
+
638
+ test('字符串 "null" 与真正的 null 能区分', () => {
639
+ const arr = ['null', null, 'null'];
640
+ const str = stringify(arr);
641
+ expect(str).toBe('["null",,"null"]');
642
+ expect(parse(str)).toEqual(['null', null, 'null']);
643
+ });
644
+
645
+ test('NaN → null', () => {
646
+ const str = stringify([NaN]);
647
+ expect(str).toBe('[null]');
648
+ expect(parse(str)).toEqual([null]);
649
+ });
650
+
651
+ test('undefined 视为 null', () => {
652
+ const str = stringify([undefined, 1]);
653
+ expect(str).toBe('[,1]');
654
+ expect(parse(str)).toEqual([null, 1]);
655
+ });
656
+
657
+ test('全部 null 的嵌套结构', () => {
658
+ const arr = [null, [null, null, null], null];
659
+ const str = stringify(arr);
660
+ expect(parse(str)).toEqual(arr);
661
+ });
662
+
663
+ test('空行 / 空白容忍', () => {
664
+ expect(parse(' [ 1 , , 3 ] ')).toEqual([1, null, 3]);
665
+ });
666
+
667
+ test('parse 对非法输入抛错', () => {
668
+ expect(() => parse('not json')).toThrow();
669
+ expect(() => parse('[1,,')).toThrow();
670
+ });
671
+
672
+ // ---- 覆盖率补齐 ----
673
+ test('非常规类型(Symbol)→ null', () => {
674
+ expect(stringify([Symbol('x')])).toBe('[null]');
675
+ });
676
+
677
+ test('转义字符 \\/ \\b \\f \\r', () => {
678
+ // 这些转义只在 parse 直接输入时出现(stringify 用 JSON.stringify 会直接输出字符)
679
+ expect(parse('["a\\/b"]')).toEqual(['a/b']);
680
+ expect(parse('["a\\bb"]')).toEqual(['a\bb']);
681
+ expect(parse('["a\\fb"]')).toEqual(['a\fb']);
682
+ expect(parse('["a\\rb"]')).toEqual(['a\rb']);
683
+ });
684
+
685
+ test('unicode 转义 \\uXXXX', () => {
686
+ expect(parse('["\\u0041"]')).toEqual(['A']);
687
+ expect(parse('["\\u4e2d"]')).toEqual(['中']);
688
+ });
689
+
690
+ test('未闭合引号报错', () => {
691
+ expect(() => parse('"hello')).toThrow();
692
+ });
693
+
694
+ test('数组中非法字符报错', () => {
695
+ expect(() => parse('[1x]')).toThrow();
696
+ });
697
+
698
+ test('对象中非法分隔符报错', () => {
699
+ expect(() => parse('{key:1x}')).toThrow();
700
+ });
701
+
702
+ test('转义字符 default 分支(非标准转义)', () => {
703
+ // \q 不是标准 JSON 转义,走 default 分支
704
+ expect(parse('["a\\qb"]')).toEqual(['aqb']);
705
+ });
706
+
707
+ test('科学计数法 e+ e-', () => {
708
+ expect(parse('[1e+10,1.5E-3]')).toEqual([1e+10, 1.5e-3]);
709
+ });
710
+
711
+ test('科学计数法 e-(单独测 - 分支)', () => {
712
+ expect(parse('[1e-3]')).toEqual([0.001]);
713
+ });
714
+
715
+ test('过大数字 → null', () => {
716
+ expect(parse('[1e1000]')).toEqual([null]);
717
+ });
718
+
719
+ test('对象 key 为 null/true/false(bare string 防御分支)', () => {
720
+ expect(parse('{null:1,true:2,false:3}'))
721
+ .toEqual({ null: 1, true: 2, false: 3 });
722
+ });
723
+
724
+ test('对象缺冒号报错', () => {
725
+ expect(() => parse('{key}')).toThrow();
726
+ });
727
+
728
+ test('对象 key 为空报错', () => {
729
+ expect(() => parse('{:1}')).toThrow();
730
+ });
731
+ });
732
+
733
+ });
734
+
735
+ /* =========================================================
736
+ stringify 省略引号测试
737
+ ========================================================= */
738
+ describe('stringify 省略引号', () => {
739
+
740
+ describe('基础:省略引号', () => {
741
+ test('安全字符串省略引号', () => {
742
+ expect(stringify(['hello'])).toBe('[hello]');
743
+ });
744
+
745
+ test('含空格的字符串保留引号', () => {
746
+ expect(stringify(['hello world'])).toBe('["hello world"]');
747
+ });
748
+
749
+ test('含特殊字符的字符串保留引号', () => {
750
+ expect(stringify(['a,b'])).toBe('["a,b"]');
751
+ expect(stringify(['a:b'])).toBe('["a:b"]');
752
+ expect(stringify(['[a]'])).toBe('["[a]"]');
753
+ });
754
+
755
+ test('空字符串保留引号', () => {
756
+ expect(stringify([''])).toBe('[""]');
757
+ });
758
+
759
+ test('null/true/false 保留引号', () => {
760
+ expect(stringify(['null'])).toBe('["null"]');
761
+ expect(stringify(['true'])).toBe('["true"]');
762
+ expect(stringify(['false'])).toBe('["false"]');
763
+ });
764
+
765
+ test('数字字符串保留引号', () => {
766
+ expect(stringify(['123'])).toBe('["123"]');
767
+ expect(stringify(['-5'])).toBe('["-5"]');
768
+ expect(stringify(['3.14'])).toBe('["3.14"]');
769
+ });
770
+
771
+ test('数字开头的中文字符串保留引号', () => {
772
+ // "23班" 以数字开头,parseValue 会误入 parseNumber 分支 → 必须保留引号
773
+ expect(stringify(['23班'])).toBe('["23班"]');
774
+ });
775
+
776
+ test('中文字符串省略引号', () => {
777
+ expect(stringify(['你好'])).toBe('[你好]');
778
+ expect(stringify(['张三'])).toBe('[张三]');
779
+ });
780
+ });
781
+
782
+ describe('数组中的省略引号', () => {
783
+ test('数组元素全部省略引号', () => {
784
+ expect(stringify(['a', 'b', 'c'])).toBe('[a,b,c]');
785
+ });
786
+
787
+ test('混合:安全的省略,不安全的保留', () => {
788
+ expect(stringify(['hello', 'hello world', 'a,b']))
789
+ .toBe('[hello,"hello world","a,b"]');
790
+ });
791
+
792
+ test('null 省略规则仍然生效', () => {
793
+ expect(stringify(['a', null, 'b'])).toBe('[a,,b]');
794
+ });
795
+ });
796
+
797
+ describe('对象中的省略引号', () => {
798
+ test('对象 key 和值都省略引号', () => {
799
+ expect(stringify({ name: 'alice', age: 25 }))
800
+ .toBe('{name:alice,age:25}');
801
+ });
802
+
803
+ test('仅安全 key 省略,不安全 key 保留', () => {
804
+ expect(stringify({ 'hello world': 1 }))
805
+ .toBe('{"hello world":1}');
806
+ });
807
+
808
+ test('不安全值保留引号、安全 key 省略', () => {
809
+ expect(stringify({ name: 'hello world' }))
810
+ .toBe('{name:"hello world"}');
811
+ });
812
+ });
813
+
814
+ describe('与 compress/decompress 联用', () => {
815
+ test('compress → stringify → parse → decompress', () => {
816
+ const data = [
817
+ { name: '张三', age: 25, city: '北京' },
818
+ { name: '李四', age: 30 }
819
+ ];
820
+ const expected = [
821
+ { name: '张三', age: 25, city: '北京' },
822
+ { name: '李四', age: 30, city: null }
823
+ ];
824
+ const compressed = compress(data);
825
+ const text = stringify(compressed);
826
+ const parsed = parse(text);
827
+ expect(decompress(parsed)).toEqual(expected);
828
+ });
829
+
830
+ test('含空格字符串保留引号', () => {
831
+ expect(stringify(['hello world'])).toBe('["hello world"]');
832
+ });
833
+ });
834
+
835
+ describe('parse 兼容有/无引号格式', () => {
836
+ test('解析无引号字符串', () => {
837
+ expect(parse('[hello]')).toEqual(['hello']);
838
+ expect(parse('[hello,world]')).toEqual(['hello', 'world']);
839
+ });
840
+
841
+ test('解析混合格式', () => {
842
+ expect(parse('[hello,"world",123]')).toEqual(['hello', 'world', 123]);
843
+ });
844
+
845
+ test('解析含中文字符串', () => {
846
+ expect(parse('[你好,张三]')).toEqual(['你好', '张三']);
847
+ });
848
+
849
+ test('关键字后无边界符时视为裸字符串', () => {
850
+ expect(parse('[nullx]')).toEqual(['nullx']);
851
+ expect(parse('[truex]')).toEqual(['truex']);
852
+ });
853
+
854
+ test('空槽仍为 null', () => {
855
+ expect(parse('[,1,]')).toEqual([null, 1, null]);
856
+ });
857
+
858
+ test('解析对象 bare key', () => {
859
+ expect(parse('{name:alice,age:25}')).toEqual({ name: 'alice', age: 25 });
860
+ });
861
+
862
+ test('解析对象混合 key', () => {
863
+ expect(parse('{name:alice,"full name":"hello world"}'))
864
+ .toEqual({ name: 'alice', 'full name': 'hello world' });
865
+ });
866
+
867
+ test('数字 key 保留引号 → 解析为字符串 key', () => {
868
+ expect(parse('{"123":hello}')).toEqual({ '123': 'hello' });
869
+ });
870
+
871
+ test('对象 bare key 往返', () => {
872
+ const obj = {
873
+ name: '张三',
874
+ age: 25,
875
+ items: [1, null, 3],
876
+ profile: { avatar: 'a.jpg', bio: 'hello' }
877
+ };
878
+ const str = stringify(obj);
879
+ const parsed = parse(str);
880
+ expect(parsed).toEqual(obj);
881
+ });
882
+ });
883
+
884
+ });
885
+
886
+ });