如何在JSON schema中扩展模式?

52

我正在使用JSON模式进行数据建模。 我定义了一个基本的文档(Document)模式,以后我将用它来定义模型模式(例如产品(Product)类别(Category)用户(User)等)。

我这样做是因为我希望所有模型都继承特定的结构/规则。 例如,每个模型实例都应具有某些通用属性(例如idcreatedAtupdatedAt)。 在面向对象编程(OOP)术语中:Product extends Document,因此它继承了其实例属性。 在模式术语(我想)中,Document是用于创建模型模式的元模式。

我已经按以下方式定义了Document模式:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "id": "http://example.com/schemas/document.json#",
  "title": "Document",
  "type": "object",
  "additionalProperties": false,
  "required": ["type", "name", "fields"],
  "properties": {
    "type": {
      "constant": "document"
    },
    "name": {
      "type": "string"
    },
    "title": {
      "type": "string"
    },
    "description": {
      "type": "string"
    },
    "readOnly": {
      "type": "boolean"
    },
    "properties": {
      // common properties 
      // model-specific properties
    }
  }
}
  1. 如何指定文档元模式 "extends" 基础 JSON 模式 (draft-07),以便不必定义草案的所有属性 ($schema, id 等)?
  2. 如何指定每个模型模式的 properties 包含一些通用属性 (id, createdAt, ...),而无需在每个模型模式定义中定义它们?
2个回答

102
JSON Schema不使用面向对象的范例,所以继承等概念并不能很好地转换。JSON Schema是一组约束条件,它是减法而不是加法,与大多数人习惯的相反。这意味着,给定一个空模式,有效 JSON 文档的集合就是所有 JSON 文档的集合。随着添加关键字,您正在从有效的JSON文档集合中删减。一旦某些内容从集合中删除,就无法再添加回去。

因此,您可以使用组合来“扩展”模式,但永远不能“覆盖”另一个模式定义的内容。

让我们看一个没有冲突属性的简单扩展示例。

/schema/base

{
  "type": "object",
  "properties": {
    "foo": { "type": "string" },
    "bar": { "type": "string" }
  }
}

/模式/扩展

{
  "allOf": [{ "$ref": "/schema/base" }],
  "properties": {
    "baz": { "type": "string" }
  }
}

这对JSON Schema非常有效。现在让我们看一个具有冲突属性定义的示例。

/schema/override

{
  "allOf": [{ "$ref": "/schema/base" }],
  "properties": {
    "bar": { "type": "integer" },
    "baz": { "type": "boolean" }
  }
}

在这个示例中,两个模式都有一个/properties/bar字段。如果您以继承的方式考虑这个问题,您将误解这里发生的情况。在这种情况下,两个 "/properties/bar"字段必须有效。没有冲突需要解决。正如关键字所说,“所有”模式都必须有效。由于bar不可能既是整数又是字符串,因此没有文档会验证通过/schema/override

希望这提供了足够的信息来解决您的问题并避免最常见的陷阱。


3
当你写道"In this case, both "/properties/bar" fields must be valid"时,你的意思是bar字段必须同时符合基本架构中的字符串类型和覆盖架构中的整数类型吗?由于这是不可能的,因此任何JSON都无法通过覆盖架构的验证,对吗? - md2perpe
8
正确的。一个不会比另一个优先。它们同时适用。由于没有可能同时将 JSON 文档作为字符串和整数,因此任何东西都不会符合该模式。 - Jason Desrosiers
2
需要注意的是,官方指南明确指出这个建议实际上并没有扩展任何内容。-- https://web.archive.org/web/20201020173257/https://json-schema.org/understanding-json-schema/reference/combining.html#id5 - James Sumners
2
@Patrick 如果您使用的是2019-09或更高版本的草案,则可以忽略 allOf。在早期的 JSON Schema 草案中,如果对象中有 $ref,则它被认为只是一个引用,而不是模式。这意味着该引用内部的任何 JSON Schema 关键字都会被忽略,因为关键字仅在模式内有意义。这实际上意味着 $ref 必须总是单独存在,并且我们需要使用 allOf 来组合引用和模式。在新版本的 $ref 中,该引用是 $ref 关键字的字符串值,且 $ref 的作用类似于具有一个模式的 allOf - Jason Desrosiers
3
additionalProperties替换为unevaluatedProperties,它将按您的期望工作。 - Jason Desrosiers
显示剩余9条评论

3

使用Node.js时,对于简单模式,可以通过几行代码使用ajv验证器轻松解决此限制:

function extendJsonSchema(baseSchema, extendingSchema) {
    let extendedSchema = Object.assign({}, extendingSchema);
    extendedSchema.properties = Object.assign(extendedSchema.properties, baseSchema.properties)
    extendedSchema.required = extendedSchema.required.concat(baseSchema.required)
    return extendedSchema
}


let baseSchema = require('./base.schema.json')
let extendingSchema = require('./extending.schema.json')

let extendedSchema = extendJsonSchema(baseSchema, extendingSchema)
const validate = ajv.compile(extendedSchema)

这至少解决了我的使用情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接