UNPKG

@proofkit/fmodata

Version:

FileMaker OData API client

65 lines (59 loc) 2.54 kB
/** * FileMaker OData API sometimes returns invalid JSON containing unquoted `?` * characters as field values (e.g., `"fieldName": ?`), which causes JSON.parse() * to fail. This module provides utilities to sanitize such responses before parsing. */ import { ResponseParseError } from "../errors"; /** * Sanitizes FileMaker OData JSON responses by replacing unquoted `?` values with `null`. * * FileMaker uses `?` to represent undefined/null values in its OData responses, * but this is not valid JSON. This function converts those to proper `null` values. * * The regex uses two patterns: * 1. `/:\s*\?(?=\s*[,}\]])/g` - for values in objects (after `:`) * 2. `/(?<=[\[,])\s*\?(?=\s*[,\]])/g` - for values in arrays (after `[` or `,`) * * @param text - The raw response text from FileMaker OData API * @returns Sanitized JSON string with `?` values replaced by `null` * * @example * sanitizeFileMakerJson('{"field1": "valid", "field2": ?, "field3": null}') * // Returns: '{"field1": "valid", "field2": null, "field3": null}' */ export function sanitizeFileMakerJson(text: string): string { // Replace unquoted ? values in objects (after colon) // Also handles arrays when the array is a value in an object let result = text.replace(/:\s*\?(?=\s*[,}\]])/g, ": null"); // Replace unquoted ? values directly in arrays (not after colon) // e.g., [1, ?, 3] -> [1, null, 3] result = result.replace(/(?<=[[,])\s*\?(?=\s*[,\]])/g, " null"); return result; } /** * Safely parses a Response body as JSON, handling FileMaker's invalid JSON responses. * * This function reads the response as text first, sanitizes any invalid `?` values, * and then parses the sanitized JSON. This approach handles the case where FileMaker * returns a Content-Type of application/json but the body contains invalid JSON. * * @param response - The fetch Response object * @returns Parsed JSON data * @throws ResponseParseError if the JSON is still invalid after sanitization (includes sanitized text for debugging) */ export async function safeJsonParse<T = unknown>(response: Response): Promise<T> { const text = await response.text(); const sanitized = sanitizeFileMakerJson(text); try { return JSON.parse(sanitized) as T; } catch (err) { throw new ResponseParseError( response.url, `Failed to parse response as JSON: ${err instanceof Error ? err.message : "Unknown error"}`, { rawText: sanitized, cause: err instanceof Error ? err : undefined, }, ); } }