Handling recursive models with springdoc

Viewed 751

So I have a nested model structure that looks something like this:

public class TagArea {
    private List<TagGroup> groups;
    
    ... constructors, getters, setters, etc.
}


public class TagGroup {
    private String tagName;
    private List<TagGroup> subgroups;
    
    ... constructors, getters, setters, etc.
}

And a controller that is returning said model. However, the schema portion of the swagger.json generated looks like this:

{ "components": 
  { "schemas": {
    "TaxonomyClientMetadata": {
      "type": "object", "properties": {
        "tagGroups": {"type": "array", "items": {"$ref": "#/components/schemas/TagGroup"}}
      }
    }, 
    "TagGroup": {
      "type": "object", "properties": {
        "tagName": {"type": "string"}
      }
    }
  }
}

An entire property just... gone. I would expect the TagGroup object to look more like this:

"TagGroup": {
  "type": "object", "properties": {
    "tagName": {"type": "string"},
    "subgroups": {"type": "array", "items": {"$ref": "#/components/schemas/TagGroup"}}
  }
}

Indeed, I'm currently migrating from Springfox pre-3.0 (so Swagger 2) to Springdoc with OAS3, and this is the behaviour I was previously observing with Springfox, and would like support for it once again.

It's possible this is related to the shift to OAS3, but I couldn't find any resources that suggest the spec no longer allows recursive types. I'm also currently not on the latest of version of springdoc (using 1.4.8) due to a dependency on a slightly older version of springboot.

Digging through the code a bit, it looks like the ModelConverterContextImpl keeps a cache of processedTypes (scoped to the current schema I think), and then adds them to a mapping cache when the type is finished being processed. In this scenario, we start processing the List<TagGroup> (and so add List<TagGroup> to the processedTypes cache), which requires recursively processing the schemas inside. When processing the TagGroup, we find another List<TagGroup>, we see that it's in the cache of processedTypes, then fetch it from the map cache. But because we never finished processing the original List<TagGroup>, it hasn't been added to the map cache. As such, null is returned, and the property gets ignored in ModelResolver.

1 Answers

You should add @JsonBackReference jackson annotation, for circular references resolution: springdoc-openapi is built on top of swagger-core, which is based on Jackson library for resolving models schema.

If you use the following definition, it will work as expected:

@Data
public class TagGroup {
    private String tagGroupName;

    @JsonBackReference
    private List<TagGroup> subgroups;

}
Related