From 60e255f167e91a2635badc1d2df9550a72eb3c9d Mon Sep 17 00:00:00 2001 From: can1357 Date: Mon, 6 Apr 2026 18:46:07 +0200 Subject: [PATCH] feat(ai): enabled literal null string preservation in JSON parsing - Removed `coerceNullStrings` function and automatic null-string coercion from JSON parsing, changing behavior to preserve literal `"null"` strings. - Updated `parseStreamingJson` to return parsed JSON without null-string transformation. --- packages/ai/CHANGELOG.md | 5 ++++- packages/ai/src/utils/json-parse.ts | 28 ++-------------------------- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 22b9efee3..8be8bd031 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## [Unreleased] +### Breaking Changes + +- Removed `coerceNullStrings` function and its automatic null-string coercion behavior from JSON parsing ## [13.19.0] - 2026-04-05 @@ -1975,4 +1978,4 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_ ## [0.9.4] - 2025-11-26 -Initial release with multi-provider LLM support. +Initial release with multi-provider LLM support. \ No newline at end of file diff --git a/packages/ai/src/utils/json-parse.ts b/packages/ai/src/utils/json-parse.ts index 795381742..feeb32ad1 100644 --- a/packages/ai/src/utils/json-parse.ts +++ b/packages/ai/src/utils/json-parse.ts @@ -14,39 +14,15 @@ export function parseStreamingJson(partialJson: string | undefined): T // Try standard parsing first (fastest for complete JSON) try { - return coerceNullStrings(JSON.parse(partialJson) as T); + return JSON.parse(partialJson) as T; } catch { // Try partial-json for incomplete JSON try { const result = partialParse(partialJson); - return coerceNullStrings((result ?? {}) as T); + return (result ?? {}) as T; } catch { // If all parsing fails, return empty object return {} as T; } } } - -/** - * Recursively replace the literal string `"null"` with `null` in parsed - * tool-call arguments. Many models serialize optional fields as the four - * character string `null` instead of the JSON keyword `null`, which - * downstream code sees as a truthy non-empty string. - */ -export function coerceNullStrings(value: T): T { - if (value === null || value === undefined) return value; - if (typeof value === "string") { - return (value === "null" ? null : value) as T; - } - if (Array.isArray(value)) { - return value.map(coerceNullStrings) as T; - } - if (typeof value === "object") { - const out: Record = {}; - for (const [k, v] of Object.entries(value as Record)) { - out[k] = coerceNullStrings(v); - } - return out as T; - } - return value; -}