1. 概述
在本教程中,我們將使用 Java 從 Java JSON Schema Generator 庫生成 JSON 模式。
首先,我們將學習如何生成簡單的和遞歸的 JSON 模式。接下來,我們將查看可用的不同模式配置。然後,我們將逐步從庫的模塊中推導出 JSON 模式:Jackson 和 Jakarta validation。最後,我們將設置一個 Maven 插件,以便在 Maven 的 generate 目標中生成 JSON 模式。
2. Setup
讓我們為我們的項目設置必要的依賴項。
2.1. 核心依賴
首先,讓我們安裝 jsonschema-generator:
<dependency>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-generator</artifactId>
<version>4.31.1</version>
</dependency>
它包含用於生成和配置模式的主要 API。
2.2. 模塊
接下來,我們將安裝三個模塊,用於從類註解生成 JSON Schema 屬性。讓我們首先添加 jsonschema-module-jackson:
<dependency>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-module-jackson</artifactId>
<version>4.31.1</version>
</dependency>
此模塊從 Jackson 註解派生 JSON Schema 屬性。
繼續,我們將安裝 jsonschema-module-jakarta-validation 以從 Jakarta 驗證註解獲取模式:
<dependency>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-module-jakarta-validation</artifactId>
<version>4.31.1</version>
</dependency>
2.3. Maven 插件
最後,讓我們添加 jsonschema-maven-plugin:
<plugin>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-maven-plugin</artifactId>
<version>4.31.1</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
稍後,我們將定義 配置條目。 它接受用於生成模式的類、模式配置和要使用的模塊。
請注意,自 Java JSON Schema Generator 版本 4.7 以來,模塊和插件應與核心依賴項具有相同的版本。
3. Basics
在本節中,我們將探索 jsonschema-generator 的構建塊,通過創建簡單的和遞歸的模式。
3.1. Simple Schema
讓我們定義一個 Article:
public class Article {
private UUID id;
private String title;
private String content;
private Date createdAt;
private Area area;
// getters and setters omitted
}
我們將從 Article 類生成模式:
SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON);
SchemaGeneratorConfig config = configBuilder.with(Option.EXTRA_OPEN_API_FORMAT_VALUES)
.without(Option.FLATTENED_ENUMS_FROM_TOSTRING)
.build();
SchemaGenerator generator = new SchemaGenerator(config);
JsonNode jsonSchema = generator.generateSchema(Article.class);
在這裏,我們正在針對 DRAFT_2020-12,當前最新的 JSON Schema 草案。 如果未指定,則模式將使用 DRAFT-7 規範生成。
PLAIN_JSON OptionPreset 包含大量默認配置,用於為每個非靜態類字段生成模式。 其他可用的預設是 JAVA_OBJECT 和 FULL_DOCUMENTATION。 前者包括類中公共字段和方法的 schema,而後者包括所有字段和公共方法。 如果未指定,則預設默認為 FULL_DOCUMENTATION。
生成的模式尊重 DRAFT_2020-12 結構:
{
"$schema":"https://json-schema.org/draft/2020-12/schema",
"type":"object",
"properties":{
"area":{
"type":"string",
"enum":[
"JAVA",
"KOTLIN",
"SCALA",
"LINUX"
]
},
"content":{
"type":"string"
},
"createdAt":{
"type":"string",
"format":"date-time"
},
"id":{
"type":"string",
"format":"uuid"
},
"title":{
"type":"string"
}
}
}
讓我們注意到這裏的一些事情。 首先,Java Date 和 UUID 在模式中都是字符串。 幸運的是,它們的真實類型在 field format 中指定,這要歸功於 EXTRA_OPEN_API_FORMAT_VALUES 選項以及 generator option。 它為特殊 JSON Schema 字符串添加了額外信息。
最後,Java enums 通過調用它們的 name() 方法表示。
3.2. Recursive Schema
讓我們有一個 Author 類:
public class Author {
private UUID id;
private String name;
private String role;
private List<AuthoredArticle> articles;
// getters, setters, omitted
}
一個 author 擁有一個 AuthoredArticle 列表。 反之,一個 AuthoredArticle 擁有一個 author:
public class AuthoredArticle {
private Author author;
// getters and setters omitted
}
保持之前部分的所有配置,AuthoredArticle 類的模式是一個 遞歸模式。
有趣的是,articles 字段的 author 屬性通過 reference 引用實際生成的模式:
{
"author":{
"type":"object",
"properties":{
"articles":{
"type":"array",
"items":{
"$ref":"#"
}
}
}
}
}
這種循環引用允許規範。 但是, $ref 不能指向另一個 $ref。
4. Configuration
In the previous section, we used some built-in presets. Now, we’ll see how to achieve fine-grained configurations.
First, we’ll customize the generated schema attributes using individual configurations. Then, we’ll have a sneak peek at advanced configurations.
4.1. Individual Configuration
Let’s configure schema fields for our Author class:
configBuilder.forFields()
.withRequiredCheck(field -> field.getAnnotationConsideringFieldAndGetter(Nullable.class) == null)
.withArrayUniqueItemsResolver(scope -> scope.getType().getErasedType() == (List.class) ? true : null);
The resulting schema marks properties that aren’t nullable as required. It also makes the articles property a unique array:
{
"$schema":"https://json-schema.org/draft/2020-12/schema",
"type":"object",
"properties":{
"articles":{
"uniqueItems":true,
"type":"array",
"items":{
"type":"object",
"properties":{
"area":{
"type":"string",
"enum":[
"JAVA",
"KOTLIN",
"SCALA",
"LINUX"
]
},
"author":{
"$ref":"#"
},
"content":{
"type":"string"
},
"createdAt":{
"type":"string",
"format":"date-time",
"default":1690565063847
},
"id":{
"type":"string",
"format":"uuid"
},
"title":{
"type":"string"
}
},
"required":[
"area",
"author",
"content",
"createdAt",
"id",
"title"
]
},
"default":[
]
},
"id":{
"type":"string",
"format":"uuid"
},
"name":{
"type":"string"
},
"role":{
"type":"string"
}
},
"required":[
"articles",
"id",
"name",
"role"
]
}
The schema above has also default values for the createdAt and articles properties. It’s due to our configuration for types:
configBuilder.forTypesInGeneral()
.withArrayUniqueItemsResolver(scope -> scope.getType().getErasedType() == (List.class) ? true : null)
.withDefaultResolver(scope -> scope.getType().getErasedType() == List.class ? Collections.EMPTY_LIST : null)
.withDefaultResolver(scope -> scope.getType().getErasedType() == Date.class ? Date.from(Instant.now()) : null);
The ArrayUniqueItemsResolver ensures that an array is marked unique if it was generated from a List type.
Just like we’ve configured fields and types, we’re also able to configure methods:
configBuilder.forMethods()
.withRequiredCheck(method -> method.getAnnotationConsideringFieldAndGetter(NotNull.class) != null);
We mark fields annotated @NotNull as required. They are also required if that annotation is on their getter.
Besides, for each configuration, returning null doesn’t set the field in the schema.
4.2. Advanced Configuration
In this section, we’ll use our AdvancedArticle class:
public class AdvancedArticle {
private UUID id;
private String title;
private String content;
@AllowedTypes({Timestamp.class, String.class, Date.class})
private Object createdAt;
private Area area;
// getters and setters omitted
}
Advanced configuration is the ultimate resort to customize JSON Schema generation. It’s particularly useful when we need attributes that aren’t provided by the individual configuration.
configBuilder.forFields()
.withInstanceAttributeOverride((node, field, context) -> node.put("readOnly", field.getDeclaredType().isInstanceOf(UUID.class)));
Here, we’ve added a readOnly attribute to every property. It defaults to false except for the UUID class:
{
"id":{
"type":"string",
"format":"uuid",
"readOnly":true
},
"title":{
"type":"string",
"readOnly":false
}
}
Another interesting configuration is the ability to specify allowed types in a given field. In our AdvancedArticle class, the createdAt property accepts both Date and Timestamp types:
configBuilder.forFields()
.withTargetTypeOverridesResolver(field -> Optional.ofNullable(field.getAnnotationConsideringFieldAndGetterIfSupported(AllowedTypes.class))
.map(AllowedTypes::value)
.map(Stream::of)
.map(stream -> stream.map(subtype -> field.getContext().resolve(subtype)))
.map(stream -> stream.collect(Collectors.toList()))
.orElse(null));
Under the hood, the TargetTypeOverride class processes every field annotated @AllowedTypes. Then, it adds those types to the resulting createdAt property:
{
"createdAt":{
"anyOf":[
{
"type":"object",
"properties":{
"nanos":{
"type":"integer",
"format":"int32",
"readOnly":false
}
},
"readOnly":false
},
{
"type":"string",
"format":"date-time",
"readOnly":false
}
]
}
}
As we can see, the resulting union type is specified by the anyOf attribute.
Let’s keep in mind that the configuration possibilities are endless. We can even add custom type definitions or custom property definitions. It’s up to us to choose which level of customization to cover our needs.
5. 模塊
Java JSON Schema Generator 允許我們使用 配置 在模塊中分組。 我們可以通過實現 Module 接口 創建我們自己的模塊。 在下一部分中,我們將看到如何使用一些 內置模塊。 我們將探索 Jackson 和 Jakarta Validation 模塊。
5.1. Jackson
Jackson 模塊 進程 Jackson 註解以創建 JSON Schema 配置。 讓我們考慮我們的 Person 類:
class Person {
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
UUID id;
@JsonProperty(access = JsonProperty.Access.READ_WRITE, required = true)
String name;
@JsonProperty(access = JsonProperty.Access.READ_WRITE, required = true)
String surname;
@JsonProperty(access = JsonProperty.Access.READ_WRITE, required = true)
Address address;
@JsonIgnore
String fullName;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
Date createdAt;
@JsonProperty(access = JsonProperty.Access.READ_WRITE)
List<Person> friends;
//getters and setters omitted
}
讓我們添加 JacksonModule 到我們的 SchemaGeneratorConfigBuilder:
JacksonModule module = new JacksonModule(RESPECT_JSONPROPERTY_REQUIRED);
SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(DRAFT_2020_12, PLAIN_JSON).with(module)
.with(EXTRA_OPEN_API_FORMAT_VALUES);
SchemaGenerator generator = new SchemaGenerator(configBuilder.build());
JsonNode jsonSchema = generator.generateSchema(Person.class);
模塊接受某些選項以進行進一步自定義。 RESPECT_JSONPROPERTY_REQUIRED 選項指示模塊將 JsonProperty.Access 在 readOnly 字段的生成中考慮。
結果的 schema 具有 required 和 readOnly 字段正確設置:
{
"type":"object",
"properties":{
"createdAt":{
"type":"string",
"format":"date-time",
"readOnly":true
},
"friends":{
"type":"array",
"items":{
"$ref":"#"
}
},
"id":{
"type":"string",
"format":"uuid",
"readOnly":true
}
},
"required":[
"address",
"name",
"surname"
]
}
未註釋的屬性和帶有 @JsonIgnore 註解的屬性將被忽略。 嵌套的 Address 類和遞歸的 friends 屬性將被正確引用。
5.2. Jakarta Validation
Jakarta Validation 模塊 生成從 jakarta.validation.constraints 註解生成的 schema 配置。 讓我們裝飾我們的 Person 類:
class Person {
@NotNull
UUID id;
@NotNull
String name;
@NotNull
@Email
@Pattern(regexp = "\\b[A-Za-z0-9._%+-]+@baeldung\\.com\\b")
String email;
@NotNull
String surname;
@NotNull
Address address;
@Null
String fullName;
@NotNull
Date createdAt;
@Size(max = 10)
List<Person> friends;
//getters and setters omitted
}
接下來,讓我們配置 JakartaValidationModule:
JakartaValidationModule module = new JakartaValidationModule(NOT_NULLABLE_FIELD_IS_REQUIRED, INCLUDE_PATTERN_EXPRESSIONS);
SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(DRAFT_2020_12, PLAIN_JSON).with(module);
SchemaGenerator generator = new SchemaGenerator(configBuilder.build());
JsonNode jsonSchema = generator.generateSchema(Person.class);
模塊可以選擇通過其 forValidationGroups() 方法傳遞驗證組。
NOT_NULLABLE_FIELD_IS_REQUIRED 選項使帶有 @NotNull 註解的字段設置為必需的。 感謝 INCLUDE_PATTERN_EXPRESSIONS,生成的 schema 包含所有帶有 @Pattern 註解的屬性的模式字段:
{
"type":"object",
"properties":{
"createdAt":{
"type":"string"
},
"email":{
"type":"string",
"format":"email",
"pattern":"\\b[A-Za-z0-9._%+-]+@baeldung\\.com\\b"
},
"friends":{
"maxItems":10,
"type":"array",
"items":{
"$ref":"#"
}
},
"fullName":{
"type":[
"string",
"null"
]
}
},
"required":[
"createdAt",
"email",
"id",
"name",
"surname"
]
}
讓我們注意到 email 屬性由於帶有 @Email 註解,因此具有 format 字段,並且 friends 屬性的 maxItems 字段已正確設置。
6. Maven 插件
Java JSON Schema Generator 具有 Maven 插件,用於從我們的構建過程生成 schema。 讓我們配置插件:
<plugin>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-maven-plugin</artifactId>
<version>4.31.1</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<packageNames>
<packageName>com.baeldung.jsonschemageneration.plugin</packageName>
</packageNames>
<classNames>
<className>com.baeldung.jsonschemageneration.plugin.Person</className>
</classNames>
<schemaVersion>DRAFT_2020_12</schemaVersion>
<schemaFilePath>src/main/resources/schemas</schemaFilePath>
<schemaFileName>{1}/{0}.json</schemaFileName>
<failIfNoClassesMatch>true</failIfNoClassesMatch>
<options>
<preset>PLAIN_JSON</preset>
<enabled>
<option>DEFINITIONS_FOR_ALL_OBJECTS</option>
<option>FORBIDDEN_ADDITIONAL_PROPERTIES_BY_DEFAULT</option>
</enabled>
<disabled>SCHEMA_VERSION_INDICATOR</disabled>
</options>
<modules>
<module>
<name>Jackson</name>
<options>
<option>RESPECT_JSONPROPERTY_REQUIRED</option>
</options>
</module>
<module>
<name>JakartaValidation</name>
<options>
<option>NOT_NULLABLE_FIELD_IS_REQUIRED</option>
<option>INCLUDE_PATTERN_EXPRESSIONS</option>
</options>
</module>
</modules>
</configuration>
</plugin>
我們將基於 Person 類(位於 com.baeldung.jsonschemageneration.plugin 包中)生成 schema。 我們可以仍然定義使用的模塊,並向它們傳遞一些選項。 但是,該插件不允許為自定義模塊配置選項。
最後,生成的的文件名模式由 {1}(即包名)和 {0}(即類名)組成。 它將位於 src\main\resources\schemas\com\baeldung\jsonschemageneration\plugin\Person.json。 要生成它,讓我們運行 mvn compile。
生成的 schema 尊重配置中指定的每個條件。
7. 結論
在本文中,我們使用了 Java JSON Schema Generator 來生成 Java 中的 JSON Schema。在對 schema 生成的一些基本知識的學習之後,我們看到了如何調整 schema 配置。接下來,我們探索了庫中的各種模塊以生成 JSON schema。最終,我們通過 Maven 的 generate 目標,使用一個專用插件創建了 JSON Schema。