uniqueItemProperties.ts 1.72 KB
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<string, any> = {}
            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