From b62b7b2ce0c95e5c995dc3a4f067fdc67c4fcc84 Mon Sep 17 00:00:00 2001 From: renxia Date: Mon, 1 Jan 2024 21:38:06 +0800 Subject: [PATCH] =?UTF-8?q?wip(objects):=20=E6=89=A9=E5=B1=95=20safeJsonPa?= =?UTF-8?q?rse=20=E6=96=B9=E6=B3=95=EF=BC=8C=E6=94=AF=E6=8C=81=E5=B0=9D?= =?UTF-8?q?=E8=AF=95=E5=8A=A0=E8=BD=BD=20JSON5=20=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/common/objects.spec.ts | 8 ++++++ src/common/objects.ts | 52 ++++++++++++++++++++++++++++---------- src/node/LiteStorage.ts | 6 ++--- 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index f74d5ff..eca618a 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "eslint-plugin-unicorn": "^50.0.1", "husky": "^8.0.3", "jest": "^29.7.0", + "json5": "^2.2.3", "micromatch": "^4.0.5", "npm-run-all": "^4.1.5", "prettier": "^3.1.1", diff --git a/src/common/objects.spec.ts b/src/common/objects.spec.ts index 257712e..ed1ddf5 100644 --- a/src/common/objects.spec.ts +++ b/src/common/objects.spec.ts @@ -8,6 +8,7 @@ import { safeStringify, ensureArray, safeJsonParse, + tryLoadJSON5, } from './objects'; describe('objects/assign', () => { @@ -18,6 +19,13 @@ describe('objects/assign', () => { expect(safeJsonParse(safeStringify(a))).toStrictEqual(a); expect(safeJsonParse('')).toStrictEqual({}); expect(safeJsonParse(null as never)).toStrictEqual({}); + expect(safeJsonParse('abc', true, true)).toStrictEqual({}); + }); + + it('safeJsonParse use JSON5', async () => { + const jsonStr = `{\n// test\n "a": 1, 'b': 2, c: 3}`; + await tryLoadJSON5(); + expect(safeJsonParse(jsonStr, true).c).toBe(3); }); it('safeStringify', () => { diff --git a/src/common/objects.ts b/src/common/objects.ts index 5af4ac4..ec56614 100644 --- a/src/common/objects.ts +++ b/src/common/objects.ts @@ -1,30 +1,54 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-explicit-any, unicorn/prefer-top-level-await */ import { isObject, isSet, isMap, isArray } from './is'; -export function safeJsonParse>(input: string): T { +let JSON5: typeof globalThis.JSON; + +export async function tryLoadJSON5() { + if (!JSON5) { + JSON5 = globalThis.JSON; + try { + // @ts-ignore + if (globalThis.JSON5) JSON5 = globalThis.JSON5; + else { + const t = await import('json5'); + JSON5 = (t.default || t) as never; + } + } catch { + // quit + } + } + return JSON5; +} +tryLoadJSON5(); + +export function safeJsonParse>(input: string, useJSON5 = false, ignoreError = false): T { try { if (input == null) return {} as T; if (typeof input === 'object') return input; - return JSON.parse(input) as T; + return (useJSON5 ? JSON5 : JSON).parse(input) as T; } catch (error: any) { - console.error('[safeJsonParse]parse error', error.message, error.stack); + if (!ignoreError) console.error('[safeJsonParse]parse error', error.message, error.stack); } return {} as T; } -export function safeStringify(obj: any): string { +export function safeStringify(obj: any, space?: string | number, useJSON5 = false): string { const seen = new Set(); - return JSON.stringify(obj, (_key, value) => { - if (isObject(value) || Array.isArray(value)) { - if (seen.has(value)) { - return '[Circular]'; - } else { - seen.add(value); + return (useJSON5 ? JSON5 : JSON).stringify( + obj, + (_key, value) => { + if (isObject(value) || Array.isArray(value)) { + if (seen.has(value)) { + return '[Circular]'; + } else { + seen.add(value); + } } - } - return value; - }); + return value; + }, + space + ); } /** diff --git a/src/node/LiteStorage.ts b/src/node/LiteStorage.ts index ceb9819..1bae36c 100644 --- a/src/node/LiteStorage.ts +++ b/src/node/LiteStorage.ts @@ -1,7 +1,7 @@ import { homedir } from 'node:os'; import { dirname, resolve } from 'node:path'; import { fs } from './fs-system'; -import { assign } from '../common/objects'; +import { assign, safeJsonParse, safeStringify } from '../common/objects'; export interface LSCache { version: string; @@ -84,7 +84,7 @@ export class LiteStorage> { const TOML = await import('@iarna/toml'); content = TOML.stringify(this.cache as never); } else { - content = JSON.stringify(this.cache, null, 4); + content = safeStringify(this.cache, 4, true); } fs.writeFileSync(this.cachePath, content, 'utf8'); return this; @@ -98,7 +98,7 @@ export class LiteStorage> { const TOML = await import('@iarna/toml'); localCache = JSON.parse(JSON.stringify(TOML.default.parse(content))); } else { - localCache = JSON.parse(content) as LSCache; + localCache = safeJsonParse(content, true) as LSCache; } if (localCache.version === this.options.version) {