perf(config): prefer native JSON parsing

This commit is contained in:
Peter Steinberger
2026-05-27 20:56:51 +01:00
parent c71c49c460
commit 4da2b5f4d9
4 changed files with 32 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
import * as fs from "node:fs/promises";
import path from "node:path";
import JSON5 from "json5";
import { parseJsonWithJson5Fallback } from "../utils/parse-json-compat.js";
import { INCLUDE_KEY, MAX_INCLUDE_DEPTH } from "./includes.js";
function listDirectIncludes(parsed: unknown): string[] {
@@ -70,7 +70,7 @@ export async function collectIncludePathsRecursive(params: {
}
const nestedParsed = (() => {
try {
return JSON5.parse(rawText);
return parseJsonWithJson5Fallback(rawText);
} catch {
return null;
}

View File

@@ -12,10 +12,10 @@
import fs from "node:fs";
import path from "node:path";
import JSON5 from "json5";
import { canUseRootFileOpen, openRootFileSync } from "../infra/boundary-file-read.js";
import { isPathInside } from "../security/scan-paths.js";
import { isPlainObject } from "../utils.js";
import { parseJsonWithJson5Fallback } from "../utils/parse-json-compat.js";
import { isBlockedObjectKey } from "./prototype-keys.js";
export const INCLUDE_KEY = "$include";
@@ -421,7 +421,7 @@ const defaultResolver: IncludeResolver = {
readFile: (p) => fs.readFileSync(p, "utf-8"),
readFileWithGuards: ({ includePath, resolvedPath, rootRealDir }) =>
readConfigIncludeFileWithGuards({ includePath, resolvedPath, rootRealDir }),
parseJson: (raw) => JSON5.parse(raw),
parseJson: parseJsonWithJson5Fallback,
};
/**

View File

@@ -0,0 +1,22 @@
import { describe, expect, it, vi } from "vitest";
import { parseConfigJson5 } from "./config.js";
describe("parseConfigJson5", () => {
it("uses native JSON parsing before JSON5 fallback", () => {
const json5 = { parse: vi.fn(() => ({ fromJson5: true })) };
const result = parseConfigJson5('{"gateway":{"mode":"local"}}', json5);
expect(result).toEqual({ ok: true, parsed: { gateway: { mode: "local" } } });
expect(json5.parse).not.toHaveBeenCalled();
});
it("falls back to JSON5 for authored config syntax", () => {
const json5 = { parse: vi.fn(() => ({ gateway: { mode: "local" } })) };
const result = parseConfigJson5("{ gateway: { mode: 'local' } }", json5);
expect(result).toEqual({ ok: true, parsed: { gateway: { mode: "local" } } });
expect(json5.parse).toHaveBeenCalledOnce();
});
});

View File

@@ -1042,6 +1042,12 @@ export function parseConfigJson5(
raw: string,
json5: { parse: (value: string) => unknown } = JSON5,
): ParseConfigJson5Result {
try {
return { ok: true, parsed: JSON.parse(raw) };
} catch {
// Keep JSON5 compatibility for authored config, but avoid the slower parser
// on the JSON files OpenClaw writes itself.
}
try {
return { ok: true, parsed: json5.parse(raw) };
} catch (err) {