Create minimal JSON schemas from custom Julia types.
Current restrictions:
- no parametric types
- no Union types, except
Union{Nothing, T}
for optional fields - no abstract types in fields, only concrete types
- must define
StructTypes.StructType
for your custom types if you want touse_references=true
- must define
StructTypes.omitempties
for optional fields
using JSONSchemaGenerator, StructTypes
struct OptionalFieldSchema
int::Int
optional::Union{Nothing, String}
end
StructTypes.StructType(::Type{OptionalFieldSchema}) = StructTypes.Struct()
StructTypes.omitempties(::Type{OptionalFieldSchema}) = (:optional,)
struct NestedFieldSchema
int::Int
field::OptionalFieldSchema
vector::Vector{OptionalFieldSchema}
end
StructTypes.StructType(::Type{NestedFieldSchema}) = StructTypes.Struct()
schema_dict = JSONSchemaGenerator.schema(NestedFieldSchema)
You can easily print the schema with JSON or JSON3
julia> using JSON
julia> JSON.print(schema_dict, 2)
{
"type": "object",
"properties": {
"int": {
"type": "integer"
},
"field": {
"type": "object",
"properties": {
"int": {
"type": "integer"
},
"optional": {
"type": "string"
}
},
"required": [
"int"
]
},
"vector": {
"type": "array",
"items": {
"type": "object",
"properties": {
"int": {
"type": "integer"
},
"optional": {
"type": "string"
}
},
"required": [
"int"
]
}
}
},
"required": [
"int",
"field",
"vector"
]
}
By default the generated schema is recursively nested, meaning that any repeating type will be generated multiple times. The use_references=true
keyword argument can generate the JSON references for you. You can see that the OptionalFieldSchema is now referenced with $ref
towards the $defs
section of the schema instead of being copied.
julia> schema_dict = JSONSchemaGenerator.schema(NestedFieldSchema, use_references=true);
julia> JSON.print(schema_dict, 2)
{
"type": "object",
"properties": {
"int": {
"type": "integer"
},
"field": {
"$ref": "#/$defs/OptionalFieldSchema"
},
"vector": {
"type": "array",
"items": {
"$ref": "#/$defs/OptionalFieldSchema"
}
}
},
"required": [
"int",
"field",
"vector"
],
"$defs": {
"OptionalFieldSchema": {
"type": "object",
"properties": {
"int": {
"type": "integer"
},
"optional": {
"type": "string"
}
},
"required": [
"int"
]
}
}
}
JSONSchema.jl provides validation for JSON schemas. The schema dictionary generated by JSONSchemaGenerator.jl works together with JSONSchema.jl.
JSONSchema.jl works with JSON.jl parsing, but JSON3.jl is better for direct JSON (de)serialization with StructTypes.jl definitions, so unfortunately you may need to use both JSON.jl and JSON3.jl, especially if you have optional fields defined with Union{Nothing, T}
.
Let's use the example above to generate a JSON string and validate it:
using JSONSchema, JSON3, JSON
schema_dict = JSONSchemaGenerator.schema(NestedFieldSchema, use_references=true)
obj = NestedFieldSchema(
1,
OptionalFieldSchema(2, nothing),
[OptionalFieldSchema(2, "string"), OptionalFieldSchema(2, nothing)]
)
# parsing back into a Dict, because that is what JSONSchema.validate wants
json_dict = JSON3.write(obj) |> JSON.parse
JSONSchema.validate(JSONSchema.Schema(schema_dict), json_dict) === nothing