@cloudpss/ubjson 0.4.32 → 0.4.34

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.
Files changed (59) hide show
  1. package/dist/common/constants.d.ts +22 -20
  2. package/dist/common/constants.js +1 -21
  3. package/dist/common/constants.js.map +1 -1
  4. package/dist/common/decoder.d.ts +4 -20
  5. package/dist/common/decoder.js +217 -178
  6. package/dist/common/decoder.js.map +1 -1
  7. package/dist/common/encoder.d.ts +8 -54
  8. package/dist/common/encoder.js +241 -259
  9. package/dist/common/encoder.js.map +1 -1
  10. package/dist/common/string-decoder.d.ts +3 -8
  11. package/dist/common/string-decoder.js +190 -46
  12. package/dist/common/string-decoder.js.map +1 -1
  13. package/dist/common/string-encoder.d.ts +2 -8
  14. package/dist/common/string-encoder.js +8 -44
  15. package/dist/common/string-encoder.js.map +1 -1
  16. package/dist/encoder.js +2 -21
  17. package/dist/encoder.js.map +1 -1
  18. package/dist/index.d.ts +1 -2
  19. package/dist/index.js +2 -4
  20. package/dist/index.js.map +1 -1
  21. package/dist/rxjs/decoder.js +2 -2
  22. package/dist/rxjs/decoder.js.map +1 -1
  23. package/dist/rxjs/index.d.ts +1 -0
  24. package/dist/rxjs/index.js +1 -0
  25. package/dist/rxjs/index.js.map +1 -1
  26. package/dist/stream/index.d.ts +1 -0
  27. package/dist/stream/index.js +2 -1
  28. package/dist/stream/index.js.map +1 -1
  29. package/dist/stream-helper/decoder.d.ts +1 -1
  30. package/dist/stream-helper/decoder.js +1 -1
  31. package/dist/stream-helper/decoder.js.map +1 -1
  32. package/dist/stream-helper/encoder.js +2 -2
  33. package/dist/stream-helper/encoder.js.map +1 -1
  34. package/dist/utils.d.ts +5 -1
  35. package/dist/utils.js +13 -4
  36. package/dist/utils.js.map +1 -1
  37. package/package.json +3 -2
  38. package/src/common/constants.ts +22 -21
  39. package/src/common/decoder.ts +197 -162
  40. package/src/common/encoder.ts +230 -277
  41. package/src/common/string-decoder.ts +173 -41
  42. package/src/common/string-encoder.ts +10 -41
  43. package/src/encoder.ts +2 -16
  44. package/src/index.ts +2 -5
  45. package/src/rxjs/decoder.ts +2 -2
  46. package/src/rxjs/index.ts +1 -0
  47. package/src/stream/index.ts +2 -0
  48. package/src/stream-helper/decoder.ts +1 -1
  49. package/src/stream-helper/encoder.ts +2 -2
  50. package/src/utils.ts +14 -4
  51. package/tests/decode.js +69 -5
  52. package/tests/e2e.js +12 -1
  53. package/tests/encode.js +52 -23
  54. package/tests/rxjs/decode.js +3 -2
  55. package/tests/rxjs/encode.js +5 -8
  56. package/tests/stream/decode.js +2 -1
  57. package/tests/stream/encode.js +4 -5
  58. package/tests/string-encoding.js +77 -25
  59. package/tsconfig.json +3 -1
package/tests/encode.js CHANGED
@@ -69,8 +69,8 @@ test('encode char', () => {
69
69
  expect(toArray(encode('a'))).toEqual(toArray('C', 'a'));
70
70
  });
71
71
 
72
- test('encode char 128', () => {
73
- expect(toArray(encode('\u00CC'))).toEqual(toArray('C', '\u00CC'));
72
+ test('encode char 127', () => {
73
+ expect(toArray(encode('\u007F'))).toEqual(toArray('C', '\u007F'));
74
74
  });
75
75
 
76
76
  test('encode char 257', () => {
@@ -85,15 +85,27 @@ test('encode string', () => {
85
85
  expect(toArray(encode('ubjson'))).toEqual(toArray('S', 'i', 6, ...'ubjson'));
86
86
  });
87
87
 
88
- test('encode string (u8 len)', () => {
88
+ test('encode string (len = 60)', () => {
89
89
  // 不生成 u8
90
- expect(toArray(encode('ubjson'.repeat(10)))).toEqual(toArray('S', 'I', 0, 6 * 10, ...'ubjson'.repeat(10)));
90
+ const str = 'ubjson'.repeat(10);
91
+ expect(toArray(encode(str))).toEqual(toArray('S', 'i', str.length, ...str));
92
+ });
93
+
94
+ test('encode string (len = 120)', () => {
95
+ // 不生成 u8
96
+ const str = 'ubjson'.repeat(20);
97
+ expect(toArray(encode(str))).toEqual(toArray('S', 'i', str.length, ...str));
98
+ });
99
+
100
+ test('encode string (len = 180)', () => {
101
+ // 不生成 u8
102
+ const str = 'ubjson'.repeat(30);
103
+ expect(toArray(encode(str))).toEqual(toArray('S', 'I', 0, str.length, ...str));
91
104
  });
92
105
 
93
106
  test('encode string (no encodeInto)', () => {
94
107
  // eslint-disable-next-line @typescript-eslint/unbound-method
95
108
  const encodeInto = TextEncoder.prototype.encodeInto;
96
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
97
109
  // @ts-expect-error 移除 encodeInto 以测试兼容性
98
110
  TextEncoder.prototype.encodeInto = undefined;
99
111
  expect(toArray(encode('ubjson'))).toEqual(toArray('S', 'i', 6, ...'ubjson'));
@@ -113,6 +125,10 @@ test('encode array (undefined)', () => {
113
125
  expect(toArray(encode([undefined]))).toEqual(toArray('[', 'Z', ']'));
114
126
  });
115
127
 
128
+ test('encode array (function)', () => {
129
+ expect(toArray(encode([() => 1]))).toEqual(toArray('[', 'Z', ']'));
130
+ });
131
+
116
132
  test('encode array (spares)', () => {
117
133
  const array = Array.from({ length: 3 });
118
134
  array[1] = true;
@@ -314,13 +330,9 @@ test('encode object (skip undefined)', () => {
314
330
  expect(toArray(encode(obj))).toEqual(toArray('{', '}'));
315
331
  });
316
332
 
317
- test('encode object (function) [error]', () => {
318
- const obj = {
319
- a: () => {
320
- // noop
321
- },
322
- };
323
- expect(() => encode(obj)).toThrow(/Unsupported type \[object Function\]/);
333
+ test('encode object (skip function)', () => {
334
+ const obj = { a: 1, b: () => 1 };
335
+ expect(toArray(encode(obj))).toEqual(toArray('{', 'i', 1, 'a', 'U', 1, '}'));
324
336
  });
325
337
 
326
338
  test('encode object (include null)', () => {
@@ -357,31 +369,48 @@ test('encode huge data (~128M)', () => {
357
369
  );
358
370
  });
359
371
 
360
- test('encode huge data (128M)', () => {
361
- const obj = [new Uint8Array(128 * 1024 * 1024)];
372
+ test('encode huge data (128M - 9)', () => {
373
+ const obj = [new Uint8Array(128 * 1024 * 1024 - 9)];
362
374
  obj[0][0] = 0x12;
363
375
  obj[0][1] = 0x34;
364
376
  const encoded = encode(obj);
365
- expect(encoded.length).toBe(128 * 1024 * 1024 + 9 + 2);
366
- expect(toArray(encoded.slice(0, 12))).toEqual(toArray('[', '[', '$', 'U', '#', 'l', 0x8, 0, 0, 0, 0x12, 0x34));
377
+ expect(encoded.length).toBe(128 * 1024 * 1024 + 2);
378
+ // (128 * 1024 * 1024 - 9) === 0x7fffff7
379
+ expect(toArray(encoded.slice(0, 12))).toEqual(
380
+ toArray('[', '[', '$', 'U', '#', 'l', 0x7, 0xff, 0xff, 0xf7, 0x12, 0x34),
381
+ );
367
382
  });
368
383
 
369
- test('encode huge data (128M + 1) [error]', () => {
370
- const obj = [new Uint8Array(128 * 1024 * 1024 + 1)];
384
+ test('encode huge data (128M - 8) [error]', () => {
385
+ const obj = [new Uint8Array(128 * 1024 * 1024 - 8)];
371
386
  expect(() => encode(obj)).toThrow(/Buffer has exceed max size/);
372
387
  });
373
388
 
374
389
  test('encode huge data (256M)', () => {
375
- const obj = [new Uint8Array(128 * 1024 * 1024), new Uint8Array(128 * 1024 * 1024)];
390
+ const obj = [new Uint8Array(128 * 1024 * 1024 - 9), new Uint8Array(128 * 1024 * 1024 - 9)];
376
391
  obj[0][0] = 0x12;
377
392
  obj[0][1] = 0x34;
378
393
  obj[1][0] = 0x56;
379
394
  obj[1][1] = 0x78;
380
395
  const encoded = encode(obj);
381
- expect(encoded.length).toBe(128 * 1024 * 1024 * 2 + 9 * 2 + 2);
382
- expect(toArray(encoded.slice(0, 12))).toEqual(toArray('[', '[', '$', 'U', '#', 'l', 0x8, 0, 0, 0, 0x12, 0x34));
383
- expect(toArray(encoded.slice(128 * 1024 * 1024 + 10, 128 * 1024 * 1024 + 10 + 11))).toEqual(
384
- toArray('[', '$', 'U', '#', 'l', 0x8, 0, 0, 0, 0x56, 0x78),
396
+ expect(encoded.length).toBe(128 * 1024 * 1024 * 2 + 2);
397
+ expect(toArray(encoded.slice(0, 12))).toEqual(
398
+ toArray('[', '[', '$', 'U', '#', 'l', 0x7, 0xff, 0xff, 0xf7, 0x12, 0x34),
399
+ );
400
+ expect(toArray(encoded.slice(128 * 1024 * 1024 + 1, 128 * 1024 * 1024 + 1 + 11))).toEqual(
401
+ toArray('[', '$', 'U', '#', 'l', 0x7, 0xff, 0xff, 0xf7, 0x56, 0x78),
385
402
  );
386
403
  expect(toArray(encoded.slice(-1))).toEqual(toArray(']'));
387
404
  });
405
+
406
+ test('encode custom toJSON', () => {
407
+ const obj = {
408
+ toJSON: () => ({ a: 1 }),
409
+ };
410
+ expect(toArray(encode(obj))).toEqual(toArray('{', 'i', 1, 'a', 'U', 1, '}'));
411
+ });
412
+
413
+ test('encode Date', () => {
414
+ const obj = new Date(0);
415
+ expect(toArray(encode(obj))).toEqual(toArray('S', 'i', 24, ...'1970-01-01T00:00:00.000Z'));
416
+ });
@@ -5,6 +5,7 @@ import { jest } from '@jest/globals';
5
5
  import { firstValueFrom, of, Subject } from 'rxjs';
6
6
  import { decode as decodePipe } from '../../dist/rxjs/index.js';
7
7
  import { toBuffer } from '../.utils.js';
8
+ import { UnexpectedEof } from '../../dist/utils.js';
8
9
 
9
10
  /**
10
11
  * 包装为 promise
@@ -109,7 +110,7 @@ test('decode string (bad size) [error]', async () => {
109
110
  });
110
111
 
111
112
  test('decode string (unexpected eof) [error]', async () => {
112
- await expect(() => decode(toBuffer('S', 'i', 2, 'x'))).rejects.toThrow(/Unexpected EOF/);
113
+ await expect(() => decode(toBuffer('S', 'i', 2, 'x'))).rejects.toThrow(UnexpectedEof);
113
114
  });
114
115
 
115
116
  test('decode ascii string', async () => {
@@ -306,7 +307,7 @@ test('decode array of objects of arrays (optimized)', async () => {
306
307
  });
307
308
 
308
309
  test('decode array (strongly typed, unexpected eof, optimized)', async () => {
309
- await expect(() => decode(toBuffer('[', '$', 'i', '#', 'i', 3, 1, 2))).rejects.toThrow();
310
+ await expect(() => decode(toBuffer('[', '$', 'i', '#', 'i', 3, 1, 2))).rejects.toThrow(UnexpectedEof);
310
311
  });
311
312
 
312
313
  test('decode array (strongly typed, invalid length value, optimized)', async () => {
@@ -85,8 +85,8 @@ test('encode char', async () => {
85
85
  expect(toArray(await encodeAsync('a'))).toEqual(toArray('C', 'a'));
86
86
  });
87
87
 
88
- test('encode char 128', async () => {
89
- expect(toArray(await encodeAsync('\u00CC'))).toEqual(toArray('C', '\u00CC'));
88
+ test('encode char 127', async () => {
89
+ expect(toArray(await encodeAsync('\u007F'))).toEqual(toArray('C', '\u007F'));
90
90
  });
91
91
 
92
92
  test('encode char 257', async () => {
@@ -109,7 +109,6 @@ test('encode huge string', async () => {
109
109
  test('encode string (no encodeInto)', async () => {
110
110
  // eslint-disable-next-line @typescript-eslint/unbound-method
111
111
  const encodeInto = TextEncoder.prototype.encodeInto;
112
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
113
112
  // @ts-expect-error 移除 encodeInto 以测试兼容性
114
113
  TextEncoder.prototype.encodeInto = undefined;
115
114
  expect(toArray(await encodeAsync('ubjson'))).toEqual(toArray('S', 'i', 6, 'u', 'b', 'j', 's', 'o', 'n'));
@@ -330,13 +329,11 @@ test('encode object (skip undefined)', async () => {
330
329
  expect(toArray(await encodeAsync(obj))).toEqual(toArray('{', '}'));
331
330
  });
332
331
 
333
- test('encode object (function) [error]', async () => {
332
+ test('encode object (skip function)', async () => {
334
333
  const obj = {
335
- a: () => {
336
- // noop
337
- },
334
+ a: () => 1,
338
335
  };
339
- await expect(() => encodeAsync(obj)).rejects.toThrow(/Unsupported type \[object Function\]/);
336
+ expect(toArray(await encodeAsync(obj))).toEqual(toArray('{', '}'));
340
337
  });
341
338
 
342
339
  test('encode object (include null)', async () => {
@@ -5,6 +5,7 @@ import { jest } from '@jest/globals';
5
5
  import { Readable } from 'node:stream';
6
6
  import { decoder as decodeStream, decode as decodeAsync } from '../../dist/stream/index.js';
7
7
  import { toBuffer } from '../.utils.js';
8
+ import { UnexpectedEof } from '../../dist/utils.js';
8
9
 
9
10
  /**
10
11
  * 包装为 promise
@@ -104,7 +105,7 @@ test('decode string (bad size) [error]', async () => {
104
105
  });
105
106
 
106
107
  test('decode string (unexpected eof) [error]', async () => {
107
- await expect(() => decode(toBuffer('S', 'i', 2, 'x'))).rejects.toThrow(/Unexpected EOF/);
108
+ await expect(() => decode(toBuffer('S', 'i', 2, 'x'))).rejects.toThrow(UnexpectedEof);
108
109
  });
109
110
 
110
111
  test('decode ascii string', async () => {
@@ -80,8 +80,8 @@ test('encode char', async () => {
80
80
  expect(toArray(await encodeAsync('a'))).toEqual(toArray('C', 'a'));
81
81
  });
82
82
 
83
- test('encode char 128', async () => {
84
- expect(toArray(await encodeAsync('\u00CC'))).toEqual(toArray('C', '\u00CC'));
83
+ test('encode char 127', async () => {
84
+ expect(toArray(await encodeAsync('\u007F'))).toEqual(toArray('C', '\u007F'));
85
85
  });
86
86
 
87
87
  test('encode char 257', async () => {
@@ -104,7 +104,6 @@ test('encode huge string', async () => {
104
104
  test('encode string (no encodeInto)', async () => {
105
105
  // eslint-disable-next-line @typescript-eslint/unbound-method
106
106
  const encodeInto = TextEncoder.prototype.encodeInto;
107
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
108
107
  // @ts-expect-error 移除 encodeInto 以测试兼容性
109
108
  TextEncoder.prototype.encodeInto = undefined;
110
109
  expect(toArray(await encodeAsync('ubjson'))).toEqual(toArray('S', 'i', 6, 'u', 'b', 'j', 's', 'o', 'n'));
@@ -325,13 +324,13 @@ test('encode object (skip undefined)', async () => {
325
324
  expect(toArray(await encodeAsync(obj))).toEqual(toArray('{', '}'));
326
325
  });
327
326
 
328
- test('encode object (function) [error]', async () => {
327
+ test('encode object (skip function)', async () => {
329
328
  const obj = {
330
329
  a: () => {
331
330
  // noop
332
331
  },
333
332
  };
334
- await expect(() => encodeAsync(obj)).rejects.toThrow(/Unsupported type \[object Function\]/);
333
+ expect(toArray(await encodeAsync(obj))).toEqual(toArray('{', '}'));
335
334
  });
336
335
 
337
336
  test('encode object (include null)', async () => {
@@ -1,5 +1,6 @@
1
- import { StringEncoder } from '../dist/common/string-encoder.js';
2
- import { StringDecoder, decodeJs } from '../dist/common/string-decoder.js';
1
+ import { Encoder } from '../dist/encoder.js';
2
+ import { decode, decodeKey, decodeJs } from '../dist/common/string-decoder.js';
3
+ import '../dist/common/constants.js';
3
4
 
4
5
  /**
5
6
  * 测试编码
@@ -9,25 +10,59 @@ function testEncoding(
9
10
  /** @type {Pick<TextDecoder, 'decode'>} */ decoder,
10
11
  ) {
11
12
  expect(decoder.decode(encoder.encode(''))).toEqual('');
13
+ expect(decoder.decode(encoder.encode('a'))).toEqual('a');
12
14
  expect(decoder.decode(encoder.encode('p4'))).toEqual('p4');
13
15
  expect(decoder.decode(encoder.encode('t0'))).toEqual('t0');
14
- expect(decoder.decode(encoder.encode('ab'))).toEqual('ab');
15
- expect(decoder.decode(encoder.encode('ba'))).toEqual('ba');
16
+ expect(decoder.decode(encoder.encode('qwe'))).toEqual('qwe');
17
+ expect(decoder.decode(encoder.encode('1qaz'))).toEqual('1qaz');
16
18
  expect(decoder.decode(encoder.encode('123456'))).toEqual('123456');
17
19
  expect(decoder.decode(encoder.encode('123465'))).toEqual('123465');
18
20
  expect(decoder.decode(encoder.encode('1234651'))).toEqual('1234651');
19
21
  expect(decoder.decode(encoder.encode('1234651'))).toEqual('1234651');
22
+ expect(decoder.decode(encoder.encode('12346511234651'))).toEqual('12346511234651');
23
+ expect(decoder.decode(encoder.encode('123465112346511234651'))).toEqual('123465112346511234651');
20
24
  expect(decoder.decode(encoder.encode('abc 你好 😊🤣'))).toEqual('abc 你好 😊🤣');
25
+ expect(decoder.decode(encoder.encode('abc 你好abc 你好abc 你好 😊🤣'))).toEqual('abc 你好abc 你好abc 你好 😊🤣');
26
+ expect(decoder.decode(encoder.encode('水'.repeat(127)))).toEqual('水'.repeat(127));
27
+ expect(decoder.decode(encoder.encode('水'.repeat(32767)))).toEqual('水'.repeat(32767));
21
28
  expect(decoder.decode(encoder.encode('abc 你好 😊🤣'.repeat(1000)))).toEqual('abc 你好 😊🤣'.repeat(1000));
22
29
 
30
+ for (let length = 1; length < 1024; length++) {
31
+ const expected = 'a'.repeat(length);
32
+ const actual = decoder.decode(encoder.encode(expected));
33
+ expect(actual).toEqual(expected);
34
+
35
+ const expected2 = '©'.repeat(length);
36
+ const actual2 = decoder.decode(encoder.encode(expected2));
37
+ expect(actual2).toEqual(expected2);
38
+
39
+ const expected3 = '水'.repeat(length);
40
+ const actual3 = decoder.decode(encoder.encode(expected3));
41
+ expect(actual3).toEqual(expected3);
42
+
43
+ const expected4 = '😊'.repeat(length);
44
+ const actual4 = decoder.decode(encoder.encode(expected4));
45
+ expect(actual4).toEqual(expected4);
46
+
47
+ const bad = new Uint8Array(length);
48
+ bad.fill(0xff);
49
+ expect(decoder.decode(bad)).toEqual('\uFFFD'.repeat(length));
50
+
51
+ bad.fill('a'.charCodeAt(0), 0, length - 1);
52
+ expect(decoder.decode(bad)).toEqual('a'.repeat(length - 1) + '\uFFFD');
53
+ }
54
+
23
55
  {
24
56
  // 检查所有单字节
25
57
  for (let index = 0; index < 0xffff; index++) {
26
58
  // 跳过 Surrogate
27
59
  if (index >= 0xd800 && index <= 0xdfff) continue;
28
60
  const expected = String.fromCharCode(index);
29
- const actual = decoder.decode(encoder.encode(expected));
30
- if (expected !== actual) expect(actual).toEqual(expected);
61
+ const encoded = encoder.encode(expected);
62
+ const actual = decoder.decode(encoded);
63
+ if (expected !== actual) {
64
+ expect(actual).toEqual(expected);
65
+ }
31
66
  }
32
67
  }
33
68
 
@@ -53,32 +88,49 @@ function testEncoding(
53
88
  }
54
89
 
55
90
  test('encode string', () => {
56
- testEncoding(new StringEncoder(), new TextDecoder('utf8', { ignoreBOM: true, fatal: false }));
91
+ testEncoding(
92
+ {
93
+ encode(s) {
94
+ const encoder = new Encoder(s);
95
+ const buffer = encoder.encode();
96
+ if (buffer[0] === 67) return buffer.subarray(1);
97
+ if (buffer[1] === 105) return buffer.subarray(3);
98
+ if (buffer[1] === 73) return buffer.subarray(4);
99
+ if (buffer[1] === 108) return buffer.subarray(6);
100
+ throw new Error('Invalid buffer');
101
+ },
102
+ },
103
+ new TextDecoder('utf8', { ignoreBOM: true, fatal: false }),
104
+ );
57
105
  });
58
106
 
59
107
  test('decode string', () => {
60
- testEncoding(
61
- new TextEncoder(),
62
- new (class extends StringDecoder {
63
- /** @override */ decode(/** @type {Uint8Array} */ buffer) {
64
- return super.decode(buffer, 0, buffer.byteLength);
65
- }
66
- })(),
67
- );
108
+ testEncoding(new TextEncoder(), {
109
+ decode(buffer) {
110
+ if (!(buffer instanceof Uint8Array)) return '';
111
+ return decode(buffer, 0, buffer.byteLength);
112
+ },
113
+ });
114
+ });
115
+
116
+ test('decode string key', () => {
117
+ testEncoding(new TextEncoder(), {
118
+ decode(buffer) {
119
+ if (!(buffer instanceof Uint8Array)) return '';
120
+ return decodeKey(buffer, 0, buffer.byteLength);
121
+ },
122
+ });
68
123
  });
69
124
 
70
125
  test('decode string js', () => {
71
- testEncoding(
72
- new TextEncoder(),
73
- new (class extends StringDecoder {
74
- /** @override */ decode(/** @type {Uint8Array} */ buffer) {
75
- return decodeJs(buffer, 0, buffer.byteLength);
76
- }
77
- })(),
78
- );
126
+ testEncoding(new TextEncoder(), {
127
+ decode(buffer) {
128
+ if (!(buffer instanceof Uint8Array)) return '';
129
+ return decodeJs(buffer, 0, buffer.byteLength);
130
+ },
131
+ });
79
132
  });
80
133
 
81
134
  test('decode malformed', () => {
82
- const decoder = new StringDecoder();
83
- expect(decoder.decode(new Uint8Array([0xff, 'a'.charCodeAt(0)]), 0, 2)).toEqual('\uFFFDa');
135
+ expect(decode(new Uint8Array([0xff, 'a'.charCodeAt(0)]), 0, 2)).toEqual('\uFFFDa');
84
136
  });
package/tsconfig.json CHANGED
@@ -2,6 +2,8 @@
2
2
  "extends": "../../tsconfig",
3
3
  "include": ["./src/"],
4
4
  "compilerOptions": {
5
- "outDir": "./dist"
5
+ "outDir": "./dist",
6
+ "isolatedModules": false,
7
+ "verbatimModuleSyntax": false
6
8
  }
7
9
  }