From 31bfff6fdff068f58bfbdcaec4772df2932150d2 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sun, 11 Oct 2026 09:28:28 +0800 Subject: [PATCH] perf(domain): order JSON shapes through their structural tree Traverse sorted siblings instead of comparing complete shared path prefixes. Return the typed producer result directly while preserving independent path segments and schema validation at unknown-input boundaries. Validation: 12 domain/CDP boundary tests, build, and check:fast passed. Six paired Flameox blocks retained identical full-output digests across deep, mixed, Unicode, and wide controls. Depth-512 inference median fell from 10.78 ms to 1.51 ms (120 samples per variant); whole harness median fell from 518.6 ms to 327.5 ms. Flameox evidence: f4a93424f34e5bfea254ada4ef278dc9bbb6c21047c977e20da130e11a8899e9. Timings describe this bounded workload only. --- src/domain/jsonShape.test.ts | 36 +++++++++++++++++++++- src/domain/jsonShape.ts | 58 +++++++++++++++++++----------------- 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/src/domain/jsonShape.test.ts b/src/domain/jsonShape.test.ts index 136c58381..ba0165ffa 100644 --- a/src/domain/jsonShape.test.ts +++ b/src/domain/jsonShape.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { inferJsonShape } from "./jsonShape.js"; +import { inferJsonShape, jsonShapeSchema } from "./jsonShape.js"; const property = (name: string) => ({ kind: "property" as const, name }); const element = { kind: "array-element" as const }; @@ -68,6 +68,40 @@ describe("inferJsonShape", () => { it("rejects malformed JSON", () => { expect(inferJsonShape("not-json")).toBeNull(); }); + + it("orders mixed array and property descendants with parents first", () => { + const shape = inferJsonShape('[{"z":1,"a":{"x":true}},[null],{"a":2}]'); + + expect(shape?.properties.map(({ path }) => path)).toEqual([ + [element], + [element, element], + [element, property("a")], + [element, property("a"), property("x")], + [element, property("z")], + ]); + expect(shape?.properties[2]).toMatchObject({ + types: ["number", "object"], + observations: 2, + }); + }); + + it("retains deep coverage and independently owned path segments", () => { + const depth = 512; + const shape = inferJsonShape("[".repeat(depth) + "1" + "]".repeat(depth)); + expect(shape).not.toBeNull(); + if (shape === null) return; + + expect(shape.node_count).toBe(depth + 1); + expect(shape.max_depth_observed).toBe(depth); + expect(shape.properties).toHaveLength(depth); + expect(shape.properties.at(-1)).toEqual({ + path: Array.from({ length: depth }, () => element), + types: ["number"], + observations: 1, + }); + expect(jsonShapeSchema.parse(shape)).toEqual(shape); + expect(shape.properties[0]?.path[0]).not.toBe(shape.properties[1]?.path[0]); + }); }); describe("JSON shape completeness and ordering", () => { diff --git a/src/domain/jsonShape.ts b/src/domain/jsonShape.ts index 1772585d1..9e158a718 100644 --- a/src/domain/jsonShape.ts +++ b/src/domain/jsonShape.ts @@ -52,7 +52,6 @@ export const inferJsonShape = (text: string): JsonShape | null => { void cause; return null; } - const properties: ShapeNode[] = []; const rootShape: ShapeNode = { path: [], types: new Set(), @@ -77,7 +76,6 @@ export const inferJsonShape = (text: string): JsonShape | null => { observations: 0, }; children.set(key, node); - properties.push(node); return node; }; const pending: Array<{ @@ -109,18 +107,26 @@ export const inferJsonShape = (text: string): JsonShape | null => { pending.push({ value, shape: childShape(current.shape, name) }); } } - return jsonShapeSchema.parse({ + // Preorder over sorted siblings gives canonical paths without repeatedly + // comparing their shared prefixes. Each returned path owns its segments. + const properties: JsonShape["properties"] = []; + const shapes = orderedChildren(rootShape); + while (shapes.length > 0) { + const shape = shapes.pop(); + if (shape === undefined) break; + properties.push({ + path: shape.path.map((segment) => ({ ...segment })), + types: [...shape.types].sort(), + observations: shape.observations, + }); + for (const child of orderedChildren(shape)) shapes.push(child); + } + return { root_type: jsonValueType(root), node_count: nodeCount, max_depth_observed: maxDepthObserved, - properties: properties - .map((value) => ({ - path: value.path, - types: [...value.types].sort(), - observations: value.observations, - })) - .sort((left, right) => compareShapePaths(left.path, right.path)), - }); + properties, + }; }; const jsonValueType = (value: unknown): JsonValueType => { @@ -141,20 +147,16 @@ const jsonValueType = (value: unknown): JsonValueType => { const isRecord = (value: unknown): value is Readonly> => typeof value === "object" && value !== null && !Array.isArray(value); -const compareShapePaths = ( - left: readonly JsonShapePathSegment[], - right: readonly JsonShapePathSegment[], -): number => { - for (let index = 0; index < Math.min(left.length, right.length); index += 1) { - const a = left[index]; - const b = right[index]; - if (a === undefined || b === undefined) break; - const order = - compareUnicodeCodePoints(a.kind, b.kind) || - (a.kind === "property" && b.kind === "property" - ? compareUnicodeCodePoints(a.name, b.name) - : 0); - if (order !== 0) return order; - } - return left.length - right.length; -}; +/** Reverse sibling order for the traversal stack; array elements sort first. */ +const orderedChildren = (node: ShapeNode): ShapeNode[] => + [...(node.children ?? [])] + .sort(([left], [right]) => + left === right + ? 0 + : left === arrayElementKey + ? 1 + : right === arrayElementKey + ? -1 + : compareUnicodeCodePoints(right, left), + ) + .map(([, child]) => child);