@theholocron/json-utils 1.9.2 → 1.10.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/dist/index.d.mts +40 -0
- package/dist/index.mjs +62 -0
- package/package.json +1 -1
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/stringify-json.d.ts
|
|
2
|
+
export interface StringifyJsonOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Indent for each level: a number of spaces (a non-negative integer) or a
|
|
5
|
+
* string of spaces and tabs (e.g. `"\t"`). Any length works, unlike
|
|
6
|
+
* `JSON.stringify`'s own ten-character cap. Default `2`.
|
|
7
|
+
*/
|
|
8
|
+
indent?: number | string;
|
|
9
|
+
/**
|
|
10
|
+
* The existing file's text. Its detected indent and line endings are used
|
|
11
|
+
* instead of `indent`, so rewriting a file doesn't reformat it. Falls back
|
|
12
|
+
* to `indent` when no indent can be detected (empty or single-line input).
|
|
13
|
+
* A file that mixes `\r\n` and `\n` keeps whichever it uses more (`\n` on a tie).
|
|
14
|
+
*/
|
|
15
|
+
like?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The indentation one level of nesting adds in a JSON document — the leading
|
|
19
|
+
* whitespace (spaces or a tab) of the first line after the first one, less any
|
|
20
|
+
* padding the root value's own line starts with. `undefined` when there is
|
|
21
|
+
* none, e.g. a minified or single-line document.
|
|
22
|
+
*
|
|
23
|
+
* Raw newlines can't occur inside a JSON string, so any line that starts with
|
|
24
|
+
* whitespace is structural indentation.
|
|
25
|
+
*/
|
|
26
|
+
export declare function detectIndent(text: string): string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* `JSON.stringify` pretty-printed and ending in exactly one newline — the shape
|
|
29
|
+
* every `package.json`-style file on disk should have.
|
|
30
|
+
*
|
|
31
|
+
* Pass the file's current text as `like` to keep its own indent (tabs, 4
|
|
32
|
+
* spaces, …) and line endings (`\r\n`) when rewriting it.
|
|
33
|
+
*
|
|
34
|
+
* @throws {TypeError} when `value` has no JSON representation (`undefined`, a
|
|
35
|
+
* function, a symbol) — `JSON.stringify` would return `undefined` for it — or
|
|
36
|
+
* when `indent` isn't a non-negative integer or a string of spaces and tabs
|
|
37
|
+
* (anything else would produce text no JSON parser accepts).
|
|
38
|
+
*/
|
|
39
|
+
export declare function stringifyJson(value: unknown, options?: StringifyJsonOptions): string;
|
|
40
|
+
//#endregion
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region src/stringify-json.ts
|
|
2
|
+
/**
|
|
3
|
+
* The indentation one level of nesting adds in a JSON document — the leading
|
|
4
|
+
* whitespace (spaces or a tab) of the first line after the first one, less any
|
|
5
|
+
* padding the root value's own line starts with. `undefined` when there is
|
|
6
|
+
* none, e.g. a minified or single-line document.
|
|
7
|
+
*
|
|
8
|
+
* Raw newlines can't occur inside a JSON string, so any line that starts with
|
|
9
|
+
* whitespace is structural indentation.
|
|
10
|
+
*/
|
|
11
|
+
function detectIndent(text) {
|
|
12
|
+
const lines = text.split(/\r?\n/);
|
|
13
|
+
const rootAt = lines.findIndex((line) => line.trim() !== "");
|
|
14
|
+
if (rootAt === -1) return void 0;
|
|
15
|
+
const rootPad = /^[ \t]*/.exec(lines[rootAt])[0];
|
|
16
|
+
for (const line of lines.slice(rootAt + 1)) {
|
|
17
|
+
const match = /^([ \t]+)\S/.exec(line);
|
|
18
|
+
if (!match) continue;
|
|
19
|
+
const pad = match[1];
|
|
20
|
+
const nested = pad.startsWith(rootPad) ? pad.slice(rootPad.length) : pad;
|
|
21
|
+
if (nested) return nested;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** `\r\n` when it is the file's majority line ending, else `\n`. */
|
|
25
|
+
function detectEol(text) {
|
|
26
|
+
const crlf = text.match(/\r\n/g)?.length ?? 0;
|
|
27
|
+
return crlf > (text.match(/\n/g)?.length ?? 0) - crlf ? "\r\n" : "\n";
|
|
28
|
+
}
|
|
29
|
+
function resolveIndent(indent) {
|
|
30
|
+
if (typeof indent === "number") {
|
|
31
|
+
if (!Number.isInteger(indent) || indent < 0) throw new TypeError(`indent must be a non-negative integer, got ${indent}`);
|
|
32
|
+
return " ".repeat(indent);
|
|
33
|
+
}
|
|
34
|
+
if (!/^[ \t]*$/.test(indent)) throw new TypeError(`indent must contain only spaces and tabs, got ${JSON.stringify(indent)}`);
|
|
35
|
+
return indent;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* `JSON.stringify` pretty-printed and ending in exactly one newline — the shape
|
|
39
|
+
* every `package.json`-style file on disk should have.
|
|
40
|
+
*
|
|
41
|
+
* Pass the file's current text as `like` to keep its own indent (tabs, 4
|
|
42
|
+
* spaces, …) and line endings (`\r\n`) when rewriting it.
|
|
43
|
+
*
|
|
44
|
+
* @throws {TypeError} when `value` has no JSON representation (`undefined`, a
|
|
45
|
+
* function, a symbol) — `JSON.stringify` would return `undefined` for it — or
|
|
46
|
+
* when `indent` isn't a non-negative integer or a string of spaces and tabs
|
|
47
|
+
* (anything else would produce text no JSON parser accepts).
|
|
48
|
+
*/
|
|
49
|
+
function stringifyJson(value, options = {}) {
|
|
50
|
+
let indent = resolveIndent(options.indent ?? 2);
|
|
51
|
+
let eol = "\n";
|
|
52
|
+
if (options.like !== void 0) {
|
|
53
|
+
indent = detectIndent(options.like) ?? indent;
|
|
54
|
+
eol = detectEol(options.like);
|
|
55
|
+
}
|
|
56
|
+
const json = indent === "" ? JSON.stringify(value) : JSON.stringify(value, null, " ");
|
|
57
|
+
if (json === void 0) throw new TypeError("Cannot stringify a value that has no JSON representation");
|
|
58
|
+
const indented = indent === "" || indent === " " ? json : json.replace(/^\t+/gm, (tabs) => indent.repeat(tabs.length));
|
|
59
|
+
return (eol === "\n" ? indented : indented.replace(/\n/g, eol)) + eol;
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { detectIndent, stringifyJson };
|