JSON 模式:“allof”和“additionalProperties”

Posted

技术标签:

【中文标题】JSON 模式:“allof”和“additionalProperties”【英文标题】:JSON schema : "allof" with "additionalProperties" 【发布时间】:2014-05-06 13:28:18 【问题描述】:

假设我们有模式跟随模式(来自教程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 部分的字段?提前致谢。

【问题讨论】:

【参考方案1】:

这是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 架构中所需属性的验证,并且只需在 shipping_address 中添加所需的 type 属性。

不幸的是,additionalProperties 只考虑了直接的兄弟级属性。也许这是有原因的。但这就是为什么我们需要重复继承的属性。

在这里,我们使用空对象语法以简化形式重复继承的属性。这意味着具有这些名称的属性无论包含何种值都是有效的。但是我们可以依靠 allOf 关键字来强制在基 address 模式中声明的类型约束(和任何其他约束)。

【讨论】:

【参考方案2】:

不要在定义级别设置 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

【讨论】:

如果我没看错的话, billing_address 的定义是通过引用属性 street_address、city 和 state 引入的;然后它在本地再次定义它们。那正确吗? shipping_address 似乎做了类似的事情。它似乎是多余的,我使用 $ref 的目标是避免重复定义。请告诉我,这有什么作用? 您必须在properties 中再次指定street_addresscitystate 才能在required 中使用它们,这就是它们在properties 中的原因 "您必须在属性中再次指定 street_address、city 和 state 才能在需要时使用它们,这就是它们在带有 的属性中的原因"。什么不,你不。无论如何都不符合规范。必填,属性是独立的。 这显然违反了DRY,这就是OP首先提出这个问题的原因 我同意这会破坏 DRY。除此之外,它产生的代码可读性较差,而且绝不是直观的。我不确定是否有原因为什么 additionalProperties 在检查允许的属性时只查看同级级别,但恕我直言,这应该更改。【参考方案3】:

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

您偶然发现了 JSON Schema 中最常见的问题,即它根本无法按照用户的期望进行继承;但同时它也是它的核心功能之一。

当你这样做时:

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

schema1schema2 彼此了解;它们在自己的上下文中进行评估。

在很多人遇到的场景中,您希望schema1 中定义的属性将为schema2 所知;但事实并非如此,而且永远不会。

这个问题是我为草案 v5 提出这两个提案的原因:

strictProperties, merge.

shipping_address 的架构将是:


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

address 中定义strictPropertiestrue


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

现在,让我回到草稿 v3。 Draft 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 关键字被明确定义。

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

【讨论】:

这两个属性不是任何规范的一部分——它们是@fge 提议的特性,所以不能保证它们会出现在任何未来的版本中。 @fge - 在您的回答中,关键字不是规范的一部分,而是您提议的扩展,这有点不明显。它们的呈现方式使它们看起来像是一个官方解决方案,而没有任何关于 v5 不断变化的警告,我认为这具有误导性。 目前在 v7,我没有看到任何 mergestrictProperties。现在可以做OP所说的吗? @JulianHonma 2019 年 1 月 strictPropertiesmerge 不属于规范草案 v7。 他们现在的版本是2019-09,但在这方面仍然没有任何改进的迹象。【参考方案4】:

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

【讨论】:

感谢您的回复,但这无济于事:这是错误:[ “level”:“error”,“schema”:“loadingURI”:“#”,“pointer”:“ /properties/shipping_address" , "instance" : "pointer" : "/shipping_address" , "domain" : "validation", "keyword" : "additionalProperties", "message" : "其他属性是不允许的", "unwanted" : [ "city", "state", "street_address" ] ] - 和以前一样。 您是否导入了“city”“state”和“street_address”,例如我的示例中的“addressProp1”? 你能澄清一下吗?我根据您的更改更改了架构。 "city" "state" 和 "street_address" 仍然是引用模式的一部分,你是认真的吗?我检查了验证器json-schema-validator.herokuapp.com 这是目前唯一可行的解​​决方案,尽管相当麻烦(当您必须添加十几个属性以保持"additionalProperties": false 开心时)

以上是关于JSON 模式:“allof”和“additionalProperties”的主要内容,如果未能解决你的问题,请参考以下文章

如何定义至少需要许多属性之一的 JSON 模式

源码分析 | CompletableFuture 组合处理 allOf 和 anyOf 太赞了!

CompletableFuture.allOf().orTimeout() 的意外行为

带有集合或列表的 Java 8 CompletableFuture.allOf(...) [重复]

Flask-restplus:如何使用“allOf”操作定义嵌套模型?

传递给 CompletableFuture.allOf() 的所有期货都会运行吗?