Схема 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? Заранее спасибо.


person lm.    schedule 27.03.2014    source источник


Ответы (4)


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

Вы столкнулись с самой распространенной проблемой в схеме 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; По сути, allOf из черновика v4 - это extends из черновика v3.

Рассмотрим это (черновик 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 ключевых слов.

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

person fge    schedule 10.04.2014
comment
Спасибо за подробное объяснение. Насколько я понимаю, strictProperties и слияние являются частью спецификации v5. Существуют ли стабильные валидаторы, поддерживающие v5? Спасибо - person lm.; 11.04.2014
comment
Эти два свойства не являются частью какой-либо спецификации - это функции, которые @fge предлагает, поэтому нет никакой гарантии, что они будут в какой-либо будущей версии. - person cloudfeet; 11.04.2014
comment
@fge - в вашем ответе не очевидно, что ключевые слова не являются частью спецификации, а являются вашим предложенным расширением. То, как они представлены, заставляет их казаться официальным решением без каких-либо предупреждений о том, что v5 находится в постоянном движении, что, на мой взгляд, вводит в заблуждение. - person cloudfeet; 11.04.2014
comment
Нет необходимости улучшать черновик V3, чтобы решить эту проблему с помощью элегантного решения: stackoverflow.com/a/24365393/1480391 - person Yves M.; 23.06.2014
comment
@fge мне все еще не ясно, как можно объединить более 2 схем (т.е. 3 полных схемы) в одну с помощью $ merge. По некоторым причинам я подумал, что это своего рода метод хранения / встраивания для нескольких схем с единственным правилом - не иметь конфликтующих свойств. - person themihai; 04.06.2015
comment
К сожалению, похоже, что спецификация v5 - это только спецификация обслуживания. github.com/json-schema/json-schema/wiki/v5- Предложения Есть ли что-нибудь, что могло бы решить эту проблему в v6? - person Peter; 08.11.2017
comment
В настоящее время на версии 7, и я не вижу ни merge, ни strictProperties. Возможно ли теперь делать то, о чем говорит ОП? - person Julian Honma; 05.01.2018
comment
@JulianHonma Январь, 2019 strictProperties и merge не являются частью проекта спецификации v7. - person Sebastian Barth; 26.01.2020
comment
Сейчас они находятся в версии 2019-09, но все еще нет знак улучшений в этой области. - person Renato; 05.01.2021

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 не будет применяться к нужным вам свойствам.

person cloudfeet    schedule 27.03.2014
comment
Спасибо за ответ, но это не помогло: вот ошибка: [{level: error, schema: {loadingURI: #, pointer: / properties / shipping_address}, instance: {pointer: / shipping_address}, domain: validation, keyword: additionalProperties, message: дополнительные свойства не разрешены, нежелательные: [city, state, street_address]}] - то же, что и раньше. - person lm.; 28.03.2014
comment
Вы импортировали город, штат и street_address, например addressProp1 в моем примере? - person cloudfeet; 28.03.2014
comment
Вы можете уточнить? Я изменил схему согласно вашим изменениям. город-государство и street_address остаются частью указанной схемы, вы это имели в виду? Я проверил с помощью валидатора json-schema-validator.herokuapp.com - person lm.; 28.03.2014
comment
В настоящее время это единственное рабочее решение, хотя и довольно громоздкое (когда вам нужно добавить дюжину свойств, чтобы "additionalProperties": false остался доволен) - person Julian Honma; 05.01.2018

Вот немного упрощенная версия решения Yves-M:

{
  "$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.

person Ted Epstein    schedule 02.08.2016

Не устанавливайте additionalProperties = 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, если вы хотите объединить его свойства с другими.

person Yves M.    schedule 23.06.2014
comment
Если я правильно это понимаю, определение billing_address извлекает посредством ссылки свойства street_address, city и state; затем он снова определяет их локально. Это правильно? Похоже, что shipping_address делает что-то подобное. Это кажется лишним, и моя цель использования $ ref - избежать повторения определений. Пожалуйста, скажите мне, чего это достичь? - person chrisinmtown; 14.07.2015
comment
Вы должны снова указать street_address, city и state в properties, чтобы использовать их в required, поэтому они находятся в properties с {} - person Yves M.; 14.07.2015
comment
Вы должны снова указать street_address, city и state в свойствах, чтобы использовать их в обязательном порядке, поэтому они находятся в свойствах с {}. Что нет, вы не делаете. в любом случае не в соответствии со спецификацией. требуется, и свойства независимы. - person thespinkus; 12.02.2016
comment
Это явно нарушает DRY, поэтому OP задал этот вопрос в первую очередь - person LionC; 02.11.2016
comment
Я согласен, что это вуалирует DRY. Кроме того, он создает менее читаемый код и никоим образом не интуитивно понятен. Я не уверен, есть ли причина, по которой additionalProperties смотрит только на уровень брата при проверке разрешенных свойств, но IMHO это должно быть изменено. - person omni; 20.02.2018