Как проверить схему JSON на основе комбинации массива перечислений?

Данный:

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

Скажем, поля таковы для каждого типа контакта:

  • Если основной контакт, то я хочу phone_number
  • Если студент, то я хочу 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