Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Broken ModelConverterContextImpl resolve method for hierarchical data structure #3512

Closed
Birthright50 opened this issue Apr 10, 2020 · 1 comment

Comments

@Birthright50
Copy link

I used latest swagger 2.1.2, spring doc 1.3.1 and spring boot mvc 2.2.6.

I have a method that returns data in a hierarchical structure:

@GetMapping(Routes.Item.External.VIEWS_STRUCTURE)
 public List<ViewStructureShared> getViewStructure(@Validated GetViewStructureRequest request) {
     return itemsService.getViewStructure(request.getViewName(), request.getLang());
}

ViewStructureShared model:

public class ViewStructureShared {
    private long id;
    private long viewId;
    private List<ViewStructureShared> children;
    private String title;
    private Long categoryId;
    private Long tagId;
    private short order;
    private boolean global;
}

Swagger generates right response for this endpoint:

        "responses": {
          "200": {
            "description": "default response",
            "content": {
              "*/*": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ViewStructureShared"
                  }
                }
              }
            }
          }
        }
...
      "ViewStructureShared": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "viewId": {
            "type": "integer",
            "format": "int64"
          },
          "children": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ViewStructureShared"
            }
          },
          "title": {
            "type": "string"
          },
          "categoryId": {
            "type": "integer",
            "format": "int64"
          },
          "tagId": {
            "type": "integer",
            "format": "int64"
          },
          "order": {
            "type": "integer",
            "format": "int32"
          },
          "global": {
            "type": "boolean"
          }
        }
      }

But, if response wrapper appears, then everything changes.

    @GetMapping(Routes.Item.External.VIEWS_STRUCTURE)
    public ActionResult<List<ViewStructureShared>> getViewStructure(@Validated GetViewStructureRequest request) {
        return ActionResult.ok(itemsService.getViewStructure(request.getViewName(), request.getLang()));
    }

ActionResult response wrapper structure:

public class ActionResult<T> {

    protected T value;
    protected boolean success;
    protected String errorCode;
    protected String message;
}

then swagger generates this schema model:

      "ViewStructureShared": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "viewId": {
            "type": "integer",
            "format": "int64"
          },
          "title": {
            "type": "string"
          },
          "categoryId": {
            "type": "integer",
            "format": "int64"
          },
          "tagId": {
            "type": "integer",
            "format": "int64"
          },
          "order": {
            "type": "integer",
            "format": "int32"
          },
          "global": {
            "type": "boolean"
          }
        }
      }

no more children field in schema.

Why is everything fine in the first case, but everything breaks in the second? I tried to figure it out.

The problem is here.

    public Schema resolve(AnnotatedType type) {
        if (processedTypes.contains(type)) {
            return modelByType.get(type);
        } else {
            processedTypes.add(type);
        }

In first case we have two different annotated types (by AnnotatedType.getType() method):
sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl for returned object in controller and com.fasterxml.jackson.databind.type.CollectionType for List<ViewStructureShared> in ViewStructureShared.

But in second we have both identical (by equals method) com.fasterxml.jackson.databind.type.CollectionType,
therefore, this code omits this processing and no children anymore, as I understand it, this is done to protect against recursion. But specifically, this example should work.

There is one hack that will help children - specify a different type of collection, for example Collection instead of List:

    @GetMapping(Routes.Item.External.VIEWS_STRUCTURE)
    public ActionResult<Collection<ViewStructureShared>> getViewStructure(@Validated GetViewStructureRequest request) {
        return ActionResult.ok(itemsService.getViewStructure(request.getViewName(), request.getLang()));
    }

but this is not the right decision

@webron
Copy link
Contributor

webron commented Apr 20, 2020

See #3511.

@webron webron closed this as completed Apr 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants