Reputation: 987
I want to generate JSON schema where "additionalProperties" : false
will be applied for all classes which I have.
Suppose I have following classes:
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;
}
}
When I am generating schema as following like below code the schema property "additionalProperties" : false
was applying only for the class A
.
ObjectMapper mapper = new ObjectMapper();
JsonSchemaGenerator schemaGen = new JsonSchemaGenerator(mapper);
ObjectSchema schema = schemaGen.generateSchema(A.class).asObjectSchema();
schema.rejectAdditionalProperties();
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema);
How can I generate the schema where "additionalProperties" : false
will be applied on all classes?
Example of 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"
}
}
}
}
}
Note: I don't want to generate schemes part by part.
For info: I have opened issue for this scenario if someone interested you can support fix of this issue. Generate json schema which should rejects all additional content
Upvotes: 16
Views: 7326
Reputation: 414
This is my solution, without any reflect and hack way, and it works very well for me.
public static void rejectAdditionalProperties(JsonSchema jsonSchema) {
if (jsonSchema.isObjectSchema()) {
ObjectSchema objectSchema = jsonSchema.asObjectSchema();
ObjectSchema.AdditionalProperties additionalProperties = objectSchema.getAdditionalProperties();
if (additionalProperties instanceof ObjectSchema.SchemaAdditionalProperties) {
rejectAdditionalProperties(((ObjectSchema.SchemaAdditionalProperties) additionalProperties).getJsonSchema());
} else {
for (JsonSchema property : objectSchema.getProperties().values()) {
rejectAdditionalProperties(property);
}
objectSchema.rejectAdditionalProperties();
}
} else if (jsonSchema.isArraySchema()) {
ArraySchema.Items items = jsonSchema.asArraySchema().getItems();
if (items.isSingleItems()) {
rejectAdditionalProperties(items.asSingleItems().getSchema());
} else if (items.isArrayItems()) {
for (JsonSchema schema : items.asArrayItems().getJsonSchemas()) {
rejectAdditionalProperties(schema);
}
}
}
}
Upvotes: 1
Reputation: 3402
The following worked for me:
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();
Using maven dependency:
<dependency>
<groupId>com.kjetland</groupId>
<artifactId>mbknor-jackson-jsonschema_2.12</artifactId>
<version>1.0.28</version>
</dependency>
Using the following classes:
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;
}
}
...and 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;
}
}
Results in (output of jsonSchemaText
piped through jq -C .
for pretty formatting):
{
"$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"
]
}
}
}
This results in "additionalProperties": false
on both Test and TestChild.
Note: You can replace JsonSchemaConfig.nullableJsonSchemaDraft4()
with JsonSchemaConfig.vanillaJsonSchemaDraft4()
in your schema generation code to get rid of the "oneof" references with "type: null" or "type: ActualType" in favor of just "type: ActualType" (note, this still won't add them to the "required" array unless you annotate the properties with @JsonProperty(required = true)
).
Upvotes: 1
Reputation: 146490
Well I would go to a simpler route if you don't want to use reflections. I would use JSONPath. So you would need to add below to your pom.xml
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.3.0</version>
</dependency>
Then below code demonstrates how to alter the generated JSON file
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);
}
}
The output of the run is (formatted manually)
{
"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
}
}
}
Upvotes: 1
Reputation: 7123
You will need to specify the schema for each properties like:
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);
You can leverage reflection api to automatically do it for you. Here is a quick and dirty example:
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;
}
Upvotes: 4