Reputation: 2136
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?
Upvotes: 10
Views: 2505
Reputation: 80754
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:
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"
.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.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.Upvotes: 14