Concept
The generation of a schema from a kotlin type is done as a sequence of steps. Each step transforms or adds to the data from the preview step. Additional steps can simply be slotted in via extension functions on the data.
The complete process can be split into four rough phases:
1. Initial Type Collection
Before any type can be analyzed and schema be generated, the initial types have to be provided or collected. For the most part, this involves providing only the one initial type to analyze and generate a schema for. However, sometimes additional types (e.g. loosely connected subtypes) have to be added manually or collected via a step for them to be included in the final schema.
Collecting Subtypes
@SubType(MySubClass::class)
open class MyExampleClass(
val someText: String,
val someNullableInt: Int?,
val someBoolList: List<Boolean>
)
initial<MyExampleClass>()
.collectSubTypes()
//...
2. Type Analysis
Type analysis is the second step to generating schemas. It looks at all involved Kotlin or Java types and extracts information about methods, fields, subtypes, supertypes, type parameters and more. This can either be done by inspecting classes using reflection or by working with the information provided by Kotlinx.Serialization.
Resulting data can be further modified or added to with additional steps, e.g. add missing supertype-connections.
Type Analysis
class MyExampleClass(
val someText: String,
val someNullableInt: Int?,
val someBoolList: List<Boolean>,
)
initial<MyExampleClass>()
.analyzeTypeUsingReflection()
.addDiscriminatorProperty("_type")
//...
3. Schema Generation
Takes the result from the type data analysis and converts it into any schema. This can be done in multiple steps to allow for easier modification of the schema during generation, for example adding a title or descriptions.
The result of this phase is an independent schema for each associated type. Schemas may not be completely valid at this point, as e.g. references are not finalized yet.
Schema Generation
class MyExampleClass(
val someText: String,
val someNullableInt: Int?,
val someBoolList: List<Boolean>,
)
initial<MyExampleClass>()
//...
.generateJsonSchema()
.withTitle(TitleType.SIMPLE)
4. Schema Compilation
The final step in the pipeline is the schema compilation. This takes the previously generated and independent schemas and merges them into the final schema.
This merging can either mean inlining all individual schemas into a single schema or keeping them separate and only referencing them.
Schema Compilation
class MyExampleClass(
val someText: String,
val someNullableInt: Int?,
val someBoolList: List<Boolean>,
)
initial<MyExampleClass>()
//...
.compileInlining()