Как да валидирам JSON схема въз основа на комбинация от масив от enum?

дадени:

Кажете, че дефинирам схема за контакти. Но мога да имам основен контакт, ученик или такъв, който е и двете; и различни свойства, които вървят и с трите избора. Типовете контакти са дефинирани в масив от contact_type: [ "Primary Contact", "Student" ], който може да бъде един или и двата.

Кажете, че полетата са като такива за тип контакт:

  • Ако е основен контакт, тогава искам phone_number
  • Ако Student, тогава искам first_name
  • Ако ученик и основен контакт, тогава искам phone_number и first_name

Използване

Използвам Ajv библиотека за валидиране в Node.js с помощта на код като този:

function validator(json_schema){
    const Ajv = require('ajv');
    const ajv = new Ajv({allErrors: true});
    return ajv.compile(json_schema)
}

const validate = validator(json_schema);

const valid = validate(input);

console.log(!!valid); //true or false
console.log(validate.errors)// object or null

Забележка: Имах проблеми с allErrors: true, докато използвах anyOf за това, и използвам изхода на allErrors, за да върна ВСИЧКИ липсващи/невалидни полета обратно на потребителя, вместо да връщам проблеми един по един. Справка: https://github.com/ajv-validator/ajv/issues/980

Схема

Написах следната схема и тя работи, ако направя или ученик, или основен контакт, но когато премина и двете, тя все още иска да потвърди срещу [студент] или [основен контакт], а не и двете.

 {
  "$schema": "http://json-schema.org/draft-07/schema",
  "type": "object",
  "required": [],
  "properties": {},
  "allOf": [
    {
      "if": {
        "properties": {
          "contact_type": {
            "contains": {
              "allOf": [
                {
                  "type": "string",
                  "const": "Primary Contact"
                },
                {
                  "type": "string",
                  "const": "Student"
                }
              ]
            }
          }
        }
      },
      "then": {
        "additionalProperties": false,
        "properties": {
          "contact_type": {
            "type": "array",
            "items": [
              {
                "type": "string",
                "enum": [
                  "Student",
                  "Primary Contact"
                ]
              }
            ]
          },
          "phone": {
            "type": "string"
          },
          "first_name": {
            "type": "string"
          }
        },
        "required": [
          "phone",
          "first_name"
        ]
      }
    },
    {
      "if": {
        "properties": {
          "contact_type": {
            "contains": {
              "type": "string",
              "const": "Student"
            }
          }
        }
      },
      "then": {
        "additionalProperties": false,
        "properties": {
          "contact_type": {
            "type": "array",
            "items": [
              {
                "type": "string",
                "enum": [
                  "Student",
                  "Primary Contact"
                ]
              }
            ]
          },
          "first_name": {
            "type": "string"
          }
        },
        "required": [
          "first_name"
        ]
      }
    },
    {
      "if": {
        "properties": {
          "contact_type": {
            "contains": {
              "type": "string",
              "const": "Primary Contact"
            }
          }
        }
      },
      "then": {
        "additionalProperties": false,
        "properties": {
          "contact_type": {
            "type": "array",
            "items": [
              {
                "type": "string",
                "enum": [
                  "Student",
                  "Primary Contact"
                ]
              }
            ]
          },
          "phone": {
            "type": "string"
          }
        },
        "required": [
          "phone"
        ]
      }
    }
  ]
}

Примерни валидни входове:

  • Само за [основен контакт]:
    {
        "contact_type":["Primary Contact"],
        "phone":"something"
    }
  • Само за [студент]:
    {
        "contact_type":["Student"],
        "first_name":"something"
    }
  • За [основен контакт, студент]
    {
        "contact_type":["Primary Contact", "Student"],
        "phone":"something",
        "first_name":"something"
    }

Въпрос:

Бих искал това да се потвърди дори ако allErrors: true, възможно ли е това? Ако не, как трябва да променя схемата?

Бележки под линия

Не искам да променям contact_type от масив, освен ако не е последна възможност. (това е изискване, но може да бъде нарушено само ако няма друг начин)

Не мога да разреша никакви допълнителни елементи, затова дефинирам напълно всеки обект в операторите if, въпреки че contact_type е често срещано. Ако преместя contact_type навън, тогава получавам съобщения за грешка за предаване на contact_type като допълнителен елемент (той преглежда свойствата на оператора if и не вижда contact_type, когато е изведен на общото място). Ето защо първоначалният ми обект properties е празен.


person Community    schedule 14.07.2020    source източник
comment
Това е напълно възможно и аз съм повече от щастлив да помогна. На пръв поглед изглежда, че сте направили поне една проста грешка, тъй като contains не приема обект на схема. За щастие, това, което искате, може да бъде постигнато по малко по-различен начин. Измислям решение за вас   -  person Relequestual    schedule 14.07.2020
comment
Редактиране: сбърках относно съдържа... Имам нужда от кафе =D   -  person Relequestual    schedule 14.07.2020


Отговори (1)


Ето как бих могъл да реша проблема с валидирането: https://jsonschema.dev/s/XLSDB

Ето схемата... (По-лесно е, ако се опитате да разделите притесненията)

{
  "$schema": "http://json-schema.org/draft-07/schema",
  "type": "object",

Първо, искаме да дефинираме нашите подсхеми за условна проверка...

  "definitions": {
    "is_student": {
      "properties": {
        "contact_type": {
          "contains": {
            "const": "Student"
          }
        }
      }
    },
    "is_primay_contact": {
      "properties": {
        "contact_type": {
          "contains": {
            "const": "Primary Contact"
          }
        }
      }
    }
  },

След това предполагам, че винаги искате contact_type

  "required": ["contact_type"],
  "properties": {
    "contact_type": {
      "type": "array",
      "items": {
        "enum": ["Primary Contact", "Student"]
      }
    },

И ние трябва да дефинираме всички разрешени свойства, за да предотвратим допълнителни свойства. (draft-07 не може да вижда ключови думи на апликатора като allOf. Можете с чернова 2019-09 и по-нататък, но това е друга история)

    "phone": true,
    "first_name": true
  },
  "additionalProperties": false,

Сега трябва да дефинираме нашите структурни ограничения...

  "allOf": [
    {

Ако лицето за контакт е студент, се изисква собствено име.

      "if": { "$ref": "#/definitions/is_student" },
      "then": { "required": ["first_name"] }
    },
    {

Ако контактът е основен контакт, тогава се изисква телефон.

      "if": { "$ref": "#/definitions/is_primay_contact" },
      "then": { "required": ["phone"] }
    },
    {

Освен това обаче, ако контактът е едновременно студент и основен контакт...

      "if": {
        "allOf": [
          { "$ref": "#/definitions/is_student" },
          { "$ref": "#/definitions/is_primay_contact" }
        ]
      },

След това изискваме телефон и собствено име...

      "then": {
        "required": ["phone", "first_name"]
      },

В противен случай телефон или собствено име е добре (което е обхванато от предишния раздел)

      "else": {
        "oneOf": [
          {
            "required": ["phone"]
          },
          {
            "required": ["first_name"]
          }
        ]
      }
    }
  ]
 }

Не съм убеден, че това е най-чистият подход, но работи за изискванията, които сте предоставили.

Що се отнася до получаването на грешки при валидиране, които можете да върнете обратно на вашия КРАЙЕН потребител... предвид условните изисквания, които излагате, това не е нещо, което можете да очаквате с чиста JSON схема...

Като каза това, ajv предоставя разширение за добавяне на персонализирани съобщения за грешка, което предвид начина, по който разделих валидирането на опасения, може да е използваемо за добавяне на персонализирани грешки, както искате да направите (https://github.com/ajv-validator/ajv-errors).

person Relequestual    schedule 14.07.2020
comment
Страхотен отговор. Що се отнася до това, не съм убеден, че това е най-чистият подход, няма чист подход при използване на "additionalProperties": false. Има няколко алтернативи, но всички са еднакво лоши. - person Jason Desrosiers; 15.07.2020
comment
Малка гнида: Последният then е излишен. Може да се отстрани. Освен това в някои случаи ще получите по-добри съобщения за грешка, ако добавите "required": ["contact_type"] към всяка от дефинициите. - person Jason Desrosiers; 15.07.2020