Skip to content

Commit baf670c

Browse files
committed
Add support for mode:jsonl in stringifyInfo
1 parent cd8892a commit baf670c

7 files changed

Lines changed: 91 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
- Fixed various issues with parsing of malformed JSON
1212
- `stringifyChunked()`:
1313
- Added `mode` option with `"json"` (default) and `"jsonl"` values; `mode: "jsonl"` serializes iterable input into newline-delimited JSON values
14+
- `stringifyInfo()`:
15+
- Added `mode` option with `"json"` (default) and `"jsonl"` values; `mode: "jsonl"` computes byte size for newline-delimited JSON values
1416

1517
## 0.6.3 (2024-10-24)
1618

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ export function stringifyInfo(value: any, options?: StringifyInfoOptions): Strin
261261
type StringifyInfoOptions = {
262262
replacer?: Replacer;
263263
space?: Space;
264+
mode?: 'json' | 'jsonl';
264265
continueOnCircular?: boolean;
265266
}
266267
type StringifyInfoResult = {

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ declare module '@discoveryjs/json-ext' {
3131
type StringifyInfoOptions = {
3232
replacer?: Replacer;
3333
space?: Space;
34+
mode?: 'json' | 'jsonl';
3435
continueOnCircular?: boolean;
3536
}
3637
type StringifyInfoResult = {

src/stringify-chunked.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
import { normalizeStringifyOptions, replaceValue } from './utils.js';
2-
3-
function resolveStringifyMode(mode = 'json') {
4-
if (mode === 'json' || mode === 'jsonl') {
5-
return mode;
6-
}
7-
8-
throw new TypeError('Invalid options: `mode` should be "json" or "jsonl"');
9-
}
1+
import { normalizeStringifyOptions, replaceValue, resolveStringifyMode } from './utils.js';
102

113
function encodeString(value) {
124
if (/[^\x20\x21\x23-\x5B\x5D-\uD799]/.test(value)) { // [^\x20-\uD799]|[\x22\x5c]

src/stringify-info.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { normalizeStringifyOptions, replaceValue } from './utils.js';
1+
import { normalizeStringifyOptions, replaceValue, resolveStringifyMode } from './utils.js';
22

33
const hasOwn = typeof Object.hasOwn === 'function'
44
? Object.hasOwn
@@ -125,21 +125,27 @@ export function stringifyInfo(value, ...args) {
125125
const { replacer, getKeys, ...options } = normalizeStringifyOptions(...args);
126126
const continueOnCircular = Boolean(options.continueOnCircular);
127127
const space = options.space?.length || 0;
128+
const roots = resolveStringifyMode(options.mode) === 'jsonl' && Array.isArray(value) ? value : [value];
128129

129130
const keysLength = new Map();
130131
const visited = new Map();
131132
const circular = new Set();
132133
const stack = [];
133-
const root = { '': value };
134134
let stop = false;
135135
let bytes = 0;
136136
let spaceBytes = 0;
137137
let objects = 0;
138138

139-
walk(root, '', value);
139+
for (let i = 0; i < roots.length; i++) {
140+
if (i > 0) {
141+
bytes += 1; // newline separator
142+
}
143+
144+
walk({ '': roots[i] }, '', roots[i]);
145+
}
140146

141147
// when value is undefined or replaced for undefined
142-
if (bytes === 0) {
148+
if (bytes === 0 && roots.length === 1) {
143149
bytes += 9; // FIXME: that's the length of undefined, should we normalize behaviour to convert it to null?
144150
}
145151

src/stringify-info.test.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,74 @@ describe('stringifyInfo()', () => {
7272
});
7373
});
7474

75+
describe('mode option', () => {
76+
it('json by default', () => {
77+
assert.deepStrictEqual(stringifyInfo([{ a: 1 }, { a: 2 }]), {
78+
bytes: '[{"a":1},{"a":2}]'.length,
79+
spaceBytes: 0,
80+
circular: []
81+
});
82+
});
83+
84+
it('jsonl mode computes newline-delimited sizes', () => {
85+
// {"a":1}\n{"a":2}\n3 = 7+1+7+1+1 = 17
86+
assert.deepStrictEqual(stringifyInfo([{ a: 1 }, { a: 2 }, 3], { mode: 'jsonl' }), {
87+
bytes: 17,
88+
spaceBytes: 0,
89+
circular: []
90+
});
91+
});
92+
93+
it('jsonl mode with non-array value treats as single value', () => {
94+
assert.deepStrictEqual(stringifyInfo({ a: 1 }, { mode: 'jsonl' }), {
95+
bytes: '{"a":1}'.length,
96+
spaceBytes: 0,
97+
circular: []
98+
});
99+
});
100+
101+
it('jsonl mode with empty array', () => {
102+
assert.deepStrictEqual(stringifyInfo([], { mode: 'jsonl' }), {
103+
bytes: 0,
104+
spaceBytes: 0,
105+
circular: []
106+
});
107+
});
108+
109+
it('jsonl mode with single element array', () => {
110+
assert.deepStrictEqual(stringifyInfo([42], { mode: 'jsonl' }), {
111+
bytes: 2,
112+
spaceBytes: 0,
113+
circular: []
114+
});
115+
});
116+
117+
it('jsonl mode with space option', () => {
118+
// Each root is formatted independently
119+
const info = stringifyInfo([{ a: 1 }, { b: 2 }], { mode: 'jsonl', space: 2 });
120+
const root1 = JSON.stringify({ a: 1 }, null, 2);
121+
const root2 = JSON.stringify({ b: 2 }, null, 2);
122+
const expected = root1 + '\n' + root2;
123+
124+
assert.strictEqual(info.bytes, Buffer.byteLength(expected, 'utf8'));
125+
});
126+
127+
it('jsonl mode with replacer', () => {
128+
assert.deepStrictEqual(stringifyInfo([{ a: 1, b: 2 }, { a: 3, b: 4 }], { mode: 'jsonl', replacer: ['a'] }), {
129+
bytes: '{"a":1}\n{"a":3}'.length,
130+
spaceBytes: 0,
131+
circular: []
132+
});
133+
});
134+
135+
it('throws on invalid mode', () => {
136+
assert.throws(
137+
() => stringifyInfo([1, 2], { mode: 'auto' }),
138+
/Invalid options: `mode` should be "json" or "jsonl"/
139+
);
140+
});
141+
});
142+
75143
describe('circular', () => {
76144
it('should stop on first circular reference by default', () => {
77145
const circularRef = {};

src/utils.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,11 @@ export function normalizeStringifyOptions(optionsOrReplacer, space) {
9898
space: normalizeSpace(optionsOrReplacer.space)
9999
};
100100
}
101+
102+
export function resolveStringifyMode(mode = 'json') {
103+
if (mode === 'json' || mode === 'jsonl') {
104+
return mode;
105+
}
106+
107+
throw new TypeError('Invalid options: `mode` should be "json" or "jsonl"');
108+
}

0 commit comments

Comments
 (0)