如何在代码中定义包含定义的Json模式

时间:2016-11-08 09:16:39

标签: json f# jsonschema

我试图通过使用Json Schema在代码中定义架构来复制以下Newtonsoft.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"
    }
  }
}

正如您所看到的,两个地址中只有一个是使用对另一个模式的引用定义的,而地址模式位于"属性"而不是"定义"。在"定义"中定义模式的诀窍是什么?并在其他地方引用它?

1 个答案:

答案 0 :(得分:9)

Hackfest! : - )

根据source code,JSON.NET Schema只是没有写一个definitions属性,故事结束。所以这一切都无望......差不多。

然而,在其他地方使用 definitions属性。即 - when generating schema from a type。在此过程中,它会创建一个JObject,将所有架构推入其中,然后将该对象添加到JSchema.ExtensionData键下的definitions。当从其他地方引用模式时,模式编写器将尊重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"
    }
  }
}

一些注释:

  1. 从JSON.NET的角度来看,definitions名称并不特别。如果您将行schema.ExtensionData.["definitions"]更改为其他内容,请说schema.ExtensionData.["xyz"],它仍然有效,所有引用都指向"#/xyz/address"
  2. 显然,整个机制是黑客攻击显然不是,according to James Netwon-King。关键的洞察力似乎是JsonSchemaWriter将能够查找以前提到的任何模式并在其他地方使用对它们的引用。这允许人们在任何人喜欢的地方推送模式并期望它们被引用。
  3. 那里的op_Implicit电话是必要的。 JSchema不是JToken的子类型,因此您无法将其加入definitions.["address"],您必须先将其转换为JToken。幸运的是,为此定义了implicit cast operator。不幸的是,它并不简单,似乎有一些魔力在继续。这个happens transparently in C#(因为,你知道,它没有足够的混淆),但在F#你必须明确地调用它。