bestjsonformatter 0.1.0 → 0.2.1

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.
@@ -0,0 +1,543 @@
1
+ import { canonicalJsonNumber, formatLosslessJson, JsonSyntaxError, jsonNodeToNative, LosslessNumber, parseLosslessJson, serializeJsonNode, stringifyNativeLosslessly, } from "./lossless-json.js";
2
+ const textEncoder = new TextEncoder();
3
+ function locationFromOffset(input, offset) {
4
+ const before = input.slice(0, offset);
5
+ const lines = before.split("\n");
6
+ return { line: lines.length, column: (lines.at(-1)?.length ?? 0) + 1 };
7
+ }
8
+ function diagnosticPath(path) {
9
+ return path.reduce((result, part) => {
10
+ if (typeof part === "number")
11
+ return `${result}[${part}]`;
12
+ return /^[A-Za-z_$][\w$]*$/.test(part)
13
+ ? `${result}.${part}`
14
+ : `${result}[${JSON.stringify(part)}]`;
15
+ }, "$");
16
+ }
17
+ function duplicateKeyReport(input, root) {
18
+ const findings = [];
19
+ const visit = (node, path) => {
20
+ if (node.kind === "array") {
21
+ node.items.forEach((item, index) => {
22
+ visit(item, [...path, index]);
23
+ });
24
+ return;
25
+ }
26
+ if (node.kind !== "object")
27
+ return;
28
+ const entriesByKey = new Map();
29
+ for (const entry of node.entries) {
30
+ const entries = entriesByKey.get(entry.key);
31
+ if (entries)
32
+ entries.push(entry);
33
+ else
34
+ entriesByKey.set(entry.key, [entry]);
35
+ visit(entry.value, [...path, entry.key]);
36
+ }
37
+ for (const [key, entries] of entriesByKey) {
38
+ if (entries.length < 2)
39
+ continue;
40
+ findings.push({
41
+ path: diagnosticPath([...path, key]),
42
+ locations: entries.map((entry) => locationFromOffset(input, entry.keyStart)),
43
+ });
44
+ }
45
+ };
46
+ visit(root, []);
47
+ if (!findings.length) {
48
+ return {
49
+ value: "No duplicate property names found. Every object key is unique.",
50
+ findings: 0,
51
+ };
52
+ }
53
+ return {
54
+ value: [
55
+ `${findings.length} duplicate ${findings.length === 1 ? "property group" : "property groups"} found.`,
56
+ "Most object parsers keep only the last occurrence, so earlier values can disappear without warning.",
57
+ "",
58
+ ...findings.flatMap((finding, index) => [
59
+ `${index + 1}. ${finding.path}`,
60
+ ` ${finding.locations.length} occurrences · ${finding.locations
61
+ .map(({ line, column }) => `line ${line}, column ${column}`)
62
+ .join(" · ")}`,
63
+ ]),
64
+ ].join("\n"),
65
+ findings: findings.length,
66
+ };
67
+ }
68
+ function numberPrecisionReport(input, root) {
69
+ const findings = [];
70
+ const visit = (node, path) => {
71
+ if (node.kind === "array") {
72
+ node.items.forEach((item, index) => {
73
+ visit(item, [...path, index]);
74
+ });
75
+ return;
76
+ }
77
+ if (node.kind === "object") {
78
+ node.entries.forEach((entry) => {
79
+ visit(entry.value, [...path, entry.key]);
80
+ });
81
+ return;
82
+ }
83
+ if (node.kind !== "number")
84
+ return;
85
+ const numeric = Number(node.raw);
86
+ let issue = "";
87
+ if (!Number.isFinite(numeric)) {
88
+ issue = "overflows JavaScript Number and becomes Infinity";
89
+ }
90
+ else if (numeric === 0 && canonicalJsonNumber(node.raw) !== "0") {
91
+ issue = "underflows JavaScript Number and becomes zero";
92
+ }
93
+ else {
94
+ const roundTrip = String(numeric);
95
+ if (canonicalJsonNumber(node.raw) !== canonicalJsonNumber(roundTrip)) {
96
+ issue = `rounds to ${roundTrip} as a JavaScript Number`;
97
+ }
98
+ else if (roundTrip !== node.raw) {
99
+ issue = `keeps its value but normalizes its spelling to ${roundTrip}`;
100
+ }
101
+ }
102
+ if (!issue)
103
+ return;
104
+ findings.push({
105
+ path: diagnosticPath(path),
106
+ raw: node.raw,
107
+ ...locationFromOffset(input, node.start),
108
+ issue,
109
+ });
110
+ };
111
+ visit(root, []);
112
+ if (!findings.length) {
113
+ return {
114
+ value: "No JavaScript number precision or notation risks found.",
115
+ findings: 0,
116
+ };
117
+ }
118
+ return {
119
+ value: [
120
+ `${findings.length} numeric ${findings.length === 1 ? "risk" : "risks"} found.`,
121
+ "Best JSON Formatter preserves every original number token. The notes below describe what ordinary JavaScript Number conversion would do.",
122
+ "",
123
+ ...findings.flatMap((finding, index) => [
124
+ `${index + 1}. ${finding.path} · line ${finding.line}, column ${finding.column}`,
125
+ ` ${finding.raw} — ${finding.issue}.`,
126
+ ]),
127
+ ].join("\n"),
128
+ findings: findings.length,
129
+ };
130
+ }
131
+ function parseError(input, error) {
132
+ if (error instanceof JsonSyntaxError) {
133
+ return {
134
+ ok: false,
135
+ message: error.message,
136
+ line: error.line,
137
+ column: error.column,
138
+ offset: error.offset,
139
+ suggestion: "Use Repair to preview a safe correction.",
140
+ };
141
+ }
142
+ const message = error instanceof Error ? error.message : "Invalid JSON";
143
+ const cleanMessage = message
144
+ .replace(/^JSON\.parse:\s*/i, "")
145
+ .replace(/\s+at position\s+\d+(?:\s+\(line\s+\d+\s+column\s+\d+\))?$/i, "")
146
+ .replace(/\s+at line\s+\d+\s+column\s+\d+.*$/i, "");
147
+ const positionMatch = message.match(/position\s+(\d+)/i);
148
+ const lineColumnMatch = message.match(/line\s+(\d+)\s+column\s+(\d+)/i);
149
+ const offset = positionMatch ? Number(positionMatch[1]) : undefined;
150
+ const location = offset !== undefined
151
+ ? locationFromOffset(input, offset)
152
+ : lineColumnMatch
153
+ ? { line: Number(lineColumnMatch[1]), column: Number(lineColumnMatch[2]) }
154
+ : {};
155
+ return {
156
+ ok: false,
157
+ message: cleanMessage,
158
+ offset,
159
+ ...location,
160
+ suggestion: "Use Repair to preview a safe correction.",
161
+ };
162
+ }
163
+ export function parseJson(input) {
164
+ try {
165
+ const parsed = parseLosslessJson(input, { buildTree: true });
166
+ return { ok: true, data: jsonNodeToNative(parsed.root, false) };
167
+ }
168
+ catch (error) {
169
+ return parseError(input, error);
170
+ }
171
+ }
172
+ function countDiffLines(value) {
173
+ if (!value)
174
+ return 0;
175
+ return value.endsWith("\n") ? value.slice(0, -1).split("\n").length : value.split("\n").length;
176
+ }
177
+ function prefixDiffLines(value, prefix) {
178
+ const withoutTrailingNewline = value.endsWith("\n") ? value.slice(0, -1) : value;
179
+ return withoutTrailingNewline.split("\n").map((line) => `${prefix}${line}`);
180
+ }
181
+ function escapeXml(value) {
182
+ return value
183
+ .replaceAll("&", "&amp;")
184
+ .replaceAll("<", "&lt;")
185
+ .replaceAll(">", "&gt;")
186
+ .replaceAll('"', "&quot;")
187
+ .replaceAll("'", "&apos;");
188
+ }
189
+ function xmlTag(key) {
190
+ return /^[A-Za-z_][\w.-]*$/.test(key) ? key : "item";
191
+ }
192
+ function toXml(value, key = "root", depth = 0) {
193
+ const tag = xmlTag(key);
194
+ const keyAttribute = tag === key ? "" : ` key="${escapeXml(key)}"`;
195
+ const padding = " ".repeat(depth);
196
+ if (value.kind === "null")
197
+ return `${padding}<${tag}${keyAttribute} null="true" />`;
198
+ if (value.kind === "array") {
199
+ if (!value.items.length)
200
+ return `${padding}<${tag}${keyAttribute} />`;
201
+ const children = value.items.map((child) => toXml(child, "item", depth + 1)).join("\n");
202
+ return `${padding}<${tag}${keyAttribute}>\n${children}\n${padding}</${tag}>`;
203
+ }
204
+ if (value.kind === "object") {
205
+ if (!value.entries.length)
206
+ return `${padding}<${tag}${keyAttribute} />`;
207
+ const children = value.entries
208
+ .map((entry) => toXml(entry.value, entry.key, depth + 1))
209
+ .join("\n");
210
+ return `${padding}<${tag}${keyAttribute}>\n${children}\n${padding}</${tag}>`;
211
+ }
212
+ const primitive = value.kind === "string" ? value.value : value.raw;
213
+ return `${padding}<${tag}${keyAttribute}>${escapeXml(primitive)}</${tag}>`;
214
+ }
215
+ function csvCell(value) {
216
+ if (!value || value.kind === "null")
217
+ return "";
218
+ const raw = value.kind === "string"
219
+ ? value.value
220
+ : value.kind === "number" || value.kind === "boolean"
221
+ ? value.raw
222
+ : serializeJsonNode(value, 0);
223
+ return /[",\r\n]/.test(raw) ? `"${raw.replaceAll('"', '""')}"` : raw;
224
+ }
225
+ function toCsv(root) {
226
+ if (root.kind !== "array" || root.items.some((item) => item.kind !== "object")) {
227
+ throw new TypeError("CSV conversion requires an array of objects.");
228
+ }
229
+ const rows = root.items;
230
+ const headers = [];
231
+ const knownHeaders = new Set();
232
+ for (const row of rows) {
233
+ for (const entry of row.entries) {
234
+ if (!knownHeaders.has(entry.key)) {
235
+ knownHeaders.add(entry.key);
236
+ headers.push(entry.key);
237
+ }
238
+ }
239
+ }
240
+ const lines = [
241
+ headers
242
+ .map((header) => csvCell({
243
+ kind: "string",
244
+ value: header,
245
+ raw: JSON.stringify(header),
246
+ start: 0,
247
+ end: 0,
248
+ }))
249
+ .join(","),
250
+ ];
251
+ for (const row of rows) {
252
+ const values = new Map(row.entries.map((entry) => [entry.key, entry.value]));
253
+ lines.push(headers.map((header) => csvCell(values.get(header))).join(","));
254
+ }
255
+ return lines.join("\r\n");
256
+ }
257
+ function formattingEdits(input, output) {
258
+ const edits = [];
259
+ let inputOffset = 0;
260
+ let outputOffset = 0;
261
+ while (inputOffset < input.length || outputOffset < output.length) {
262
+ const inputCharacter = input[inputOffset];
263
+ const outputCharacter = output[outputOffset];
264
+ if (inputCharacter === '"') {
265
+ const inputStart = inputOffset;
266
+ inputOffset += 1;
267
+ while (inputOffset < input.length) {
268
+ if (input[inputOffset] === "\\")
269
+ inputOffset += 2;
270
+ else if (input[inputOffset++] === '"')
271
+ break;
272
+ }
273
+ const tokenLength = inputOffset - inputStart;
274
+ if (output.slice(outputOffset, outputOffset + tokenLength) !==
275
+ input.slice(inputStart, inputOffset)) {
276
+ return undefined;
277
+ }
278
+ outputOffset += tokenLength;
279
+ continue;
280
+ }
281
+ const inputWhitespaceStart = inputOffset;
282
+ const outputWhitespaceStart = outputOffset;
283
+ while (inputOffset < input.length && /[\t\n\r ]/.test(input[inputOffset]))
284
+ inputOffset += 1;
285
+ while (outputOffset < output.length && /[\t\n\r ]/.test(output[outputOffset]))
286
+ outputOffset += 1;
287
+ if (inputOffset !== inputWhitespaceStart || outputOffset !== outputWhitespaceStart) {
288
+ const before = input.slice(inputWhitespaceStart, inputOffset);
289
+ const after = output.slice(outputWhitespaceStart, outputOffset);
290
+ if (before !== after) {
291
+ edits.push({ from: inputWhitespaceStart, to: inputOffset, insert: after });
292
+ if (edits.length > 20_000)
293
+ return undefined;
294
+ }
295
+ continue;
296
+ }
297
+ if (inputCharacter !== outputCharacter)
298
+ return undefined;
299
+ inputOffset += 1;
300
+ outputOffset += 1;
301
+ }
302
+ return edits;
303
+ }
304
+ async function executeTool(request) {
305
+ if (request.operation === "repair") {
306
+ try {
307
+ const { jsonrepair } = await import("jsonrepair");
308
+ const value = jsonrepair(request.input);
309
+ const parsed = parseLosslessJson(value);
310
+ return {
311
+ ok: true,
312
+ value,
313
+ metadata: {
314
+ changed: value !== request.input,
315
+ duplicateKeys: parsed.metadata.duplicateKeys,
316
+ unsafeNumbers: parsed.metadata.unsafeNumbers,
317
+ },
318
+ };
319
+ }
320
+ catch (error) {
321
+ return parseError(request.input, error);
322
+ }
323
+ }
324
+ if (request.operation === "compare") {
325
+ let left;
326
+ let right;
327
+ try {
328
+ left = parseLosslessJson(request.input, { buildTree: true });
329
+ }
330
+ catch (error) {
331
+ return parseError(request.input, error);
332
+ }
333
+ try {
334
+ right = parseLosslessJson(request.secondaryInput ?? "", { buildTree: true });
335
+ }
336
+ catch (error) {
337
+ const failure = parseError(request.secondaryInput ?? "", error);
338
+ return { ...failure, message: `Second document: ${failure.message}` };
339
+ }
340
+ const { diffLines } = await import("diff");
341
+ const leftValue = serializeJsonNode(left.root, 2, true);
342
+ const rightValue = serializeJsonNode(right.root, 2, true);
343
+ const semanticallyIdentical = serializeJsonNode(left.root, 0, true, true) === serializeJsonNode(right.root, 0, true, true);
344
+ const parts = diffLines(leftValue, rightValue);
345
+ const changedAdditions = parts
346
+ .filter((part) => part.added)
347
+ .reduce((total, part) => total + countDiffLines(part.value), 0);
348
+ const changedRemovals = parts
349
+ .filter((part) => part.removed)
350
+ .reduce((total, part) => total + countDiffLines(part.value), 0);
351
+ const identical = semanticallyIdentical;
352
+ const additions = identical ? 0 : changedAdditions;
353
+ const removals = identical ? 0 : changedRemovals;
354
+ const value = identical
355
+ ? "No structural differences. The JSON documents are equivalent."
356
+ : [
357
+ "--- Original",
358
+ "+++ Changed",
359
+ ...parts.flatMap((part) => prefixDiffLines(part.value, part.added ? "+ " : part.removed ? "- " : " ")),
360
+ ].join("\n");
361
+ return {
362
+ ok: true,
363
+ value,
364
+ metadata: {
365
+ additions,
366
+ removals,
367
+ identical,
368
+ duplicateKeys: left.metadata.duplicateKeys + right.metadata.duplicateKeys,
369
+ unsafeNumbers: left.metadata.unsafeNumbers + right.metadata.unsafeNumbers,
370
+ },
371
+ };
372
+ }
373
+ if (request.operation === "schema" ||
374
+ request.operation === "schema_validate" ||
375
+ request.operation === "schema_sample") {
376
+ try {
377
+ const schemaTools = await import("./json-schema.js");
378
+ if (request.operation === "schema") {
379
+ const inferred = schemaTools.inferJsonSchema(request.input);
380
+ return {
381
+ ok: true,
382
+ value: JSON.stringify(inferred.schema, null, 2),
383
+ metadata: {
384
+ duplicateKeys: inferred.metadata.duplicateKeys,
385
+ unsafeNumbers: inferred.metadata.unsafeNumbers,
386
+ },
387
+ };
388
+ }
389
+ if (request.operation === "schema_sample") {
390
+ const sample = schemaTools.generateJsonSample(request.input);
391
+ return {
392
+ ok: true,
393
+ value: sample.value,
394
+ metadata: {
395
+ duplicateKeys: sample.metadata.duplicateKeys,
396
+ unsafeNumbers: sample.metadata.unsafeNumbers,
397
+ },
398
+ };
399
+ }
400
+ const validated = await schemaTools.validateJsonSchema(request.input, request.secondaryInput ?? "");
401
+ return {
402
+ ok: true,
403
+ value: validated.valid
404
+ ? "Valid against this JSON Schema."
405
+ : validated.errors.map((error, index) => `${index + 1}. ${error}`).join("\n"),
406
+ metadata: {
407
+ ...validated.metadata,
408
+ schemaValid: validated.valid,
409
+ results: validated.errors.length,
410
+ },
411
+ };
412
+ }
413
+ catch (error) {
414
+ return parseError(request.operation === "schema_validate" && !request.secondaryInput?.trim()
415
+ ? (request.secondaryInput ?? "")
416
+ : request.input, error);
417
+ }
418
+ }
419
+ try {
420
+ if (request.operation === "duplicates" || request.operation === "precision") {
421
+ const parsed = parseLosslessJson(request.input, { buildTree: true });
422
+ const report = request.operation === "duplicates"
423
+ ? duplicateKeyReport(request.input, parsed.root)
424
+ : numberPrecisionReport(request.input, parsed.root);
425
+ return {
426
+ ok: true,
427
+ value: report.value,
428
+ metadata: {
429
+ type: parsed.metadata.type,
430
+ duplicateKeys: parsed.metadata.duplicateKeys,
431
+ unsafeNumbers: parsed.metadata.unsafeNumbers,
432
+ values: parsed.metadata.values,
433
+ results: report.findings,
434
+ },
435
+ };
436
+ }
437
+ if (request.operation === "format" ||
438
+ request.operation === "minify" ||
439
+ request.operation === "validate") {
440
+ if (request.operation === "validate") {
441
+ const parsed = parseLosslessJson(request.input);
442
+ return {
443
+ ok: true,
444
+ value: request.input,
445
+ metadata: {
446
+ type: parsed.metadata.type,
447
+ duplicateKeys: parsed.metadata.duplicateKeys,
448
+ unsafeNumbers: parsed.metadata.unsafeNumbers,
449
+ values: parsed.metadata.values,
450
+ bytes: textEncoder.encode(request.input).length,
451
+ },
452
+ };
453
+ }
454
+ const parsed = formatLosslessJson(request.input, request.operation === "minify" ? 0 : request.indent);
455
+ return {
456
+ ok: true,
457
+ value: parsed.formatted,
458
+ edits: request.includeEdits === false
459
+ ? undefined
460
+ : formattingEdits(request.input, parsed.formatted),
461
+ metadata: {
462
+ type: parsed.metadata.type,
463
+ duplicateKeys: parsed.metadata.duplicateKeys,
464
+ unsafeNumbers: parsed.metadata.unsafeNumbers,
465
+ values: parsed.metadata.values,
466
+ },
467
+ };
468
+ }
469
+ const parsed = parseLosslessJson(request.input, { buildTree: true });
470
+ const root = parsed.root;
471
+ const parseMetadata = {
472
+ duplicateKeys: parsed.metadata.duplicateKeys,
473
+ unsafeNumbers: parsed.metadata.unsafeNumbers,
474
+ };
475
+ switch (request.operation) {
476
+ case "sort":
477
+ return {
478
+ ok: true,
479
+ value: serializeJsonNode(root, request.indent, true),
480
+ metadata: parseMetadata,
481
+ };
482
+ case "yaml": {
483
+ const losslessNumberTag = {
484
+ identify: (value) => value instanceof LosslessNumber,
485
+ default: true,
486
+ tag: "tag:yaml.org,2002:float",
487
+ test: /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/,
488
+ resolve: (raw) => new LosslessNumber(raw),
489
+ stringify: (node) => node.value.raw,
490
+ };
491
+ return {
492
+ ok: true,
493
+ value: (await import("yaml")).stringify(jsonNodeToNative(root, true), {
494
+ customTags: [losslessNumberTag],
495
+ }),
496
+ metadata: parseMetadata,
497
+ };
498
+ }
499
+ case "csv":
500
+ return { ok: true, value: toCsv(root), metadata: parseMetadata };
501
+ case "xml":
502
+ return {
503
+ ok: true,
504
+ value: `<?xml version="1.0" encoding="UTF-8"?>\n${toXml(root)}`,
505
+ metadata: parseMetadata,
506
+ };
507
+ case "jsonpath": {
508
+ const query = request.query?.trim();
509
+ if (!query)
510
+ return { ok: false, message: "Enter a JSONPath query, such as $.store.book[*].title." };
511
+ const { JSONPath } = await import("jsonpath-plus");
512
+ const result = JSONPath({ path: query, json: jsonNodeToNative(root, true) });
513
+ return {
514
+ ok: true,
515
+ value: stringifyNativeLosslessly(result, request.indent),
516
+ metadata: {
517
+ ...parseMetadata,
518
+ results: Array.isArray(result) ? result.length : 1,
519
+ },
520
+ };
521
+ }
522
+ }
523
+ }
524
+ catch (error) {
525
+ if (error instanceof JsonSyntaxError)
526
+ return parseError(request.input, error);
527
+ return {
528
+ ok: false,
529
+ message: error instanceof Error ? error.message : "The operation could not be completed.",
530
+ };
531
+ }
532
+ }
533
+ export async function runTool(request) {
534
+ const result = await executeTool(request);
535
+ return {
536
+ ...result,
537
+ metadata: {
538
+ inputBytes: textEncoder.encode(request.input).byteLength,
539
+ ...(result.ok ? { outputBytes: textEncoder.encode(result.value).byteLength } : {}),
540
+ ...result.metadata,
541
+ },
542
+ };
543
+ }
@@ -0,0 +1,83 @@
1
+ export type JsonObjectEntry = {
2
+ key: string;
3
+ keyRaw: string;
4
+ keyStart: number;
5
+ keyEnd: number;
6
+ duplicate: boolean;
7
+ value: JsonNode;
8
+ };
9
+ type JsonNodeBase = {
10
+ start: number;
11
+ end: number;
12
+ };
13
+ export type JsonNode = (JsonNodeBase & {
14
+ kind: "object";
15
+ entries: JsonObjectEntry[];
16
+ }) | (JsonNodeBase & {
17
+ kind: "array";
18
+ items: JsonNode[];
19
+ }) | (JsonNodeBase & {
20
+ kind: "string";
21
+ value: string;
22
+ raw: string;
23
+ }) | (JsonNodeBase & {
24
+ kind: "number";
25
+ raw: string;
26
+ }) | (JsonNodeBase & {
27
+ kind: "boolean";
28
+ value: boolean;
29
+ raw: "true" | "false";
30
+ }) | (JsonNodeBase & {
31
+ kind: "null";
32
+ raw: "null";
33
+ });
34
+ export type JsonParseMetadata = {
35
+ type: JsonNode["kind"];
36
+ duplicateKeys: number;
37
+ unsafeNumbers: number;
38
+ values: number;
39
+ treeTruncated: boolean;
40
+ };
41
+ export type LosslessParseResult = {
42
+ root?: JsonNode;
43
+ formatted?: string;
44
+ metadata: JsonParseMetadata;
45
+ };
46
+ export type LosslessTreeResult = LosslessParseResult & {
47
+ root: JsonNode;
48
+ };
49
+ export type LosslessFormatResult = LosslessParseResult & {
50
+ formatted: string;
51
+ };
52
+ export declare class JsonSyntaxError extends Error {
53
+ offset: number;
54
+ line: number;
55
+ column: number;
56
+ constructor(message: string, input: string, offset: number);
57
+ }
58
+ type ParserOptions = {
59
+ buildTree?: boolean;
60
+ indent?: "" | " " | " " | " " | "\t";
61
+ maxTreeValues?: number;
62
+ };
63
+ export declare function parseLosslessJson(input: string, options: ParserOptions & {
64
+ buildTree: true;
65
+ }): LosslessTreeResult;
66
+ export declare function parseLosslessJson(input: string, options?: ParserOptions): LosslessParseResult;
67
+ export declare function indentToken(indent: 0 | 2 | 3 | 4 | "tab" | undefined): ParserOptions["indent"];
68
+ export declare function formatLosslessJson(input: string, indent: 0 | 2 | 3 | 4 | "tab" | undefined): LosslessFormatResult;
69
+ export declare function serializeJsonNode(node: JsonNode, indent?: 0 | 2 | 3 | 4 | "tab", sortKeys?: boolean, canonicalNumbers?: boolean): string;
70
+ export declare function canonicalJsonNumber(raw: string): string;
71
+ export declare class LosslessNumber {
72
+ readonly raw: string;
73
+ constructor(raw: string);
74
+ valueOf(): number;
75
+ toString(): string;
76
+ }
77
+ export type NativeJson = null | boolean | number | string | bigint | LosslessNumber | NativeJson[] | {
78
+ [key: string]: NativeJson;
79
+ };
80
+ export declare function jsonNodeToNative(node: JsonNode, preserveNumbers?: boolean): NativeJson;
81
+ export declare function stringifyNativeLosslessly(value: unknown, indent?: 0 | 2 | 3 | 4 | "tab"): string;
82
+ export declare function findDeepestNodeAtOffset(node: JsonNode, offset: number): JsonNode;
83
+ export {};