Дополнительное сообщение об ошибке от валидатора схемы JSON при наличии if-else

Вот моя схема JSON (гипотетическая, так как я не могу поделиться своей реальной) и JSON. Условие if-then-else похоже:

одновременно может появиться только одна (1) из трех (3) стран для определения статистики.

Схема

    {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "type": "object",
        "required": [
            "year",
            "country",
            "statistics"
        ],
        "definitions": {
        "statisticsUsa": {
          "if": {
            "properties": {
              "type": {
                "const": "statisticsUsa"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "properties": {
              "type": {
                "const": "statisticsUsa"
              }
            }
          }
        },
            "statisticsFinland": {
          "if": {
            "properties": {
              "type": {
                "const": "statisticsFinland"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "properties": {
              "type": {
                "const": "statisticsFinland"
              }
            }
          }
        },
        "statisticsGermany": {
          "if": {
            "properties": {
              "type": {
                "const": "statisticsGermany"
              }
            },
            "required": [
              "type", "literacy"
            ]
          },
          "then": {
            "properties": {
              "type": {
                "const": "statisticsGermany"
              },
              "literacy": {
                "type": "string"
              }
            }
          }
        }
        },
        "properties": {
            "year": {
                "type": "string"
            },
            "country": {
                "type": "string",
                "enum": [
                    "USA",
                    "FINLAND",
                    "GERMANY"
                ]
            },
            "statistics": {

                "type": "array",
                "allOf": [{
                        "if": {
                            "contains": {
                                "type": "object",
                                "properties": {
                                    "type": {
                                        "type": "string",
                                        "enum": [
                                            "statisticsUsa"
                                        ]
                                    }
                                }
                            }
                        },
                        "then": {
                            "not": {
                                "contains": {
                                    "type": "object",
                                    "properties": {
                                        "type": {
                                            "type": "string",
                                            "enum": [
                                                "statisticsFinland",
                                                "statisticsGermany"
                                            ]
                                        }
                                    }
                                }
                            }
                        },"errorMessage" :"Only USA applicable at a time for statistics"
                    },
                    {
                        "if": {
                            "contains": {
                                "type": "object",
                                "properties": {
                                    "type": {
                                        "type": "string",
                                        "enum": [
                                            "statisticsFinland"
                                        ]
                                    }
                                }
                            }
                        },
                        "then": {
                            "not": {
                                "contains": {
                                    "type": "object",
                                    "properties": {
                                        "type": {
                                            "type": "string",
                                            "enum": [
                                                "statisticsUsa",
                                                "statisticsGermany"
                                            ]
                                        }
                                    }
                                }
                            }
                        },"errorMessage" :"Only Finland applicable at a time for statistics"
                    },
                    {
                        "if": {
                            "contains": {
                                "type": "object",
                                "properties": {
                                    "type": {
                                        "type": "string",
                                        "enum": [
                                            "statisticsGermany"
                                        ]
                                    }
                                }
                            }
                        },
                        "then": {
                            "not": {
                                "contains": {
                                    "type": "object",
                                    "properties": {
                                        "type": {
                                            "type": "string",
                                            "enum": [
                                                "statisticsUsa",
                                                "statisticsFinland"
                                            ]
                                        }
                                    }
                                }
                            }
                        },"errorMessage" :"Only Germany applicable at a time for statistics"
                    }

                ],
                "minItems": 1,
                "items": {
                    "anyOf": [{
                            "$ref": "#/definitions/statisticsUsa"
                        },
                        {
                            "$ref": "#/definitions/statisticsFinland"
                        },
                        {
                            "$ref": "#/definitions/statisticsGermany"
                        }
                    ]
                }
            }
        }
    }

Json 1

    const Ajv = require('ajv');
    const ajv = new Ajv({
        allErrors: true,
        jsonPointers: true
    });

    const schema1 = require("./sof.json");
    require('ajv-errors')(ajv /*, {singleError: true} */);
    const data = {
      year: "2000",
      country: "USA",
      statistics: [
        {"type":"statisticsUsa"},
        {"type":"statisticsFinland"}
      ]
    }

    let valid = ajv.validate(schema1, data); //schema, data
    if (!valid) {
      console.log(`${JSON.stringify(ajv.errors,null,2)}`);
    }

Здесь я вижу 2 ошибки как часть массива, когда у меня 2 страны. Мой вопрос в том, что быть «Финляндией» нельзя с «США», ошибка для «Только США применимы одновременно для статистики» НЕ приемлема, так как удовлетворяет условию. Но ошибка Финдленда приемлема.

    [
      {
        "keyword": "errorMessage",
        "dataPath": "/statistics",
        "schemaPath": "#/properties/statistics/allOf/0/errorMessage",
        "params": {
          "errors": [
            {
              "keyword": "not",
              "dataPath": "/statistics",
              "schemaPath": "#/properties/statistics/allOf/0/then/not",
              "params": {},
              "message": "should NOT be valid"
            },
            {
              "keyword": "if",
              "dataPath": "/statistics",
              "schemaPath": "#/properties/statistics/allOf/0/if",
              "params": {
                "failingKeyword": "then"
              },
              "message": "should match \"then\" schema"
            }
          ]
        },
        "message": "Only USA applicable at a time for statistics"
      },
      {
        "keyword": "errorMessage",
        "dataPath": "/statistics",
        "schemaPath": "#/properties/statistics/allOf/1/errorMessage",
        "params": {
          "errors": [
            {
              "keyword": "not",
              "dataPath": "/statistics",
              "schemaPath": "#/properties/statistics/allOf/1/then/not",
              "params": {},
              "message": "should NOT be valid"
            },
            {
              "keyword": "if",
              "dataPath": "/statistics",
              "schemaPath": "#/properties/statistics/allOf/1/if",
              "params": {
                "failingKeyword": "then"
              },
              "message": "should match \"then\" schema"
            }
          ]
        },
        "message": "Only Finland applicable at a time for statistics"
      }
    ]

Кроме того, если я добавлю "Германия" в JSON как,

{
  year: "2000",
  country: "USA",
  statistics: [
    {"type":"statisticsUsa"},
    {"type":"statisticsFinland"},
    {"type":"statisticsGermany"}
  ]
}

он выдает 3 ошибки как часть массива (см. ниже). Почему?

  1. Технически валидатор схемы должен остановиться на 2-м элементе и игнорировать «Германия» в массиве ошибок, поскольку он обнаруживает, что запись «Findland» нарушает условия if-else.
  2. Кроме того, проверяет ли схема-валидатор каждый элемент из массива 'statistics' (из JSON) и проверяет условия в 'allOf' для каждого запуска, и поэтому 3 записи об ошибках в массиве. Я правильно понимаю. (Если вы видите первый массив ошибок выше, он содержит только 2 записи)

    [
      {
        "keyword": "errorMessage",
        "dataPath": "/statistics",
        "schemaPath": "#/properties/statistics/allOf/0/errorMessage",
        "params": {
          "errors": [
            {
              "keyword": "not",
              "dataPath": "/statistics",
              "schemaPath": "#/properties/statistics/allOf/0/then/not",
              "params": {},
              "message": "should NOT be valid"
            },
            {
              "keyword": "if",
              "dataPath": "/statistics",
              "schemaPath": "#/properties/statistics/allOf/0/if",
              "params": {
                "failingKeyword": "then"
              },
              "message": "should match \"then\" schema"
            }
          ]
        },
        "message": "Only USA applicable at a time for statistics"
      },
      {
        "keyword": "errorMessage",
        "dataPath": "/statistics",
        "schemaPath": "#/properties/statistics/allOf/1/errorMessage",
        "params": {
          "errors": [
            {
              "keyword": "not",
              "dataPath": "/statistics",
              "schemaPath": "#/properties/statistics/allOf/1/then/not",
              "params": {},
              "message": "should NOT be valid"
            },
            {
              "keyword": "if",
              "dataPath": "/statistics",
              "schemaPath": "#/properties/statistics/allOf/1/if",
              "params": {
                "failingKeyword": "then"
              },
              "message": "should match \"then\" schema"
            }
          ]
        },
        "message": "Only Finland applicable at a time for statistics"
      },
      {
        "keyword": "errorMessage",
        "dataPath": "/statistics",
        "schemaPath": "#/properties/statistics/allOf/2/errorMessage",
        "params": {
          "errors": [
            {
              "keyword": "not",
              "dataPath": "/statistics",
              "schemaPath": "#/properties/statistics/allOf/2/then/not",
              "params": {},
              "message": "should NOT be valid"
            },
            {
              "keyword": "if",
              "dataPath": "/statistics",
              "schemaPath": "#/properties/statistics/allOf/2/if",
              "params": {
                "failingKeyword": "then"
              },
              "message": "should match \"then\" schema"
            }
          ]
        },
        "message": "Only Germany applicable at a time for statistics"
      }
    ]


person user8479984    schedule 16.04.2020    source источник


Ответы (1)


Вы уже сами это поняли: allOf contains применяет все подсхемы и проверяет все элементы массива.

Если вам нужно только одно сообщение об ошибке, вам следует определить приоритет стран и проверять только последующие allOf части для остальных стран, а не все из них каждый раз. Например.

  1. Если массив содержит запись «США», ни «ГЕРМАНИЯ», ни «ФИНЛЯНДИЯ» не должны присутствовать (поскольку они у вас уже есть).
  2. Если массив содержит запись «ГЕРМАНИЯ», запись «ФИНЛЯНДИЯ» не должна присутствовать (не проверяйте «США» снова).
  3. Если массив содержит запись «ФИНЛЯНДИЯ», возможные сценарии уже охвачены двумя вышеуказанными проверками - здесь нет необходимости в какой-либо дополнительной проверке.

Таким образом, вы никогда не получите несколько таких ошибок одновременно.

person Carsten    schedule 17.04.2020
comment
Спасибо за ответ. Но в более широком случае, если у меня есть большие записи в массиве и они проверяются на allOf, есть вероятность, что будет такое количество сообщений об ошибках, которых неизбежно следует избегать. Кроме того, как лучше всего доставить такое сообщение об ошибке обратно вызывающему абоненту более чистым способом (хотя я использую ajv-errors npm)? - person user8479984; 18.04.2020
comment
Не уверен в улучшении того, как отображаются ошибки проверки. Однако вы также можете принять несколько возвращаемых сообщений об ошибках и просто перефразировать их примерно так: «найдено несколько стран, где разрешена только одна. found: USA »и возвращают одну и ту же ошибку для каждого типа (точно так же, как и сейчас). - person Carsten; 18.04.2020
comment
Другой альтернативой было бы использовать oneOf вместо allOf. У вас по-прежнему будет несколько сообщений об ошибках, но в них будет четко указано, что в oneOf есть несколько действительных записей. Каждая запись может быть определена как «массив содержит (X), а не (содержит (что угодно, кроме X))» - с дополнительным случаем «массив пуст», если это разрешено. - person Carsten; 18.04.2020
comment
Спасибо @Carsten. Попробую один из вариантов - person user8479984; 18.04.2020