#433

compileSpecStream: a patch whose path passes through null throws TypeError

Opengrayashhopened 17d ago

Reproduction

Reproduced on @json-render/core 0.21.3 (latest) and 0.21.1. main has no commits since 0.21.3 was published, and the checks below are the same there (eeb0424).

import { compileSpecStream } from "@json-render/core";

compileSpecStream(
  [
    '{"op":"add","path":"/elements/main","value":{"type":"Section","props":{},"children":null}}',
    '{"op":"add","path":"/elements/main/children/0","value":"a"}',
  ].join("\n")
);
// TypeError: Cannot set properties of null (setting '0')

replace and remove fail the same way when a parent on the path is null:

compileSpecStream(
  '{"op":"add","path":"/state","value":{"form":null}}\n' +
    '{"op":"replace","path":"/state/form/name","value":"x"}'
);
// TypeError: Cannot set properties of null (setting 'name')

compileSpecStream(
  '{"op":"add","path":"/state","value":{"form":null}}\n' +
    '{"op":"remove","path":"/state/form/name"}'
);
// TypeError: Cannot convert undefined or null to object

A missing parent is created instead:

compileSpecStream('{"op":"add","path":"/elements/main/children/0","value":"a"}');
// { elements: { main: { children: ["a"] } } }

Cause

setByPath, addByPath and removeByPath decide whether an intermediate value is a container with typeof current[segment] !== "object" (and typeof current[index] !== "object" in the array branch). typeof null is "object", so the walk keeps null as the next container and the next property access throws.

https://github.com/vercel-labs/json-render/blob/eeb04244cf3c2f5e15d0a3a9c468737cb589f945/packages/core/src/types.ts#L386-L397

The same checks are at L440-L451 (addByPath) and L485-L496 (removeByPath).

How we hit it

A model streaming SpecStream JSONL wrote "children": null on an element, then added /elements/<key>/children/0. That one patch makes compileSpecStream throw for the whole stream, while a line that is not valid JSON is skipped (parseSpecStreamLine returns null). createSpecStreamCompiler().push() and applySpecPatch() throw the same TypeError on this input, since they go through the same applySpecStreamPatch. We wrap compileSpecStream in a try/catch for now.

Expected

Treat null like a missing value: add and replace create the container, as they do for a missing parent, and remove returns early, as it does for a missing path.

Related: #217 proposes an onError callback in createSpecStreamCompiler for patches that fail to apply. This issue is about the path helpers failing on null in the first place.

Suggested fix

Add a null check to the container tests in all three helpers, for example in the object branch:

if (
  !Object.hasOwn(current, segment) ||
  current[segment] === null ||
  typeof current[segment] !== "object"
) {

and current[index] == null || typeof current[index] !== "object" in the array branch.

Activity

0

No activity yet.