const POSTGRES_JSON_REPLACEMENT_CHARACTER = '\uFFFD'; const POSTGRES_JSON_SUSPECT_CODE_UNIT_RE = /[\u0000\uD800-\uDFFF]/; function sanitizePostgresJsonString(value: string): string { if (!POSTGRES_JSON_SUSPECT_CODE_UNIT_RE.test(value)) return value; let sanitized = ''; let changed = false; for (let index = 0; index < value.length; index += 1) { const codeUnit = value.charCodeAt(index); if (codeUnit === 0) { changed = true; continue; } if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) { const nextCodeUnit = value.charCodeAt(index + 1); if (nextCodeUnit >= 0xdc00 && nextCodeUnit <= 0xdfff) { sanitized += value[index] + value[index + 1]; index += 1; continue; } sanitized += POSTGRES_JSON_REPLACEMENT_CHARACTER; changed = true; continue; } if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) { sanitized += POSTGRES_JSON_REPLACEMENT_CHARACTER; changed = true; continue; } sanitized += value[index]; } return changed ? sanitized : value; } function postgresJsonReplacer(_key: string, value: unknown): unknown { if (typeof value === 'string') { return sanitizePostgresJsonString(value); } if (value === null || typeof value !== 'object' || Array.isArray(value)) { return value; } const keys = Object.keys(value); if (!keys.some((key) => sanitizePostgresJsonString(key) !== key)) { return value; } const sanitizedEntries: Array<[string, unknown]> = []; const sanitizedKeys = new Set(); for (const key of keys) { const sanitizedKey = sanitizePostgresJsonString(key); if (sanitizedKeys.has(sanitizedKey)) { throw new TypeError( 'Postgres JSON key normalization produced a duplicate object key.', ); } sanitizedKeys.add(sanitizedKey); sanitizedEntries.push([ sanitizedKey, (value as Record)[key], ]); } return Object.fromEntries(sanitizedEntries); } export function sanitizePostgresJsonValue(value: T): T { const serialized = stringifyPostgresJson(value); return serialized === undefined ? value : (JSON.parse(serialized) as T); } export function stringifyPostgresJson(value: unknown): string | undefined { return JSON.stringify(value, postgresJsonReplacer); }