【问题标题】:How to set up local file references in python-jsonschema document?如何在 python-jsonschema 文档中设置本地文件引用?
【发布时间】:2019-05-26 21:12:15
【问题描述】:

我有一组符合jsonschema 的文档。一些文档包含对其他文档的引用(通过$ref 属性)。我不希望托管这些文档,以便可以通过 HTTP URI 访问它们。因此,所有引用都是相对的。所有文档都位于本地文件夹结构中。

如何让python-jsonschema 理解正确使用我的本地文件系统来加载引用的文档?


例如,如果我有一个文件名为 defs.json 的文档,其中包含一些定义。我尝试加载引用它的不同文档,例如:

{
  "allOf": [
    {"$ref":"defs.json#/definitions/basic_event"},
    {
      "type": "object",
      "properties": {
        "action": {
          "type": "string",
          "enum": ["page_load"]
        }
      },
      "required": ["action"]
    }
  ]
}

我收到一个错误RefResolutionError: <urlopen error [Errno 2] No such file or directory: '/defs.json'>

我在一个 linux 机器上可能很重要。


(我之所以写这篇问答是因为我很难弄清楚这一点,observed other folkshaving trouble too。)

【问题讨论】:

    标签: python json jsonschema python-jsonschema


    【解决方案1】:

    我最难弄清楚如何解决一组$ref 彼此的模式(我是 JSON 模式的新手)。事实证明,关键是使用store 创建RefResolver,这是一个从url 映射到模式的dict。 基于@devin-p 的回答:

    import json
    
    from jsonschema import RefResolver, Draft7Validator
    
    base = """
    {
      "$id": "base.schema.json",
      "type": "object",
      "properties": {
        "prop": {
          "type": "string"
        }
      },
      "required": ["prop"]
    }
    """
    
    extend = """
    {  
      "$id": "extend.schema.json",
      "allOf": [
        {"$ref": "base.schema.json#"},
        {
          "properties": {
            "extra": {
              "type": "boolean"
            }
          },
        "required": ["extra"]
        }
      ]
    }
    """
    
    extend_extend = """
    {
      "$id": "extend_extend.schema.json",
      "allOf": [
        {"$ref": "extend.schema.json#"},
        {
          "properties": {
            "extra2": {
              "type": "boolean"
            }
          },
        "required": ["extra2"]
        }
      ]
    }
    """
    
    data = """
    {
    "prop": "This is the property string",
    "extra": true,
    "extra2": false
    }
    """
    
    schema = json.loads(base)
    extendedSchema = json.loads(extend)
    extendedExtendSchema = json.loads(extend_extend)
    schema_store = {
        schema['$id'] : schema,
        extendedSchema['$id'] : extendedSchema,
        extendedExtendSchema['$id'] : extendedExtendSchema,
    }
    
    
    resolver = RefResolver.from_schema(schema, store=schema_store)
    validator = Draft7Validator(extendedExtendSchema, resolver=resolver)
    
    jsonData = json.loads(data)
    validator.validate(jsonData)
    

    上面是用jsonschema==3.2.0构建的。

    【讨论】:

    • 我像这样初始化 RefResolver:jsonschema.RefResolver(None, referrer=None, store=schema_store)。然后商店有带有“$id”字段的条目,例如:"https://example.com/path/subpath/filename.json"。 (这不需要任何网络调用——除非你指定一个不在存储中的模式——因为存储包含我们需要的任何引用的缓存)。
    • 为什么在$ref 末尾使用# 符号? {"$ref": "base.schema.json#"}, 而不是把它作为前缀?
    • 在上面的示例中,# 符号(用于描述 URI fragment)是多余的。在 $ref URI 中,片段引用模式中的路径,因此,在上面的示例中,{"$ref": "base.schema.json#/properties/prop/type"} 将解析为 "string"
    【解决方案2】:

    您必须为每个使用相对引用的架构构建自定义 jsonschema.RefResolver,并确保您的解析器知道给定架构在文件系统上的位置。

    比如……

    import os
    import json
    from jsonschema import Draft4Validator, RefResolver # We prefer Draft7, but jsonschema 3.0 is still in alpha as of this writing 
    
    
    abs_path_to_schema = '/path/to/schema-doc-foobar.json'
    with open(abs_path_to_schema, 'r') as fp:
      schema = json.load(fp)
    
    resolver = RefResolver(
      # The key part is here where we build a custom RefResolver 
      # and tell it where *this* schema lives in the filesystem
      # Note that `file:` is for unix systems
      schema_path='file:{}'.format(abs_path_to_schema),
      schema=schema
    )
    Draft4Validator.check_schema(schema) # Unnecessary but a good idea
    validator = Draft4Validator(schema, resolver=resolver, format_checker=None)
    
    # Then you can...
    data_to_validate = `{...}`
    validator.validate(data_to_validate)
    

    【讨论】:

    • 这是因为 JSON 模式规范说它是一个 URI。并且 URI 不能是相对路径?所以如果我们最终得到一个相对路径,我们就没有编写一个正确的符合规范的 json 模式?
    • 我的测试表明definitions 不是必需的。无需#... 部分即可编写完整的 JSON 模式文档。我想知道definitions 是可选的还是约定的。
    • jsonschema 3.0.1 默认草稿 7 现已发布(根据您在示例中的评论说您更喜欢草稿 7)
    【解决方案3】:

    EDIT-1

    修复了对 base 架构的错误引用 ($ref)。 更新了示例以使用 the 文档中的示例:https://json-schema.org/understanding-json-schema/structuring.html

    EDIT-2

    正如 cmets 中所指出的,在下面我使用了以下导入:

    from jsonschema import validate, RefResolver 
    from jsonschema.validators import validator_for
    

    这只是@Daniel 答案的另一个版本——这对我来说是正确的。基本上,我决定在基本模式中定义$schema。然后释放其他模式并在实例化 resolver 时进行明确的调用。

    • RefResolver.from_schema() 获得 (1) some 架构和 (2) 架构存储这一事实对我来说不是很清楚,顺序和哪个“some" 架构在这里是相关的。所以你在下面看到的结构。

    我有以下几点:

    base.schema.json:

    {
      "$schema": "http://json-schema.org/draft-07/schema#"
    }
    

    definitions.schema.json:

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

    address.schema.json:

    {
      "type": "object",
    
      "properties": {
        "billing_address": { "$ref": "definitions.schema.json#" },
        "shipping_address": { "$ref": "definitions.schema.json#" }
      }
    }
    

    我喜欢这个设置有两个原因:

    1. RefResolver.from_schema() 是一个更清洁的电话:

      base = json.loads(open('base.schema.json').read())
      definitions = json.loads(open('definitions.schema.json').read())
      schema = json.loads(open('address.schema.json').read())
      
      schema_store = {
        base.get('$id','base.schema.json') : base,
        definitions.get('$id','definitions.schema.json') : definitions,
        schema.get('$id','address.schema.json') : schema,
      }
      
      resolver = RefResolver.from_schema(base, store=schema_store)
      
    2. 然后我从图书馆提供的方便工具中获益,为您提供最佳 validator_for 您的架构(根据您的$schema 密钥):

      Validator = validator_for(base)
      
    3. 然后把它们放在一起实例化validator:

      validator = Validator(schema, resolver=resolver)
      

    最后,你validate你的数据:

    data = {
      "shipping_address": {
        "street_address": "1600 Pennsylvania Avenue NW",
        "city": "Washington",
        "state": "DC"   
      },
      "billing_address": {
        "street_address": "1st Street SE",
        "city": "Washington",
        "state": 32
      }
    }
    
    • 这个 会崩溃,因为"state": 32
    >>> validator.validate(data)
    
    ValidationError: 32 is not of type 'string'
    
    Failed validating 'type' in schema['properties']['billing_address']['properties']['state']:
        {'type': 'string'}
    
    On instance['billing_address']['state']:
        32
    

    更改"DC"将验证

    【讨论】:

    • 这个答案对我来说非常有效。只是想指出其他人的导入依赖关系也尝试这个from jsonschema import validate, RefResolverfrom jsonschema.validators import validator_for
    • 谢谢@khuang834。我对此进行了调整/添加了注释。
    • 我们如何使用这种方法验证Nested properties。假设我在 address.schema.json 中有一个条件属性 "Zipcode"。并希望根据definitions.schema.json"city" 的值进行验证。
    • @curiousguy 也许我不明白你的问题或者它是不恰当的。尽管如此,以下是我的想法,希望能帮助您解决问题:zipcodecity 齐头并进,它们都是地址的一部分。 AFAIU,您想验证给定的zipcodecity 的一部分。恕我直言,这是模式验证的外部:大致而言,模式验证是关于数据类型/格式的。话虽如此,您可以包含一组城市的“条件属性”,并可能为另一个“cities-definitions.schema.json”中的每个城市关联邮政编码 (enum)。跨度>
    • 它对我有用,但为什么你在 ref value{ "$ref": "definitions.schema.json#" } 的末尾添加了 #?而不是{ "$ref": "#/definitions.schema.json" }
    【解决方案4】:

    跟进@chris-w 提供的答案,我想对jsonschema 3.2.0 做同样的事情,但他的回答并没有完全涵盖它我希望这个答案能帮助那些仍然来这个问题寻求帮助的人,但是正在使用更新版本的软件包。

    要使用该库扩展 JSON 架构,请执行以下操作:

    1. 创建基础架构:
    base.schema.json
    {
      "$id": "base.schema.json",
      "type": "object",
      "properties": {
        "prop": {
          "type": "string"
        }
      },
      "required": ["prop"]
    }
    
    1. 创建扩展架构
    extend.schema.json
    {
      "allOf": [
        {"$ref": "base.schema.json"},
        {
          "properties": {
            "extra": {
              "type": "boolean"
            }
          },
          "required": ["extra"]
        }
      ]
    }
    
    1. 创建要针对架构进行测试的 JSON 文件
    data.json
    {
      "prop": "This is the property",
      "extra": true
    }
    
    1. 为基础架构创建 RefResolver 和 Validator 并使用它来检查数据
    #Set up schema, resolver, and validator on the base schema
    baseSchema = json.loads(baseSchemaJSON) # Create a schema dictionary from the base JSON file
    relativeSchema = json.loads(relativeJSON) # Create a schema dictionary from the relative JSON file
    resolver = RefResolver.from_schema(baseSchema) # Creates your resolver, uses the "$id" element
    validator = Draft7Validator(relativeSchema, resolver=resolver) # Create a validator against the extended schema (but resolving to the base schema!)
    
    # Check validation!
    data = json.loads(dataJSON) # Create a dictionary from the data JSON file
    validator.validate(data)
    

    您可能需要对上述条目进行一些调整,例如不使用 Draft7Validator。这应该适用于单级引用(扩展基的子级),您需要小心您的架构以及如何设置 RefResolverValidator 对象。

    附:这是一个练习上述内容的片段。尝试修改 data 字符串以删除所需属性之一:

    import json
    
    from jsonschema import RefResolver, Draft7Validator
    
    base = """
    {
      "$id": "base.schema.json",
      "type": "object",
      "properties": {
        "prop": {
          "type": "string"
        }
      },
      "required": ["prop"]
    }
    """
    
    extend = """
    {
      "allOf": [
        {"$ref": "base.schema.json"},
        {
          "properties": {
            "extra": {
              "type": "boolean"
            }
          },
          "required": ["extra"]
        }
      ]
    }
    """
    
    data = """
    {
    "prop": "This is the property string",
    "extra": true
    }
    """
    
    schema = json.loads(base)
    extendedSchema = json.loads(extend)
    resolver = RefResolver.from_schema(schema)
    validator = Draft7Validator(extendedSchema, resolver=resolver)
    
    jsonData = json.loads(data)
    validator.validate(jsonData)
    

    【讨论】:

      【解决方案5】:

      我的方法是将所有架构片段预加载到 RefResolver 缓存。我创建了一个要点来说明这一点:https://gist.github.com/mrtj/d59812a981da17fbaa67b7de98ac3d4b

      【讨论】:

        【解决方案6】:

        这是我用来从给定目录中的所有模式动态生成schema_store 的方法

        base.schema.json

        {
          "$id": "base.schema.json",
          "type": "object",
          "properties": {
            "prop": {
              "type": "string"
            }
          },
          "required": ["prop"]
        }
        

        extend.schema.json

        {  
          "$id": "extend.schema.json",
          "allOf": [
            {"$ref": "base.schema.json"},
            {
              "properties": {
                "extra": {
                  "type": "boolean"
                }
              },
            "required": ["extra"]
            }
          ]
        }
        

        instance.json

        {
          "prop": "This is the property string",
          "extra": true
        }
        

        validator.py

        import json
        
        from pathlib import Path
        
        from jsonschema import Draft7Validator, RefResolver
        from jsonschema.exceptions import RefResolutionError
        
        schemas = (json.load(open(source)) for source in Path("schema/dir").iterdir())
        schema_store = {schema["$id"]: schema for schema in schemas}
        
        schema = json.load(open("schema/dir/name.schema.json"))
        instance = json.load(open("instance/dir/instance.json"))
        resolver = RefResolver.from_schema(schema, store=schema_store)
        validator = Draft7Validator(schema, resolver=resolver)
        
        try:
            errors = sorted(validator.iter_errors(instance), key=lambda e: e.path)
        except RefResolutionError as e:
            print(e)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-01-15
          • 1970-01-01
          • 2021-11-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多