JSON模式:“allof”带有“additionalProperties”

时间:2014-03-27 13:59:59

标签: javascript json jsonschema

假设我们有架构遵循架构(来自教程here):

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

我收到验证错误(已检查here):

[ {
  "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部分的字段?提前谢谢。

4 个答案:

答案 0 :(得分:71)

[v4验证规范草案的作者]

您偶然发现了JSON Schema中最常见的问题,即它根本无法按用户的意愿进行继承;但与此同时,它是其核心特征之一。

当你这样做时:

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

schema1schema2 没有彼此的知识;他们在自己的背景下进行评估。

在许多人遇到的情景中,您希望schema1中定义的属性为schema2;但情况并非如此,永远不会。

这就是为什么我为草案v5制定了这两个提案的原因:

shipping_address的架构将是:

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

以及strictPropertiestrueaddress的定义。


顺便说一下,我也是您所指的网站的作者。

现在,让我回到草案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" } ]
}

总而言之,这意味着草案v3中的extends从未真正做过人们期望它做的事情。对于草案v4,*Of关键字已明确定义。

但到目前为止,你遇到的问题是最常遇到的问题。因此,我的建议将一劳永逸地解决这种误解的来源!

答案 1 :(得分:7)

additionalProperties适用于立即架构中未由propertiespatternProperties计算的所有属性。

这意味着当你有:

    {
      "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将不适用于您想要的属性。

答案 2 :(得分:5)

以下是Yves-M's 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模式中声明的类型约束(以及任何其他约束)。

答案 3 :(得分:1)

不要在定义级别

设置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_addressshipping_address都应指定自己需要的属性。

如果你想将他的属性与其他属性结合起来,你的定义不应该有"additionalProperties": false