Джексон: Как я могу сгенерировать 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;
    }
}