用 Java 編程生成 JSON 模式
1. 概述
在本教程中,我們將使用Java JSON Schema Generator庫從 Java 創建 JSON 模式。
首先,我們將了解如何生成簡單的遞歸 JSON 模式。接下來,我們將了解可用的不同架構配置。接下來,我們將陸續從庫的一些模塊派生 JSON 模式: Jackson和Jakarta 驗證。最後,我們將設置一個Maven 插件,以便在 Maven generate
目標中包含 JSON 模式。
2. 設置
讓我們為我們的項目設置必要的依賴項。
2.1.核心依賴
首先,讓我們安裝[jsonschema-generator](https://mvnrepository.com/artifact/com.github.victools/jsonschema-generator)
:
<dependency>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-generator</artifactId>
<version>4.31.1</version>
</dependency>
它包含用於模式生成和配置的主要 API。
2.2.模塊
接下來,我們將安裝三個模塊來從類註釋生成 JSON 架構屬性。讓我們首先添加[jsonschema-module-jackson](https://mvnrepository.com/artifact/com.github.victools/jsonschema-module-jackson)
:
<dependency>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-module-jackson</artifactId>
<version>4.31.1</version>
</dependency>
該模塊從 Jackson 註釋中派生 JSON 模式屬性。
繼續,我們將安裝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>
稍後,我們將定義[configuration](https://github.com/victools/jsonschema-generator/tree/main/jsonschema-maven-plugin#complete-example)
條目.
它需要生成模式的類、模式配置和要使用的模塊。
請注意,自Java JSON Schema Generator 4.7 版本以來,強烈建議模塊和插件使用與核心依賴項相同的版本。
3. 基礎知識
在本節中,我們將通過創建簡單的遞歸模式來探索jsonschema-generator的構建塊。
3.1.簡單模式
讓我們定義一篇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](https://json-schema.org/draft/2020-12/release-notes.html)
,這是目前最新的 JSON 架構草案。如果未指定,架構將使用DRAFT-7
規範生成。
PLAIN_JSON
OptionPreset
包含許多默認配置,以使用每個非靜態類字段進行模式生成。其他可用的預設是JAVA_OBJECT
和FULL_DOCUMENTATION
。第一個包括架構中的公共字段和方法,而第二個包括所有字段和公共方法。如果未指定,預設默認為**FULL_DOCUMENTATION** .
生成的架構遵循[DRAFT_2020-12](https://json-schema.org/draft/2020-12/release-notes.html)
結構:
{
"$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
是模式中的字符串。幸運的是,由於EXTRA_OPEN_API_FORMAT_VALUES
生成器選項,它們的實際類型在field
格式中指定。它為特殊 JSON 模式字符串添加了額外信息。
最後,Java 枚舉通過調用它們的name()
方法來表示。
3.2.遞歸模式
讓我們有一個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
類的架構是遞歸架構。
有趣的是, author
屬性的articles
字段引用了正在生成的實際模式:
{
"author":{
"type":"object",
"properties":{
"articles":{
"type":"array",
"items":{
"$ref":"#"
}
}
}
}
}
規範允許這種循環引用。但是, $ref
不能指向另一個$ref
。
4. 配置
在上一節中,我們使用了一些內置預設。現在,我們將了解如何實現細粒度配置。
首先,我們將使用單獨的配置自定義生成的架構屬性。接下來,我們就先睹為快看看高級配置。
4.1.個性化配置
讓我們為Author
類配置模式字段:
configBuilder.forFields()
.withRequiredCheck(field -> field.getAnnotationConsideringFieldAndGetter(Nullable.class) == null)
.withArrayUniqueItemsResolver(scope -> scope.getType().getErasedType() == (List.class) ? true : null);
生成的架構會根據需要標記不可為空的屬性。它還使articles
屬性成為一個唯一的數組:
{
"$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"
]
}
上面的模式還具有createdAt
和articles
屬性的默認值。這是由於我們的類型配置:
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);
ArrayUniqueItemsResolver
確保數組從List
類型生成時被標記為唯一。
就像我們配置字段和類型一樣,我們也可以配置方法:
configBuilder.forMethods()
.withRequiredCheck(method -> method.getAnnotationConsideringFieldAndGetter(NotNull.class) != null);
我們根據需要標記註釋為@NotNull
字段。如果該註釋位於其 getter 上,則它們也是必需的。
此外,對於每個配置,返回null
不會在架構中設置字段。
4.2.高級配置
在本節中,我們將使用AdvancedArticle
類:
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
}
高級配置是自定義 JSON Schema 生成的終極手段。當我們需要單個配置未提供的屬性時,它特別有用:
configBuilder.forFields()
.withInstanceAttributeOverride((node, field, context) -> node.put("readOnly", field.getDeclaredType().isInstanceOf(UUID.class)));
在這裡,我們為每個屬性添加了一個readOnly
屬性。除UUID
類外,它默認為false
:
{
"id":{
"type":"string",
"format":"uuid",
"readOnly":true
},
"title":{
"type":"string",
"readOnly":false
}
}
另一個有趣的配置是能夠在給定字段中指定允許的類型。在我們的AdvancedArticle
類中, createdAt
屬性接受Date
和Timestamp
類型:
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));
在底層, TargetTypeOverride
類處理每個帶@AllowedTypes.
然後,它將這些類型添加到生成的createdAt
屬性中:
{
"createdAt":{
"anyOf":[
{
"type":"object",
"properties":{
"nanos":{
"type":"integer",
"format":"int32",
"readOnly":false
}
},
"readOnly":false
},
{
"type":"string",
"format":"date-time",
"readOnly":false
}
]
}
}
正如我們所看到的,生成的聯合類型由anyOf
屬性指定。
讓我們記住,配置的可能性是無限的。我們甚至可以添加自定義類型定義或自定義屬性定義。我們可以選擇哪種級別的定制來滿足我們的需求。
5. 模塊
Java JSON 模式生成器允許我們對模塊中的配置進行分組。我們可以通過實現Module
接口來創建我們自己的模塊。在接下來的部分中,我們將了解如何使用一些內置模塊。我們將探索傑克遜和雅加達驗證模塊。
5.1.傑克遜
Jackson 模塊處理 Jackson 註釋以創建 JSON 架構配置。讓我們考慮一下我們的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
選項指示模塊在架構中生成readOnly
字段時考慮JsonProperty.Access
。
生成的模式已正確設置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.constraints
註釋生成模式配置。讓我們來裝飾我們的Person
類:
class Person {
@NotNull
UUID id;
@NotNull
String name;
@NotNull
@Email
@Pattern(regexp = "\\b[A-Za-z0-9._%+-][email protected]\\.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
,生成的模式包括用@Pattern
註釋的所有屬性的模式字段:
{
"type":"object",
"properties":{
"createdAt":{
"type":"string"
},
"email":{
"type":"string",
"format":"email",
"pattern":"\\b[A-Za-z0-9._%+-][email protected]\\.com\\b"
},
"friends":{
"maxItems":10,
"type":"array",
"items":{
"$ref":"#"
}
},
"fullName":{
"type":[
"string",
"null"
]
}
},
"required":[
"createdAt",
"email",
"id",
"name",
"surname"
]
}
請注意, email
屬性有一個format
字段,因為它在Person
類中用@Email
進行了註釋。同樣, friends
屬性也正確設置了maxItems
字段。
6.Maven插件
Java JSON 模式生成器有一個 Maven 插件,可以從我們的構建過程中生成模式。讓我們配置插件:
<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>
我們將基於com.baeldung.jsonschemageneration.plugin
包中的Person
類生成一個架構。我們仍然可以定義要使用的模塊,並向它們傳遞一些選項。然而,該插件不允許配置自定義模塊的選項。
最後,生成的文件名模式由包名{1}
和類名{0}
組成。它將位於src\main\resources\schemas\com\baeldung\jsonschemageneration\plugin\Person.json
。要生成它,我們運行mvn compile
。
生成的模式遵循插件配置中指定的每個條件。
七、結論
在本文中,我們使用Java JSON Schema Generator在 Java 中生成 JSON Schema。在了解了架構生成的一些基礎知識之後,我們了解瞭如何微調架構配置。接下來,我們探索了庫中的各種模塊來生成 JSON 模式。最終,我們使用專用插件創建了 JSON 模式,作為 Maven generate
目標的一部分。
與往常一樣,本文的代碼可以在 GitHub 上獲取。