import type {FuncKeywordDefinition, AnySchemaObject} from "ajv" import equal = require("fast-deep-equal") const SCALAR_TYPES = ["number", "integer", "string", "boolean", "null"] export default function getDef(): FuncKeywordDefinition { return { keyword: "uniqueItemProperties", type: "array", schemaType: "array", compile(keys: string[], parentSchema: AnySchemaObject) { const scalar = getScalarKeys(keys, parentSchema) return (data) => { if (data.length <= 1) return true for (let k = 0; k < keys.length; k++) { const key = keys[k] if (scalar[k]) { const hash: Record = {} for (const x of data) { if (!x || typeof x != "object") continue let p = x[key] if (p && typeof p == "object") continue if (typeof p == "string") p = '"' + p if (hash[p]) return false hash[p] = true } } else { for (let i = data.length; i--; ) { const x = data[i] if (!x || typeof x != "object") continue for (let j = i; j--; ) { const y = data[j] if (y && typeof y == "object" && equal(x[key], y[key])) return false } } } } return true } }, metaSchema: { type: "array", items: {type: "string"}, }, } } function getScalarKeys(keys: string[], schema: AnySchemaObject): boolean[] { return keys.map((key) => { const t = schema.items?.properties?.[key]?.type return Array.isArray(t) ? !t.includes("object") && !t.includes("array") : SCALAR_TYPES.includes(t) }) } module.exports = getDef