import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Result from "effect/Result"; import * as Schema from "effect/Schema"; import * as SchemaGetter from "effect/SchemaGetter"; import * as SchemaTransformation from "effect/SchemaTransformation"; export const decodeJsonResult = >( schema: S, ) => { const decode = Schema.decodeExit(Schema.fromJsonString(schema)); return (input: string) => { const result = decode(input); if (Exit.isFailure(result)) { return Result.fail(result.cause); } return Result.succeed(result.value); }; }; const decodeJsonString = Schema.decodeEffect(Schema.fromJsonString(Schema.Unknown)); const parseLenientJsonGetter = SchemaGetter.transformEffect((input: string) => { // Match comments together so URLs stay inside blocks; whitespace keeps adjacent tokens apart. let stripped = input.replace( /("(?:[^"\\]|\\.)*")|\/\/[^\r\n]*|\/\*[\s\S]*?\*\//g, (match, stringLiteral: string | undefined) => (stringLiteral ? match : " "), ); stripped = stripped.replace( /("(?:[^"\\]|\\.)*")|,(\s*[}\]])/g, (match, stringLiteral: string | undefined, bracket: string | undefined) => stringLiteral ? match : (bracket ?? ""), ); return decodeJsonString(stripped).pipe(Effect.mapError((error) => error.issue)); }); const fromLenientJsonString = new SchemaTransformation.Transformation( parseLenientJsonGetter, SchemaGetter.stringifyJson(), ); const prettyJsonString = SchemaGetter.parseJson().pipe( SchemaGetter.compose(SchemaGetter.stringifyJson({ space: 2 })), ); export const fromLenientJson = (schema: S) => Schema.String.pipe(Schema.decodeTo(schema, fromLenientJsonString)); export function extractJsonObject(raw: string): string { const trimmed = raw.trim(); if (trimmed.length === 0) { return trimmed; } const start = trimmed.indexOf("{"); if (start < 0) { return trimmed; } let depth = 0; let inString = false; let escaping = false; for (let index = start; index < trimmed.length; index += 1) { const char = trimmed[index]; if (inString) { if (escaping) { escaping = false; } else if (char === "\\") { escaping = true; } else if (char === '"') { inString = false; } continue; } if (char === '"') { inString = true; continue; } if (char === "{") { depth += 1; continue; } if (char === "}") { depth -= 1; if (depth === 0) { return trimmed.slice(start, index + 1); } } } return trimmed.slice(start); } export const fromJsonStringPretty = (schema: S) => Schema.fromJsonString(schema).pipe( Schema.encode({ decode: prettyJsonString, encode: prettyJsonString, }), );