Repository navigation
fix(coerce): unwrap nested stringified JSON objects to satisfy schema - #1095
Conversation
Models sometimes double-encode a NESTED field as a JSON string —
{"attachment":"{...}"} instead of {"attachment":{...}} — which fails schema
validation even though the intended object is present. Observed in production:
a structured {summary, attachment} response came back with attachment as a JSON
string, so the declared file type (pdf) was lost and the content degraded to a
markdown/text fallback.
coerceJsonToSchema already unwraps a stringified TOP-LEVEL object; this extends
it to nested fields. When a parsed candidate fails schema.safeParse, string
fields whose value is itself a JSON object/array are parsed (deepUnwrapJsonStrings)
and the result is re-validated:
- The safeParse gate is what makes it safe — an over-eager unwrap (a field that
should stay a string) fails re-validation and is rejected, falling back to the
prior behaviour.
- A parsed string is NOT re-descended into, so an attachment's own string
content (even if it looks like JSON) is preserved verbatim.
- Recursion is depth-bounded; only runs when a schema is supplied (no schema =
unchanged first-parseable-wins behaviour).
Deterministic suite added; existing structured-coerce suite still green.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
More reviews will be available in 6 minutes and 17 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Single Commit Policy - COMPLIANTStatus: Policy requirements met • 1 commit • Valid format • Ready for merge 📊 View validation details📝 Commit Details
✅ Validation Results
🤖 Automated validation by NeuroLink Single Commit Enforcement |
🤖 AI Review & Build Compliance ✅Status: AI analysis complete • Build rules validated • Ready for review 📊 View detailed analysis results🛡️ Analysis Complete
📋 Ready for Merge When
🤖 AI analysis complete - check individual code comments for specific feedback |
Tara-ag
left a comment
There was a problem hiding this comment.
Review Summary
Files reviewed: 2
New issues: 0 (all clear)
Analysis
This PR addresses a production issue where models double-encode nested fields as JSON strings (e.g., {"attachment": "{...}"} instead of {"attachment": {...}}). The fix is well-architected with multiple safety layers:
src/lib/utils/json/coerce.ts:
- ✅ Depth-bounded recursion (
MAX_NESTED_UNWRAP_DEPTH = 6) prevents stack overflow on pathological inputs - ✅ Schema re-validation gate prevents over-unwrapping fields that should remain strings
- ✅ No re-descent into parsed objects preserves intended string content (e.g., file content)
- ✅ Immutable implementation returns new values, never mutates input
- ✅
repaired: truecorrectly signals when transformation occurred
test/continuous-test-suite-coerce-nested-unwrap.ts:
- ✅ Covers the production bug case (Tara's attachment envelope)
- ✅ Validates content preservation (no re-descent)
- ✅ Tests the over-unwrap safety gate
- ✅ Includes null handling and no-schema boundary cases
- ✅ Follows established test harness patterns
Verification Against Standards
- CLAUDE.md Rule compliance: No violations detected
- Security: No hardcoded secrets, safe JSON parsing with try/catch
- Type safety: Proper
unknownhandling, noanycasts - Backward compatibility: No breaking changes to public API
The implementation elegantly solves the nested double-encoding problem while maintaining defensive programming practices. Approved for merge.
|
🎉 This PR is included in version 9.70.7 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Problem
Models sometimes double-encode a nested field as a JSON string —
{"attachment":"{...}"}instead of{"attachment":{...}}— which fails schema validation even though the intended object is right there.Observed in production (Tara's
{summary, attachment}envelope): a structured response came back withattachmentas a JSON string, so the declared file type (pdf) was lost and the content degraded to a markdown/text fallback.coerceJsonToSchemaalready unwraps a stringified top-level object (the JSON-string-literal case), but not a stringified nested field.Fix
When a parsed candidate fails
schema.safeParse, run a depth-boundeddeepUnwrapJsonStringspass that parses any string-valued field whose content is itself a JSON object/array, then re-validate:safeParsegate is the safety net — an over-eager unwrap (a field that should stay a string) fails re-validation and is rejected, falling back to prior behaviour.content(even if it looks like JSON) is preserved verbatim.This generalizes to any double-encoded nested field, for every NeuroLink consumer — not just curator.
Test
Covers: the production attachment-as-string case, content preservation (no re-descent), clean passthrough, null attachment, the over-unwrap gate (JSON-looking string kept as string), and the no-schema boundary.
tscclean; eslint clean.Follow-up to the structured-output line of fixes (#1088 top-level unwrap, #1092 responseSchema sanitize, #1093 400-abort). Curator picks this up on the next
@juspay/neurolinkbump.