Подтвердить что ты не робот

Схема JSON: "allof" с "дополнительными свойствами"

Предположим, что у нас есть схема, следующая за схемой (из учебника здесь):

{
  "$schema": "http://json-schema.org/draft-04/schema#",

  "definitions": {
    "address": {
      "type": "object",
      "properties": {
        "street_address": { "type": "string" },
        "city":           { "type": "string" },
        "state":          { "type": "string" }
      },
      "required": ["street_address", "city", "state"]
    }
  },

  "type": "object",

  "properties": {
    "billing_address": { "$ref": "#/definitions/address" },
    "shipping_address": {
      "allOf": [
        { "$ref": "#/definitions/address" },
        { "properties":
          { "type": { "enum": [ "residential", "business" ] } },
          "required": ["type"]
        }
      ]
    } 

  }
}

И вот действительный экземпляр:

{
      "shipping_address": {
        "street_address": "1600 Pennsylvania Avenue NW",
        "city": "Washington",
        "state": "DC",
        "type": "business"
      }
}

Мне нужно убедиться, что любые дополнительные поля для shipping_address будут недействительными. Я знаю, для этой цели существует additionalProperties, который должен быть установлен на "false". Но когда я устанавливаю "additionalProprties":false, как в следующем:

"shipping_address": {
          "allOf": [
            { "$ref": "#/definitions/address" },
            { "properties":
              { "type": { "enum": [ "residential", "business" ] } },
              "required": ["type"]
            }
          ],
          "additionalProperties":false
        } 

Я получаю ошибку проверки (отмечен здесь):

[ {
  "level" : "error",
  "schema" : {
    "loadingURI" : "#",
    "pointer" : "/properties/shipping_address"
  },
  "instance" : {
    "pointer" : "/shipping_address"
  },
  "domain" : "validation",
  "keyword" : "additionalProperties",
  "message" : "additional properties are not allowed",
  "unwanted" : [ "city", "state", "street_address", "type" ]
} ] 

Вопрос: как мне ограничивать поля только для части shipping_address? Спасибо заранее.

4b9b3361

Ответ 1

[автор проекта спецификации проверки v4 здесь]

Вы столкнулись с наиболее распространенной проблемой в схеме JSON, т.е. с ее фундаментальной неспособностью выполнять наследование по мере того, как ожидают пользователи; но в то же время это одна из основных функций.

Когда вы выполните:

"allOf": [ { "schema1": "here" }, { "schema2": "here" } ]

schema1 и schema2 не знают друг друга; они оцениваются в их собственном контексте.

В вашем сценарии, с которым сталкивается много людей, вы ожидаете, что свойства, определенные в schema1, будут известны schema2; но это не так и никогда не будет.

Эта проблема заключается в том, почему я сделал эти два предложения для проекта v5:

Ваша схема для shipping_address будет следующей:

{
    "merge": {
        "source": { "$ref": "#/definitions/address" },
        "with": {
            "properties": {
                "type": { "enum": [ "residential", "business" ] }
            }
        }
    }
}

вместе с определением strictProperties - true в address.


Кстати, я также являюсь автором сайта, на который вы ссылаетесь.

Теперь позвольте мне вернуться к проекту v3. Проект v3 определил extends, и его значение было либо схемой, либо массивом схем. По определению этого ключевого слова это означало, что экземпляр должен быть действительным в отношении текущей схемы и всех схем, указанных в extends; в основном, проект v4 allOf представляет собой проект v3 extends.

Рассмотрим это (проект v3):

{
    "extends": { "type": "null" },
    "type": "string"
}

А теперь, что:

{
    "allOf": [ { "type": "string" }, { "type": "null" } ]
}

Они одинаковы. Или возможно?

{
    "anyOf": [ { "type": "string" }, { "type": "null" } ]
}

Или что?

{
    "oneOf": [ { "type": "string" }, { "type": "null" } ]
}

В общем, это означает, что extends в проекте v3 никогда не делал то, что люди ожидали от него. С черновиками v4 четко определены ключевые слова *Of.

Но проблема, с которой вы столкнулись, является наиболее часто встречающейся проблемой, безусловно. Отсюда мои предложения, которые раз и навсегда угасят этот источник непонимания!

Ответ 2

additionalProperties применяется ко всем свойствам, которые не учитываются в properties или patternProperties в непосредственной схеме.

Это означает, что когда у вас есть:

    {
      "allOf": [
        { "$ref": "#/definitions/address" },
        { "properties":
          { "type": { "enum": [ "residential", "business" ] } },
          "required": ["type"]
        }
      ],
      "additionalProperties":false
    }

additionalProperties здесь применяется ко всем свойствам, потому что нет записи properties на уровне брата - внутри внутри allOf не учитывается.

Одна вещь, которую вы могли бы сделать, - переместить определение properties на один уровень вверх и предоставить записи заглушки для импортируемых вами свойств:

    {
      "allOf": [{"$ref": "#/definitions/address"}],
      "properties": {
        "type": {"enum": ["residential", "business"]},
        "addressProp1": {},
        "addressProp2": {},
        ...
      },
      "required": ["type"],
      "additionalProperties":false
    }

Это означает, что additionalProperties не будет применяться к свойствам, которые вы хотите.

Ответ 3

Здесь немного упрощенная версия Yves-M Solution:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "definitions": {
    "address": {
      "type": "object",
      "properties": {
        "street_address": {
          "type": "string"
        },
        "city": {
          "type": "string"
        },
        "state": {
          "type": "string"
        }
      },
      "required": [
        "street_address",
        "city",
        "state"
      ]
    }
  },
  "type": "object",
  "properties": {
    "billing_address": {
      "$ref": "#/definitions/address"
    },
    "shipping_address": {
      "allOf": [
        {
          "$ref": "#/definitions/address"
        }
      ],
      "properties": {
        "type": {
          "enum": [
            "residential",
            "business"
          ]
        },
        "street_address": {},
        "city": {},
        "state": {}
      },
      "required": [
        "type"
      ],
      "additionalProperties": false
    }
  }
}

Это сохраняет проверку необходимых свойств в базовой схеме address и просто добавляет требуемое свойство type в shipping_address.

Несчастливо, что additionalProperties учитывает только непосредственные свойства уровня брата. Может быть, есть причина для этого. Но именно поэтому нам нужно повторить унаследованные свойства.

Здесь мы повторяем унаследованные свойства в упрощенной форме, используя синтаксис пустого объекта. Это означает, что свойства с этими именами будут действительны независимо от того, какую ценность они содержат. Но мы можем полагаться на ключевое слово allOf для обеспечения ограничений типа (и любых других ограничений), объявленных в базовой схеме address.

Ответ 4

Не устанавливайте addProperties = false на уровне определения

И все будет хорошо:

{    
    "definitions": {
        "address": {
            "type": "object",
            "properties": {
                "street_address": { "type": "string" },
                "city":           { "type": "string" },
                "state":          { "type": "string" }
            }
        }
    },

    "type": "object",
    "properties": {

        "billing_address": {
            "allOf": [
                { "$ref": "#/definitions/address" }
            ],
            "properties": {
                "street_address": {},
                "city": {},
                "state": {}                 
            },          
            "additionalProperties": false
            "required": ["street_address", "city", "state"] 
        },

        "shipping_address": {
            "allOf": [
                { "$ref": "#/definitions/address" },
                {
                    "properties": {
                        "type": {
                            "enum": ["residential","business"]
                        }
                    }
                }
            ],
            "properties": {
                "street_address": {},
                "city": {},
                "state": {},
                "type": {}                          
            },              
            "additionalProperties": false
            "required": ["street_address","city","state","type"] 
        }

    }
}

Каждый из ваших billing_address и shipping_address должен указывать свои собственные требуемые свойства.

У вашего определения не должно быть "additionalProperties": false, если вы хотите совместить его свойства с другими.