UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

84 lines (81 loc) • 2.09 kB
export function hasExactKeys(value, expected) { return ( value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === expected.length && expected.every((key) => Object.hasOwn(value, key)) ); } /** Reject duplicate object keys before JSON.parse can silently keep the last value. */ function hasDuplicateJsonObjectKeys(text) { const stack = []; let expectingKey = false; let index = 0; const skipWhitespace = () => { while (/\s/u.test(text[index] ?? "")) { index += 1; } }; while (index < text.length) { const char = text[index]; if (char === '"') { const start = index; index += 1; let escaped = false; while (index < text.length) { const next = text[index++]; if (escaped) { escaped = false; } else if (next === "\\") { escaped = true; } else if (next === '"') { break; } } const keys = stack.at(-1); if (expectingKey && keys) { let key; try { key = JSON.parse(text.slice(start, index)); } catch { return false; } skipWhitespace(); if (text[index] === ":" && typeof key === "string") { if (keys.has(key)) { return true; } keys.add(key); expectingKey = false; } } continue; } if (char === "{") { stack.push(new Set()); expectingKey = true; } else if (char === "[") { stack.push(null); expectingKey = false; } else if (char === "}" || char === "]") { stack.pop(); expectingKey = false; } else if (char === ",") { expectingKey = stack.at(-1) instanceof Set; } index += 1; } return false; } export function parseStrictJsonObject(text) { if (hasDuplicateJsonObjectKeys(text)) { return null; } try { const parsed = JSON.parse(text); return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null; } catch { return null; } }