Java multi-schema generator using annotations



I have a series of inter-related Java classes which form a super-set of possible schema. I’m looking for some way to annotate/tag individual fields such that I can create separate JSON schema for each ‘namespace’.

A simple example:

class SupersetClass {
    @BelongsToSchema(schema={"alice"}, description="foo")
    Integer a;

    @BelongsToSchema(schema={"alice", "bob"}, description="bar")
    String b;

    @BelongsToSchema(schema={"bob"}, description="")
    Long c;
} 

Output would have separate Alice and Bob schema, where Alice has a and b, and Bob has b and c.

My current idea is iterate over the schema I’d like to generate, then use reflection to create a custom derived class and pass that into jackson-mapper, but this is quite OTT if there’s already a good way to do this.

Answer

Disclaimer: I’m the maintainer of the victools/jsonschema-generator library mentioned below.

If you’re not dead-set on using the (slightly outdated) FasterXML/jackson-module-jsonSchema, you could use the victools/jsonschema-generator library. The latter is supporting the newer JSON Schema Draft versions and gives you a lot of flexibility in terms of what ends up in your generated schema. However, it is (at least as of today) not supporting the same range of jackson specific annotations out-of-the-box.

That being said, there are multiple ways to go about what you’re asking.

1. Simply ignore properties that belong to a different context

SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(
    SchemaVersion.DRAFT_2019_09, OptionPreset.PLAIN_JSON);
configBuilder.forFields()
    .withIgnoreCheck(field -> {
        BelongsToSchema annotation = field.getAnnotation(BelongsToSchema.class);
        return annotation != null
            && !Arrays.asList(annotation.schema()).contains("alice");
    });

2. Exclude the schema of properties from a different context while mentioning them

configBuilder.forFields()
    .withTargetTypeOverridesResolver(field -> {
        BelongsToSchema annotation = field.getAnnotation(BelongsToSchema.class);
        if (annotation == null || Arrays.asList(annotation.value()).contains("alice")) {
            return null;
        }
        return Collections.singletonList(field.getContext().resolve(Object.class));
    });

3. Include an external $ref instead of the actual subschema

configBuilder.forFields()
    .withCustomDefinitionProvider((field, context) -> {
        BelongsToSchema annotation = field.getAnnotation(BelongsToSchema.class);
        if (annotation == null || Arrays.asList(annotation.value()).contains("alice")) {
            return null;
        }
        ObjectNode customSubschema = context.getGeneratorConfig().createObjectNode()
                .put(SchemaKeyword.TAG_REF.forVersion(SchemaVersion.DRAFT_2019_09),
                        "https://your-external.ref/" + field.getSimpleTypeDescription());
        return new CustomPropertyDefinition(customSubschema);
    });

There are probably a few more possibilities depending on what it is you want exactly.

I encourage you to play around with it a bit and have a look at the documentation. If you have project-specific questions, feel free to raise those as Issues on GitHub.



Source: stackoverflow