Skip to content

The situation with allOf is hard to deal with #584

Description

@rupert-madden-abbott

Thanks for the brilliant library!

There are a bunch of situations in which allOf is inserted into the schema due to multiple annotations supplying the same field. I understand that this is the intended behavior.

Here are some examples:

I understand the reasoning behind the choice but the problem is that it leads to difficult to predict consequences.

For example, this leads to an allOf element being inserted along with both descriptions for the size field:

@Schema(description = "available sizes")
public enum Size {
    SMALL, MEDIUM, LARGE;
}

public class TShirt {
    @Schema(description = "T-Shirt size")
    public Size size;
}

whereas this leads to no allOf and a single description because the Size and the size descriptions are identical:

@Schema(description = "available sizes")
public enum Size {
    SMALL, MEDIUM, LARGE;
}

public class TShirt {
    @Schema(description = "available sizes")
    public Size size;
}

In more complex cases with inheritance, it can be quickly hard to reason about what you are going to get and to provide consistency across a schema. Imagine the case where the enum is exposed in 2 different fields that don't have the same value, then you get 2 different results. Across large APIs (100+ classes) this gets difficult to reason about fast.

I am migrating from Jackson's deprecated module schema and this seemed to take a "closest one wins" approach. In the first example, the description would have been "T-Shirt size" for example.

I can't see a way to get this behavior using the current API. I can strip my nested classes/enums of having class level annotations but this doesn't work if I have a class that is exposed at the root level and also nested in a different context. It is also brittle because as soon as a dev adds one back in, but only with a different value, will it lead to an unexpected allOf.

I have worked around this for now by post-processing the schema as follows. This only works for me because I know I don't have any "genuine" allOf in my schema but this would need to be more complex if I did.

    private static void flattenAllOf(ObjectNode schema) {
        flattenAllOfInChildren(node);

        JsonNode allOf = node.get("allOf");
        if (allOf == null || !allOf.isArray()) return;

        node.remove("allOf");
        allOf.forEach(entry -> {
            if (entry instanceof ObjectNode entryObj) {
                entryObj.properties().forEach(e -> {
                    node.set(e.getKey(), e.getValue());
                });
            }
        });
    }

    private static void flattenAllOfInChildren(ObjectNode node) {
        node.properties().forEach(entry -> {
            if (entry.getValue() instanceof ObjectNode child) {
                flattenAllOf(child);
            } else if (entry.getValue() instanceof ArrayNode array) {
                array.forEach(item -> { if (item instanceof ObjectNode o) flattenAllOf(o); });
            }
        });
    }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions