Last active
February 1, 2026 18:21
-
-
Save TiagoGouvea/6ddd3e15ce1552d199f1fd1e1dd1e575 to your computer and use it in GitHub Desktop.
Extracts Json Objects from strings, fix missing root atributes, and so
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { jsonrepair } from 'jsonrepair'; | |
| /** | |
| * Extracts and parses the first JSON object from a string. | |
| * Extra text: trims prefix/suffix chatter around a JSON payload. | |
| * Concatenated JSONs: returns the first balanced object in a sequence. | |
| * Minor glitches: attempts repair for missing quotes/commas via jsonrepair. | |
| */ | |
| export function extractJsonObjectFromString(inputString: string) { | |
| let cleanedInputString, repaired; | |
| try { | |
| cleanedInputString = extractOnlyJson(inputString); | |
| // Strategy 1: Try JSON.parse directly first (fast path for valid JSON). | |
| const possibleJsons = findAllPossibleJsons(cleanedInputString); | |
| for (const jsonCandidate of possibleJsons) { | |
| // Try native JSON.parse first (much faster, no compiled code). | |
| try { | |
| const json = JSON.parse(jsonCandidate); | |
| return json; | |
| } catch (parseError) { | |
| // If native parse fails, try jsonrepair (slower, last resort). | |
| try { | |
| repaired = jsonrepair(jsonCandidate); | |
| const json = JSON.parse(repaired); | |
| return json; | |
| } catch (repairError) { | |
| continue; | |
| } | |
| } | |
| } | |
| // Strategy 2: Fallback to the original approach if no candidates worked. | |
| const originalResult = findFirstCompleteJson(cleanedInputString); | |
| if (originalResult) { | |
| try { | |
| const json = JSON.parse(originalResult); | |
| return json; | |
| } catch (parseError) { | |
| repaired = jsonrepair(originalResult); | |
| const json = JSON.parse(repaired); | |
| return json; | |
| } | |
| } | |
| throw new Error('No JSON found in the string.'); | |
| } catch (error: any) { | |
| throw new Error('Failed to parse JSON into object: ' + error.message); | |
| } | |
| } | |
| /** Extracts the substring between the first and last braces. */ | |
| function extractOnlyJson(str: string) { | |
| const start = str.indexOf('{'); | |
| const end = str.lastIndexOf('}') + 1; // Include the closing brace. | |
| if (start !== -1 && end !== -1) { | |
| return str.slice(start, end); | |
| } else { | |
| return 'Invalid input: no braces found.'; | |
| } | |
| } | |
| /** Checks whether the string may contain JSON braces. */ | |
| export const hasPossibleJson = (str: string) => { | |
| const start = str.indexOf('{'); | |
| const end = str.lastIndexOf('}') + 1; // Include the closing brace. | |
| return start > -1 && end > 1; | |
| }; | |
| /** Validates whether the string is valid JSON. */ | |
| export function isJsonString(str: string) { | |
| try { | |
| JSON.parse(str); | |
| return true; | |
| } catch (e) { | |
| return false; | |
| } | |
| } | |
| /** Finds every complete JSON object in the input. */ | |
| function findAllPossibleJsons(input: string): string[] { | |
| const candidates: string[] = []; | |
| // Look for all occurrences of opening braces. | |
| for (let i = 0; i < input.length; i++) { | |
| if (input[i] === '{') { | |
| const jsonCandidate = findCompleteJsonStartingAt(input, i); | |
| if (jsonCandidate) { | |
| candidates.push(jsonCandidate); | |
| } | |
| } | |
| } | |
| return candidates; | |
| } | |
| /** Returns a balanced JSON object starting at a given index. */ | |
| function findCompleteJsonStartingAt( | |
| input: string, | |
| startIndex: number, | |
| ): string | null { | |
| let braceCount = 0; | |
| let inString = false; | |
| let escapeNext = false; | |
| for (let i = startIndex; i < input.length; i++) { | |
| const char = input[i]; | |
| if (escapeNext) { | |
| escapeNext = false; | |
| continue; | |
| } | |
| if (char === '\\') { | |
| escapeNext = true; | |
| continue; | |
| } | |
| if (char === '"' && !escapeNext) { | |
| inString = !inString; | |
| continue; | |
| } | |
| if (!inString) { | |
| if (char === '{') { | |
| braceCount++; | |
| } else if (char === '}') { | |
| braceCount--; | |
| if (braceCount === 0) { | |
| return input.substring(startIndex, i + 1); | |
| } | |
| } | |
| } | |
| } | |
| return null; // No complete JSON found. | |
| } | |
| /** Finds the first complete JSON object in the input. */ | |
| function findFirstCompleteJson(input: string): string | null { | |
| let braceCount = 0; | |
| let startIndex = -1; | |
| for (let i = 0; i < input.length; i++) { | |
| const char = input[i]; | |
| if (char === '{') { | |
| if (startIndex === -1) startIndex = i; | |
| braceCount++; | |
| } else if (char === '}') { | |
| braceCount--; | |
| if (braceCount === 0 && startIndex !== -1) { | |
| return input.substring(startIndex, i + 1); | |
| } | |
| } | |
| } | |
| return null; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { z } from 'zod'; | |
| export function wrapRootIfMissing(parsed: any, schema: z.ZodTypeAny): any { | |
| if (!(schema instanceof z.ZodObject)) return parsed; | |
| const shape = schema.shape; | |
| const rootKeys = Object.keys(shape); | |
| if (rootKeys.length !== 1) return parsed; | |
| const rootKey = rootKeys[0]!; | |
| const rootSchema = shape[rootKey]; | |
| if (!(rootSchema instanceof z.ZodObject)) return parsed; | |
| if (parsed && typeof parsed === 'object' && rootKey in parsed) return parsed; | |
| const childShape = rootSchema.shape; | |
| const childKeys = Object.keys(childShape); | |
| const hasAllChildren = | |
| parsed && typeof parsed === 'object' && childKeys.every((k) => k in parsed); | |
| if (hasAllChildren) { | |
| console.log( | |
| '🤩 🧱 wrapRootIfMissing: wrapped root', | |
| rootKey, | |
| 'with child keys', | |
| childKeys, | |
| ); | |
| return { [rootKey]: parsed }; | |
| } | |
| return parsed; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment