Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I define a Json Schema containing definitions, in code

I am attempting to replicate the following Json Schema example, by defining the schema in code using 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" }
  }

This is as close as I've got so far. (Example is in F# but might just as well be in C#.)

Code:

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

Output:

{
  "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"
    }
  }
}

As you can see, only one of the two addresses is defined using a reference to another schema, and the address schema is in "properties" rather than "definitions". What's the trick to defining a schema in "definitions" and referencing it elsewhere?

like image 832
Kit Avatar asked Nov 08 '16 09:11

Kit


People also ask

How do I specify a schema in a JSON file?

At the top of the file, you can specify the schema's id, the schema that should be used to validate the format of your schema, and a descriptive title. These are all defined using the keywords id, $schema and title, all of which are provided in the draft JSON Schema.

What are definitions in JSON Schema?

JSON Schema is a JSON media type for defining the structure of JSON data. JSON Schema provides a contract for what JSON data is required for a given application and how to interact with it. JSON Schema is intended to define validation, documentation, hyperlink navigation, and interaction control of JSON data.

Can JSON Schema have comments?

Comments. The $comment keyword is strictly intended for adding comments to a schema. Its value must always be a string. Unlike the annotations title , description , and examples , JSON schema implementations aren't allowed to attach any meaning or behavior to it whatsoever, and may even strip them at any time.

How do you define an object in a schema?

To define a Realm object type, create a schema object that specifies the type's name and properties . The type name must be unique among object types in a realm. For details on how to define specific properties, see Define Object Properties.


1 Answers

Hackfest! :-)

According to the source code, JSON.NET Schema just doesn't write a definitions property, end of story. So it's all hopeless... Almost.

It does use the definitions property in another place, however. Namely - when generating schema from a type. During that process, it creates a JObject, pushes all schemas into it, and then adds that object to JSchema.ExtensionData under the definitions key. And when referencing a schema from another place, the schema writer will respect that definitions object, if present, thus making the whole thing work together.

So, armed with this knowledge, we can hack our way into it:

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

And voila! The resulting schema now has a definitions object, just as the sacred texts tell us it should:

{
  "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"
    }
  }
}

A few notes:

  1. The definitions name is not special from the JSON.NET's point of view. If you change the line schema.ExtensionData.["definitions"] to something different, say schema.ExtensionData.["xyz"], it will still work, with references all pointing to "#/xyz/address".
  2. This whole mechanism, obviously, is a hack Apparently not, according to James Netwon-King. The key insight seems to be that the JsonSchemaWriter will be able to lookup any previous mentions of schemas and use references to them in other places. This allows one to shove schemas wherever one likes and expect them to be referenced.
  3. That op_Implicit call in there is necessary. JSchema is not a subtype of JToken, so you can't just jam it into definitions.["address"] like that, you have to convert it to JToken first. Fortunately, there is an implicit cast operator defined for that. Unfortunately, it's not straightforward, there seems to be some magic going on. This happens transparently in C# (because, you know, there is not enough confusion as it is), but in F# you have to call it explicitly.
like image 144
Fyodor Soikin Avatar answered Oct 30 '22 08:10

Fyodor Soikin