Джексон: Как я могу сгенерировать json-схему, которая отвергает весь дополнительный контент
Я хочу создать схему JSON, где "additionalProperties": false
будет применяться для всех классов, которые у меня есть.
Предположим, что у меня есть следующие классы:
class A{
private String s;
private B b;
public String getS() {
return s;
}
public B getB() {
return b;
}
}
class B{
private BigDecimal bd;
public BigDecimal getBd() {
return bd;
}
}
Когда я генерирую схему, как показано ниже, свойство схемы "additionalProperties": false
свойства "additionalProperties": false
применяется только для класса A
ObjectMapper mapper = new ObjectMapper();
JsonSchemaGenerator schemaGen = new JsonSchemaGenerator(mapper);
ObjectSchema schema = schemaGen.generateSchema(A.class).asObjectSchema();
schema.rejectAdditionalProperties();
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema);
Как я могу сгенерировать схему, где "additionalProperties": false
будет применяться ко всем классам?
Пример schema
{ "type": "object", "id": "urn:jsonschema:com.xxx.xxx:A", "additionalProperties": false, "properties": { "s": { "type": "string" }, "b": { "type": "object", "id": "urn:jsonschema:com.xxx.xxx:B", "properties": { "bd": { "type": "number" } } } } }
Примечание. Я не хочу генерировать схемы по частям.
Ответы
Ответ 1
Вам нужно будет указать схему для каждого свойства, например:
ObjectMapper mapper = new ObjectMapper();
JsonSchemaGenerator schemaGen = new JsonSchemaGenerator(mapper);
ObjectSchema schemaB = schemaGen.generateSchema(B.class).asObjectSchema();
schemaB.rejectAdditionalProperties();
ObjectSchema schema = schemaGen.generateSchema(A.class).asObjectSchema();
schema.rejectAdditionalProperties();
schema.putProperty("b", schemaB);
Вы можете использовать отражение api, чтобы автоматически сделать это за вас. Вот быстрый и грязный пример:
public static void main(String[] args) throws JsonProcessingException {
final ObjectMapper mapper = new ObjectMapper();
final JsonSchemaGenerator schemaGen = new JsonSchemaGenerator(mapper);
ObjectSchema schema = generateSchema(schemaGen, A.class);
schema.rejectAdditionalProperties();
System.out.print(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema));
}
public static <T> ObjectSchema generateSchema(JsonSchemaGenerator generator, Class<T> type) throws JsonMappingException {
ObjectSchema schema = generator.generateSchema(type).asObjectSchema();
for (final Field field : type.getDeclaredFields()) {
if (!field.getType().getName().startsWith("java") && !field.getType().isPrimitive()) {
final ObjectSchema fieldSchema = generateSchema(generator, field.getType());
fieldSchema.rejectAdditionalProperties();
schema.putProperty(field.getName(), fieldSchema);
}
}
return schema;
}
Ответ 2
Следующие работали для меня:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kjetland.jackson.jsonSchema.JsonSchemaConfig;
import com.kjetland.jackson.jsonSchema.JsonSchemaGenerator;
...
ObjectMapper objectMapper = new ObjectMapper();
JsonSchemaConfig config = JsonSchemaConfig.nullableJsonSchemaDraft4();
JsonSchemaGenerator schemaGenerator = new JsonSchemaGenerator(objectMapper, config);
JsonNode jsonNode = schemaGenerator.generateJsonSchema(Test.class);
String jsonSchemaText = jsonNode.toString();
Использование зависимости maven:
<dependency>
<groupId>com.kjetland</groupId>
<artifactId>mbknor-jackson-jsonschema_2.12</artifactId>
<version>1.0.28</version>
</dependency>
Используя следующие классы:
Test.java:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Test {
@JsonProperty(required = true)
private final String name;
private final TestChild child;
@JsonCreator
public Test (
@JsonProperty("name") String name,
@JsonProperty("child") TestChild child) {
this.name = name;
this.child = child;
}
public String getName () {
return name;
}
public TestChild getChild () {
return child;
}
}
... и TestChild.java:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class TestChild {
@JsonProperty(required = true)
private final String childName;
@JsonCreator
public TestChild (@JsonProperty("childName") String childName) {
this.childName = childName;
}
public String getChildName () {
return childName;
}
}
Результаты (вывод jsonSchemaText
через jq -C.
Для форматирования):
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Test",
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
},
"child": {
"oneOf": [
{
"type": "null",
"title": "Not included"
},
{
"$ref": "#/definitions/TestChild"
}
]
}
},
"required": [
"name"
],
"definitions": {
"TestChild": {
"type": "object",
"additionalProperties": false,
"properties": {
"childName": {
"type": "string"
}
},
"required": [
"childName"
]
}
}
}
Это приводит к "additionalProperties": false
свойствам "additionalProperties": false
как для Test, так и для TestChild.
Примечание. Вы можете заменить JsonSchemaConfig.nullableJsonSchemaDraft4()
на JsonSchemaConfig.vanillaJsonSchemaDraft4()
в коде генерации схемы, чтобы избавиться от ссылок "oneof" с "type: null" или "type: ActualType" в пользу только "type: ActualType "(обратите внимание: это все равно не добавит их в" требуемый "массив, если вы не аннотируете свойства с помощью @JsonProperty(required = true)
).
Ответ 3
Хорошо, я бы пошел на более простой маршрут, если вы не хотите использовать отражения. Я бы использовал JSONPath. Поэтому вам нужно будет добавить ниже свой pom.xml
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.3.0</version>
</dependency>
Затем ниже код демонстрирует, как изменить сгенерированный файл JSON
package taruntest;
import com.jayway.jsonpath.*;
public class Test {
public static void main(String[] args) throws Exception {
String data = "{\n" +
" \"type\" : \"object\",\n" +
" \"id\" : \"urn:jsonschema:com.xxx.xxx:A\",\n" +
" \"additionalProperties\" : false,\n" +
" \"properties\" : {\n" +
" \"s\" : {\n" +
" \"type\" : \"string\"\n" +
" },\n" +
" \"b\" : {\n" +
" \"type\" : \"object\",\n" +
" \"id\" : \"urn:jsonschema:com.xxx.xxx:B\",\n" +
" \"properties\" : {\n" +
" \"bd\" : {\n" +
" \"type\" : \"number\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
DocumentContext doc = JsonPath.parse(data);
doc.put("$..[?(@.id =~ /urn:jsonschema:.*/)]", "additionalProperties", false);
String modified = doc.jsonString();
System.out.println(modified);
}
}
Результат запуска (отформатирован вручную)
{
"type": "object",
"id": "urn:jsonschema:com.xxx.xxx:A",
"additionalProperties": false,
"properties": {
"s": {
"type": "string"
},
"b": {
"type": "object",
"id": "urn:jsonschema:com.xxx.xxx:B",
"properties": {
"bd": {
"type": "number"
}
},
"additionalProperties": false
}
}
}
Ответ 4
Вы можете добиться этого, добавив интерфейс в эти классы:
public interface I {
boolean additionalProperties = false;
}
public class A implements I {
private String s;
private B b;
public String getS() {
return s;
}
public B getB() {
return b;
}
}
public class B implements I {
private BigDecimal bd;
public BigDecimal getBd() {
return bd;
}
}