如何在代码中定义包含定义的 Json 模式
How do I define a Json Schema containing definitions, in code
我正在尝试通过使用 Newtonsoft.Json.Schema
:
在代码中定义模式来复制以下 Json Schema
示例
{
"$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": { "$ref": "#/definitions/address" }
}
这是我目前为止最接近的结果。 (示例在 F# 中,但也可能在 C# 中。)
代码:
open Newtonsoft.Json.Schema
open Newtonsoft.Json.Linq
let makeSchema =
let addressSchema = JSchema()
addressSchema.Properties.Add("street_address", JSchema(Type = Nullable(JSchemaType.String)))
addressSchema.Properties.Add("city", JSchema(Type = Nullable(JSchemaType.String)))
addressSchema.Properties.Add("state", JSchema(Type = Nullable(JSchemaType.String)))
addressSchema.Required.Add "street_address"
addressSchema.Required.Add "city"
addressSchema.Required.Add "state"
let schema = JSchema()
schema.Properties.Add("billing_address", addressSchema)
schema.Properties.Add("shipping_address", addressSchema)
schema
输出:
{
"properties": {
"billing_address": {
"properties": {
"street_address": {
"type": "string"
},
"city": {
"type": "string"
},
"state": {
"type": "string"
}
},
"required": [
"street_address",
"city",
"state"
]
},
"shipping_address": {
"$ref": "#/properties/billing_address"
}
}
}
如您所见,两个地址中只有一个是使用对另一个模式的引用定义的,地址模式在 "properties" 而不是 "definitions" 中。在 "definitions" 中定义模式并在其他地方引用它有什么诀窍?
黑客节! :-)
根据 source code,JSON.NET 架构不会写 definitions
属性,故事结束。所以都无望了……差不多了。
它确实在另一个地方使用 definitions
属性。即 - when generating schema from a type。在此过程中,它会创建一个 JObject
,将所有模式推送到其中,然后将该对象添加到 definitions
键下的 JSchema.ExtensionData
。当从另一个地方引用模式时,模式编写者将尊重 definitions
对象(如果存在),从而使整个事物协同工作。
所以,有了这些知识,我们就可以破解它了:
let makeSchema =
let addressSchema = JSchema()
...
let definitions = JObject() :> JToken
definitions.["address"] <- addressSchema |> JSchema.op_Implicit
let schema = JSchema()
schema.ExtensionData.["definitions"] <- definitions
schema.Properties.Add("billing_address", addressSchema)
schema.Properties.Add("shipping_address", addressSchema)
schema
瞧!生成的模式现在有一个 definitions
对象,正如神圣的文本告诉我们它应该:
{
"definitions": {
"address": {
"properties": {
"street_address": {
"type": "string"
},
"city": {
"type": "string"
},
"state": {
"type": "string"
}
},
"required": [
"street_address",
"city",
"state"
]
}
},
"properties": {
"billing_address": {
"$ref": "#/definitions/address"
},
"shipping_address": {
"$ref": "#/definitions/address"
}
}
}
几点说明:
- 从 JSON.NET 的角度来看,
definitions
的名字并不特别。如果您将行 schema.ExtensionData.["definitions"]
更改为不同的内容,比如 schema.ExtensionData.["xyz"]
,它仍然有效,所有引用都指向 "#/xyz/address"
.
这整个机制显然是一个 hack 显然不是,according to James Netwon-King。关键的见解似乎是 JsonSchemaWriter
将能够查找之前提到的任何模式并在其他地方使用对它们的引用。这允许人们将模式推到任何喜欢的地方并期望它们被引用。
- 那个
op_Implicit
的电话进来是有必要的。 JSchema
不是 JToken
的子类型,所以你不能像那样把它塞进 definitions.["address"]
,你必须先把它转换成 JToken
。幸运的是,有一个 implicit cast operator defined for that. Unfortunately, it's not straightforward, there seems to be some magic going on. This happens transparently in C#(因为,你知道的,没有足够的混淆),但在 F# 中你必须明确地调用它。
我正在尝试通过使用 Newtonsoft.Json.Schema
:
Json Schema
示例
{
"$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": { "$ref": "#/definitions/address" }
}
这是我目前为止最接近的结果。 (示例在 F# 中,但也可能在 C# 中。)
代码:
open Newtonsoft.Json.Schema
open Newtonsoft.Json.Linq
let makeSchema =
let addressSchema = JSchema()
addressSchema.Properties.Add("street_address", JSchema(Type = Nullable(JSchemaType.String)))
addressSchema.Properties.Add("city", JSchema(Type = Nullable(JSchemaType.String)))
addressSchema.Properties.Add("state", JSchema(Type = Nullable(JSchemaType.String)))
addressSchema.Required.Add "street_address"
addressSchema.Required.Add "city"
addressSchema.Required.Add "state"
let schema = JSchema()
schema.Properties.Add("billing_address", addressSchema)
schema.Properties.Add("shipping_address", addressSchema)
schema
输出:
{
"properties": {
"billing_address": {
"properties": {
"street_address": {
"type": "string"
},
"city": {
"type": "string"
},
"state": {
"type": "string"
}
},
"required": [
"street_address",
"city",
"state"
]
},
"shipping_address": {
"$ref": "#/properties/billing_address"
}
}
}
如您所见,两个地址中只有一个是使用对另一个模式的引用定义的,地址模式在 "properties" 而不是 "definitions" 中。在 "definitions" 中定义模式并在其他地方引用它有什么诀窍?
黑客节! :-)
根据 source code,JSON.NET 架构不会写 definitions
属性,故事结束。所以都无望了……差不多了。
它确实在另一个地方使用 definitions
属性。即 - when generating schema from a type。在此过程中,它会创建一个 JObject
,将所有模式推送到其中,然后将该对象添加到 definitions
键下的 JSchema.ExtensionData
。当从另一个地方引用模式时,模式编写者将尊重 definitions
对象(如果存在),从而使整个事物协同工作。
所以,有了这些知识,我们就可以破解它了:
let makeSchema =
let addressSchema = JSchema()
...
let definitions = JObject() :> JToken
definitions.["address"] <- addressSchema |> JSchema.op_Implicit
let schema = JSchema()
schema.ExtensionData.["definitions"] <- definitions
schema.Properties.Add("billing_address", addressSchema)
schema.Properties.Add("shipping_address", addressSchema)
schema
瞧!生成的模式现在有一个 definitions
对象,正如神圣的文本告诉我们它应该:
{
"definitions": {
"address": {
"properties": {
"street_address": {
"type": "string"
},
"city": {
"type": "string"
},
"state": {
"type": "string"
}
},
"required": [
"street_address",
"city",
"state"
]
}
},
"properties": {
"billing_address": {
"$ref": "#/definitions/address"
},
"shipping_address": {
"$ref": "#/definitions/address"
}
}
}
几点说明:
- 从 JSON.NET 的角度来看,
definitions
的名字并不特别。如果您将行schema.ExtensionData.["definitions"]
更改为不同的内容,比如schema.ExtensionData.["xyz"]
,它仍然有效,所有引用都指向"#/xyz/address"
. 这整个机制显然是一个 hack显然不是,according to James Netwon-King。关键的见解似乎是JsonSchemaWriter
将能够查找之前提到的任何模式并在其他地方使用对它们的引用。这允许人们将模式推到任何喜欢的地方并期望它们被引用。- 那个
op_Implicit
的电话进来是有必要的。JSchema
不是JToken
的子类型,所以你不能像那样把它塞进definitions.["address"]
,你必须先把它转换成JToken
。幸运的是,有一个 implicit cast operator defined for that. Unfortunately, it's not straightforward, there seems to be some magic going on. This happens transparently in C#(因为,你知道的,没有足够的混淆),但在 F# 中你必须明确地调用它。