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.111")
}
}
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 definitions
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.
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.111")
}
}
and the apply
statement must be added after any plugins
block:
apply plugin: JSONSchemaCodegenPlugin
To 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
2024-10-27