Gradle JSON Schema code generation plugin.
The means of specifying input files to the generation process has been extended; see the inputs section.
The json-kotlin-schema-codegen project provides a means of
generating Kotlin or Java classes (or TypeScript interfaces) from JSON Schema object
descriptions.
The json-kotlin-gradle plugin simplifies the use of this code generation mechanism from the
Gradle Build Tool.
Include the following at the start of your build.gradle.kts file:
import net.pwall.json.kotlin.codegen.gradle.JSONSchemaCodegenPlugin
import net.pwall.json.kotlin.codegen.gradle.JSONSchemaCodegen // only required if "configure" block included
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("net.pwall.json:json-kotlin-gradle:0.121")
}
}
apply<JSONSchemaCodegenPlugin>()The apply line should come after the buildscript block, and before any configure block (see below).
And to have the generated source files compiled:
sourceSets.main {
java.srcDirs("build/generated-sources/kotlin")
}This shows the use of the default output directory; if it is changed by the use of the outputDir
configuration property, this setting will also change.
The srcDirs function takes a vararg list; if other source files are to be included in the compilation task the
directories may be added to the function call.
The plugin follows the principle of “convention over configuration”. The default location for the schema file or files to be input to the generation process is:
«project root»/src/main/resources/schema
and the default location for the config file (if required) is:
«project root»/src/main/resources/codegen-config.json
If your files are in these default locations, the above additions to build.gradle.kts will be all you will need.
If you wish to specify the location of your schema or config files, or if you wish to use any of the other plugin customisation options, you may include a configuration block as follows:
configure<JSONSchemaCodegen> {
configFile.set(file("path/to/your/config.json")) // if not in the default location
packageName.set("your.package.name") // if not specified in a config file
generatorComment.set("comment...")
inputs {
inputFile(file("path/to/your/schema/file/or/files")) // if not in the default location
inputFile {
file.set(file("path/to/your/schema/file/or/files")) // alternative syntax for above
}
inputComposite {
file.set(file("path/to/your/schema/composite/file"))
pointer.set("/\$defs") // a JSON Pointer to the group of definitions within the file
}
inputURI(uri("https://example.com/path/to/file"))
inputCompositeURI(uri("https://example.com/path/to/composite"), "\$defs")
// inputFile, inputComposite, inputURI and inputCompositeURI
// may be repeated as necessary in any combination
}
classMappings { // configure specific class mappings if required
byFormat("java.time.Duration", "duration")
}
schemaExtensions { // configure extension keyword uses if required
patternValidation("x-type", "account-number", Regex("^[0-9]{4,10}$"))
}
}If any setting in this configuration block conflicts with the equivalent setting in the config file, this configuration
block (in build.gradle.kts) takes precedence.
The inputs section specifies the location(s) of the input files.
By default, the plugin will process all files in the src/main/resources/schema directory (and subdirectories);
to specify a different location or a combination of inputs, the inputs section allows multiple inputFile or
inputComposite definitions.
This specifies an individual file or directory of files to be added to the list of files to be processed.
inputs {
inputFile {
file.set(file("path/to/your/schema/file/or/files"))
subPackage.set("sub.package.name") // optional sub-package name
}
inputFile(file("path/to/your/schema/file/or/files")) // alternative syntax
inputFile(file("path/to/your/schema/file/or/files"), "sub.package.name") // alternative syntax
}If a subPackage name is supplied, the class or classes generated from this inputFile declaration will be output to
the package base.subPackage where base is the base package from the packageName declaration or the
config file, and subPackage is the name given here.
The name may be a single name or a structured name using dot separators.
As many inputFile entries as are required may be specified, and they may be combined with other forms of input
specification.
One of the common uses of the code generator is to generate a set of classes from a composite file.
For example, an OpenAPI file may contain a number of schema definitions for the request and response objects of the API,
or a JSON Schema file may contain a set of definitions in the $defs section.
To specify this type of usage:
inputs {
inputComposite {
file.set(file("path/to/your/composite/file"))
pointer.set("/\$defs")
include.set(listof("IncludeThis", "AndThis")) // optional - specifies classes to include
exclude.set(listof("NotThis", "NorThis")) // optional - sepcifies files to exclude
}
inputComposite(file.set(file("path/to/your/composite/file")), "\$defs") // alternative syntax
}As many inputComposite entries as are required may be specified, and they may be combined with other forms of input
specification.
Many organisations will make their schema files available via a public URL. Others will have a local schema repository accessible within their own VPN. The code generator can generate files directly from a URI:
inputs {
inputURI {
uri.set(uri("https://local.domain.com/schema/customer.json"))
}
inputURI(uri("https://local.domain.com/schema/customer.json")) // alternative syntax
}In this case, the URI must be a valid URL pointing to a downloadable file.
As many inputURI entries as are required may be specified, and they may be combined with other forms of input
specification.
Composite files may also be accessed by URI:
inputs {
inputCompositeURI {
uri.set(uri("https://local.domain.com/schema/api.json"))
pointer.set("/\$defs")
include.set(listof("IncludeThis", "AndThis")) // optional - specifies classes to include
exclude.set(listof("NotThis", "NorThis")) // optional - sepcifies files to exclude
}
inputCompositeURI(uri("https://local.domain.com/schema/api.json"), "\$defs") // alternative syntax
}The URI must be a valid URL pointing to a downloadable file, and the pointer string must select a sub-tree within that
file.
To include two separate sections of the same file, include two separate inputCompositeURI entries with the same URI
but different pointers (and possibly include/exclude settings).
As many inputCompositeURI entries as are required may be specified, and they may be combined with other forms of input
specification.
To include only a nominated subset of definitions from a combined file (using inputComposite or
inputCompositeURI), specify the names of the definitions to be included (optional – see
example above).
Alternatively, to exclude nominated definitions from a combined file, specify the names of the definitions to be excluded.
If both include and exclude are supplied for the same composite, both will be applied, but this clearly
doesn’t make a great deal of sense since in order to be excluded, a definition must first have been explicitly
included.
The alternative (shorter) syntax for inputComposite or inputCompositeURI does not provide for the specification of
include or exclude.
If the config file is not in the default location of src/main/resources/codegen-config.json, the location of the file
may be specified using the configFile property:
configFile.set(file("path/to/your/config.json"))This is the earlier means of specifying the input file or files;
the inputs section above is more flexible, and the older form may be deprecated in future.
inputFile.set(file("path/to/your/schema/file/or/files"))This is the earlier means of specifying a composite input file;
the inputs section above is more flexible, and the older form may be deprecated in future.
inputFile.set(file("path/to/your/file")) // must be a single file, and the default is not allowed
pointer.set("/pointer/to/definitions") // JSON Pointer to the object containing the definitionsThis is the earlier means of specifying classes to be included from a composite input file;
the inputs section above is more flexible, and the older form may be deprecated in future.
include.set(listof("IncludeThis", "AndThis"))This is the earlier means of specifying classes to be included from a composite input file;
the inputs section above is more flexible, and the older form may be deprecated in future.
exclude.set(listof("NotThis", "NorThis"))The default output directory is build/generated-sources/«language», where «language» is kotlin, java or
ts (for TypeScript), depending on the target language.
To specify a different output directory, use:
outputDir.set(file("path/to/your/output/directory"))The default target language is Kotlin (naturally enough, for a project written in Kotlin and primarily intended to target Kotlin). To specify Java output:
language.set(file("java"))The value of this setting must be kotlin, java or typescript.
This setting may be specified in the config file, and that is the recommended practice.
By default, classes will be generated without a package directive.
To supply a package name:
packageName.set(file("com.example.model"))This setting may be specified in the config file, and that is the recommended practice.
The generatorComment allows the provision of a line of text to be added to the comment block output to the start of
each generated file:
generatorComment.set("This was generated from Schema version 1.0")This setting may be specified in the config file, and that is the recommended practice.
This property allows the specification of custom types to be used for class properties or array items.
It has been superseded by the
customClasses facility of
the config file, and will probably be deprecated in future releases and eventually removed.
This property allows the specification of custom schema extensions to add validations to be mapped to certain
keyword-value combinations.
It has been superseded by the
extensionValidations
facility of the config file, and will probably be deprecated in future releases and eventually removed.
Many people prefer to use the Groovy syntax for Gradle files, even for Kotlin-based projects.
In this case, the buildscript at the start of the build.gradle file will be:
import net.pwall.json.kotlin.codegen.gradle.JSONSchemaCodegenPlugin
buildscript {
repositories {
mavenLocal()
}
dependencies {
classpath("net.pwall.json:json-kotlin-gradle:0.121")
}
}and the apply statement must be added after any plugins block:
apply plugin: JSONSchemaCodegenPluginTo compile the generated sources:
sourceSets {
main.kotlin.srcDirs += "build/generated-sources/kotlin"
}Then, the configuration block (if required) will be something like:
jsonSchemaCodegen {
configFile = file('path/to/your/config.json')
inputFile = file('path/to/your/file/or/files')
pointer = '/pointer'
exclude = ['ExcludeThis', 'AndThis']
packageName = 'com.example.model'
generatorComment = 'This was generated from Schema version 1.0'
}(The classMappings and schemaExtensions sections are not available in Groovy.
The inputs section is also not available – the inputFile and pointer definitions as shown in the above
example must be used instead.)
This plugin has been tested with Gradle version 8.1.1. The build process gives warnings about features that will be incompatible with future Gradle versions, but for the moment it seems to be OK.
(For Maven users, there is a Maven equivalent to this plugin –
json-kotlin-maven.)
Peter Wall
2025-05-15