We will use ajvMetadata to demonstrate how to get Modélico to generate complex JSON schemas. This is also an example of a schema with circular references.
The Shape in the inner types can be either a Diamond or a Circle. Let's look at their definitions:
const ShapeType = M.Enum.fromArray(['CIRCLE', 'DIAMOND'])
// We are going to use this metadata in several places, so by reusing it, we
// not only save unnecessary processing, but our generated JSON schema will
// also be more compact.
const greaterThanZero = number({
exclusiveMinimum: 0
})
const reviver = (k, v) => {
if (k !== '') {
return v
}
switch (v.type) {
case ShapeType.CIRCLE().toJSON():
return new Circle(v)
case ShapeType.DIAMOND().toJSON():
return new Diamond(v)
default:
throw TypeError('Unsupported or missing shape type in the Shape reviver.')
}
}
class Shape extends M.Base {
toJSON () {
const fields = M.fields(this)
let type
// the generated JSON must contain runtime type information for Modélico
// to be able to revive it later
switch (this[M.symbols.typeSymbol]()) {
case Circle:
type = ShapeType.CIRCLE()
break
case Diamond:
type = ShapeType.DIAMOND()
break
default:
throw TypeError('Unsupported Shape in the toJSON method.')
}
return Object.freeze(Object.assign({type}, fields))
}
static innerTypes () {
return Object.freeze({
// notice the self-reference here and how the subclasses are
// going to extend Shape's inner types
relatedShape: maybe(_(Shape))
})
}
static metadata () {
// We are going to use meta so that M.getSchema can give us a more
// robust JSON schema. If you don't need that, you could return the
// baseMetadata directly.
const baseMetadata = Object.assign({}, base(Shape), {reviver})
return meta(baseMetadata, {}, {}, () => ({
anyOf: [
Circle,
Diamond
].map(x => M.getSchema(base(x), false))
}))
}
}
Now remember how we have been using ajvMetadata and even enhanced the Shape metadata to account for its subtypes. This is going to allow us to get a very detailed schema that would not be easy to write by hand.
Note: definitions are sequentially named to avoid collisions. In the example below, the definition numbers have gaps because some of them got reserved in case they'd be reused. Short sub-schemas (less than 2 keys and not arrays) are always inlined.