Questions about swagger-jersey-jaxrs

506 views
Skip to first unread message

jlist9

unread,
Apr 23, 2015, 9:51:06 PM4/23/15
to swagger-sw...@googlegroups.com
Hi all, I have a few questions from my initial experience with swagger-jersey-jaxrs. I'd appreciate any inputs/points.

1. swagger-ui shows parent classes of the model classes, but not all of them.

When swagger-ui renders the swagger.json served by the swagger-jersey-jaxrs servlet, under "Model" of each API, the direct model class and all its parent classes are show. Sometimes I see empty Iterator class. Something like this:

MyModel {
...
}
MyModelParent {
}
Class {
}
Iterator {
}

Note that things like Iterator and Class also show up here. But parent classes don't always show up for each API. For some API methods they do, for some other methods they don't. I haven't figured out the rules.

So it looks like the parent classes are not collapsed into the MyModel class which is the type that the API clients will see. And there is no discretion what class to include in the Java reflection process (unless I'm missing something which is very possible.) The API client is usually not interested in the class hierarchy of the back-end implementation. I wonder if this is a design decision to preserve the class hierarchy?

2. Does swagger-jersey-jaxrs support @XmlJavaTypeAdapter and other custom JSON serializer and deserializers?

3. Does swagger-jersey-jaxrs support custom model schema to override the generated version from Java reflection?

Your help is much appreciated!

Jack

Ron Ratovsky

unread,
Apr 24, 2015, 6:10:55 AM4/24/15
to swagger-sw...@googlegroups.com
Hi Jack,

From the question it seems you're using swagger-core 1.5.

In that version we have a tight integration with Jackson for serialization of models. Anything that Jackson supports out of the box should be supported here as well (annotations, customizations and so on). Keep in mind that we have our own mappers with which you may need to register your customizations - one in the Json class and one in the Yaml.

You can also write a custom converter like this one - https://github.com/swagger-api/swagger-core/blob/08d82b38b6bac69cfe6e929f24daabfe8e99f17c/modules/swagger-core/src/test/scala/com/wordnik/swagger/model/override/SamplePropertyConverter.java to completely manipulate the serialization if needed.

As for the first question - as said, that's controlled by Jackson and perhaps there's something off there. Without a concrete code sample, it would be difficult to pinpoint the source of the problem though.

Ron

--
You received this message because you are subscribed to the Google Groups "Swagger" group.
To unsubscribe from this group and stop receiving emails from it, send an email to swagger-swaggers...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
-----------------------------------------
http://swagger.io
https://twitter.com/SwaggerApi
-----------------------------------------

jlist9

unread,
Apr 28, 2015, 10:43:00 PM4/28/15
to swagger-sw...@googlegroups.com
Thanks Ron.

Our project uses Jersey, models are annotated with jaxb annotations and not jackson annotations. I wonder if this matters?

Also I'd like to confirm I'm doing it right about setting up the servlet. We already have a apiServlet that serves the API. It currently has all the resource classes as providers. To serve swagger.json, I added a new servlet (swaggerServlet) parallel to the current apiServlet, and added all resource classes as well as the ApiListingResource and SwaggerSerializer classes to providers. Is this understanding correct?

If my understanding is correct, I am wondering if the two servlets can potentially be combined. Right now the answer is no because ApiListingResource maps to path "/", which seems to cause a conflect. /swagger.json returns 404 when I just added the two swagger classes to the apiServlet. Other than this, is there any other reason why we need a new servlet just to serve swagger.json and swagger.yaml?

Back to my original question of how Models are generated, I created a sample project with a Model class (which has String name variable) extending a BaseModel calsss (which has String id and String type.) I then created two APIs, one returns List<Model>, one returns Model. 

    @ApiOperation(value = "Get models", notes = "Returns a model list")
    @ApiResponses(value = { @ApiResponse(code = 400, message = "Invalid request") })
    @GET
    @Path("")
    @Produces(MediaType.APPLICATION_JSON)
    public List<Model> getAll() {
        List<Model> models = Arrays.<Model>asList(new Model("myId1", "myType1", "myName1"), new Model("myId2", "myType2", "myName2"));
        return models;
    }

    @ApiOperation(value = "Get model", notes = "Returns a model object")
    @ApiResponses(value = { @ApiResponse(code = 400, message = "Invalid request") })
    @GET
    @Path("/1")
    @Produces(MediaType.APPLICATION_JSON)
    public Model getOne() {
        return new Model("myId", "myType", "myName");
    }


In the generated swagger.json, I see an issue with the List<Model> API:

  "paths": {
    "/model": {
      "get": {
        "tags": [
          "apimodel"
        ],
        "summary": "Get models",
        "description": "Returns a model list",
        "operationId": "getAll",
        "produces": [
          "application/json"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "successful operation",
            "schema": {
              "type": "array",
              "items": {
                "type": "object"
              }
            }
          },
          "400": {
            "description": "Invalid request"
          }
        }
      }
    },

Note the "schema" section which is an array of type "object", and not an array of Model.

For comparison, the /model/1 API that is supposed to return a single Model object does the right thing by referencing #/definitions/Model defined in the definitions section:
Model model {
  id (string): ID of the model,
  type (string): Type of the model,
  name (string, optional): Name of the model,
  description (string, optional)
}

I wonder what I'm missing that causes the Model to be generated correctly, but not List<Model>?

Thanks again!

Jack

Ron Ratovsky

unread,
Apr 29, 2015, 12:18:01 PM4/29/15
to swagger-sw...@googlegroups.com
I admit I don't follow your initialization process. I'd recommend following the guides https://github.com/swagger-api/swagger-core/wiki/Swagger-Core-Jersey-1.X-Project-Setup-1.5 or https://github.com/swagger-api/swagger-core/wiki/Swagger-Core-Jersey-2.X-Project-Setup-1.5 depending on the jersey version you use. It should be fairly straightforward.

As for the example above, which exact version of swagger-core do you use?

jlist9

unread,
Apr 29, 2015, 2:12:59 PM4/29/15
to swagger-sw...@googlegroups.com
Hi Ron,

Thanks for the reply again. Sorry for being unclear. I was trying to avoid copying&pasting large amount of code which will make the email long but I guess I need it to show what I did. So here is a more detailed version of what I did and the problem I am having.

Yes, I am using swagger-jersey-jaxrs 1.5.3-M1 and I was following your link. I ran into a question and a problem.

1. The question - one thing I wasn't so sure about is from the doc page: "You can set Jersey's ServletContainer as a servlet... To hook Swagger into your application, you need to add the some of Swagger's packages and classes."

I wasn't sure if this meant to create a new servlet (dedicated for serving swagger.json and swagger.yaml), or to add the swagger packages and classes to my existing servlet that's currently serving the APIs (e.g mapped to /api.) I tried adding the  packages and my model packages to my existing /api servlet, I got 404 on /api/swagger.json and /api/swagger.yaml. I then tried with a brand new servlet mapped to /swagger with the two swagger classes and my model packages and it served /swagger/swagger.json and /swagger/swagger.yaml fine. So my understanding at this point is that it requires a new dedicated servlet for serving swagger.json and swagger.yaml files?

The way I created the dedicated servlet is by creating a SwaggerApplication extends javax.ws.rs.core.Application, and return my model resource class and the two swagger classes in getClasses(). I also instantiate a BeanConfig in the constructor and set up my environment.

public class SwaggerApplication extends Application {
    public SwaggerApplication() {
        BeanConfig beanConfig = new BeanConfig();
        beanConfig.setVersion("1.0.2");
        beanConfig.setSchemes(new String[]{"http"});
        beanConfig.setHost("localhost:8080");
        beanConfig.setBasePath("/api");
        beanConfig.setResourcePackage("hello");
        beanConfig.setScan(true);
    }

    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> resources = new HashSet<Class<?>>();
        resources.add(ModelResource.class);
        resources.add(com.wordnik.swagger.jaxrs.listing.ApiListingResource.class);
        resources.add(com.wordnik.swagger.jaxrs.listing.SwaggerSerializers.class);
        return resources;
    }
}

Here's my Model class hierarchy (two classes):

public class Model extends BaseModel {
    private String name;
    private String description;
    
    public Model() {}
    public Model(String id, String type, String name) {
        super(id, type);
        this.name = name;
    }

    public String getName() { return name; }    
    public String getDescription() { return description; }
}

public class BaseModel {
    private String id;
    private String type;
    
    public BaseModel() {}
    public BaseModel(String id, String type) { this.id = id; this.type = type; }
    
    public String getId() { return id; }
    public String getType() { return type; }
}


I then created ModelResource class and added two API methods:

@Api(value = "/api/model", description = "Sample model")
@Provider()
@Component
@Path("/model")
public class ModelResource {
    @ApiOperation(value = "Get models", notes = "Returns a model list")
    @ApiResponses(value = { @ApiResponse(code = 400, message = "Invalid request") })
    @GET
    @Path("")
    @Produces(MediaType.APPLICATION_JSON)
    public List<Model> getAll() {
        List<Model> models = Arrays.<Model>asList(new Model("myId1", "myType1", "myName1"), new Model("myId2", "myType2", "myName2"));
        return models;
    }

    @ApiOperation(value = "Get model", notes = "Returns a model object")
    @ApiResponses(value = { @ApiResponse(code = 400, message = "Invalid request") })
    @GET
    @Path("/1")
    @Produces(MediaType.APPLICATION_JSON)
    public Model getOne() {
        return new Model("myId", "myType", "myName");
    }
}

2. The problem I'm having:

After I did all of the above (I'm using Spring boot), both REST API get methods work. And /swagger/swagger.json returns a schema. However, I notice that the /api/model method returns array of object without any typing information while the /api/model/1 method does the right thing.

Here's the schema for the problematic /api/model:

    schema:
            type: "array"
            items:
              type: "object"

Below is the complete swagger.yaml for your reference (I'm using .yaml and not .json to be less verbose):

---
swagger: "2.0"
info:
  version: "1.0.2"
host: "localhost:8080"
basePath: "/api"
tags:
- name: "apimodel"
schemes:
- "http"
paths:
  /model:
    get:
      tags:
      - "apimodel"
      summary: "Get models"
      description: "Returns a model list"
      operationId: "getAll"
      produces:
      - "application/json"
      parameters: []
      responses:
        200:
          description: "successful operation"
          schema:
            type: "array"
            items:
              type: "object"
        400:
          description: "Invalid request"
  /model/1:
    get:
      tags:
      - "apimodel"
      summary: "Get model"
      description: "Returns a model object"
      operationId: "getOne"
      produces:
      - "application/json"
      parameters: []
      responses:
        200:
          description: "successful operation"
          schema:
            $ref: "#/definitions/Model"
        400:
          description: "Invalid request"
  /model/string:
    get:
      tags:
      - "apimodel"
      summary: "Get string"
      description: "Returns a string"
      operationId: "string"
      produces:
      - "text/plain"
      parameters: []
      responses:
        200:
          description: "successful operation"
          schema:
            type: "string"
        400:
          description: "Invalid request"
definitions:
  Model:
    required:
    - "id"
    - "name"
    - "type"
    properties:
      id:
        type: "string"
      type:
        type: "string"
      name:
        type: "string"
      description:
        type: "string"


Thanks again for looking!

Jack

Ron Ratovsky

unread,
Apr 30, 2015, 10:04:42 AM4/30/15
to swagger-sw...@googlegroups.com
Let's try dealing with one problem at a time.

First, please upgrade your dependency to 1.5.1-M2. Yes, it's newer.

Second, you use Jersey in combination with Spring Boot? Which version of Jersey do you use? If another JAX-RS library, which one and what version?

Third, do you have a web.xml? If so, can you share it please?

As for the confusion regarding the servlet - there's a bit of confusion here with regards to how Jersey is configured. The explanation in the tutorial provides you different versions depending on how you configure your Jersey. There's no need to map anything specifically to swagger.json/yaml as the ApiListingResource provider exposes those endpoints.

jlist9

unread,
Apr 30, 2015, 7:41:42 PM4/30/15
to swagger-sw...@googlegroups.com
Thanks again for your reply. Let me answer your questions:

1. Ah. I did not know 1.5.1-M2 is newer than 1.5.3-M1 :) I updated it. Now the /api/swagger.json works which means I won't need a dedicated servlet just to serve swagger file. Sweet!

2. Yes, I'm using Spring Boot with Jersey 1.19. 

3. No. Configuration is done in code the Spring Boot way.

After I updated to swagger-jersey-jaxrs 1.5.1-M2, I still have the model problem I described in my last email. Anything else I should check?

Jack

Ron Ratovsky

unread,
May 3, 2015, 5:57:41 AM5/3/15
to swagger-sw...@googlegroups.com
Due to the fun aspect of type erasure in Java, the relevant information is not always available when using reflection.
Try changing the @ApiOperation of getAll() as such:

    @ApiOperation(value = "Get models", notes = "Returns a model list" response = Model.class, responseContainer = "List" )

jlist9

unread,
May 7, 2015, 6:34:04 PM5/7/15
to swagger-sw...@googlegroups.com
Thanks again Ron. Once I added response and responseContainer it seems to be doing the right thing now. Now I have a problem with returning a Page (for pagination.)

For List, I get this schema and model:

[
  {
    "id": "string",
    "type": "string",
    "name": "string",
    "description": "string"
  }
]

Inline Model [
Model
]
Model {
id (string),
type (string),
name (string),
description (string, optional)
}

The othere API which returns a Page object has this annotation:
@ApiOperation(value = "Get models", notes = "Returns a model page", response = Model.class, responseContainer = "Page")

The class Page looks like this:

public class Page<T> {
    private List<T> objects;
    public Page(List<T> objects) {
        this.objects = objects;
    }   
    public List<T> getObjecs() {
        return objects;
    }
}

I get this this schema and model:

{
  "id": "string",
  "type": "string",
  "name": "string",
  "description": "string"
}

Model {
id (string),
type (string),
name (string),
description (string, optional)
}

Note that the schema and model both are that of a Model class, not a Page class, and there is no Page wrapper around the model schema. But if I remove the response = Model.class, I get the following which doesn't include Model class's definition:

{
  "objecs": [
    {}
  ]
}

Page {
objecs (Array[Inline Model 1], optional)
}
Inline Model 1 {
}

I wonder how responseContainer is used, and what's the best way to generate schema and definition for Page classes?

Thanks,
Jack

Ron Ratovsky

unread,
May 9, 2015, 9:25:45 AM5/9/15
to swagger-sw...@googlegroups.com
responseContainer can only be list, set or map. It's not a general purpose class container.
As far as I know, we cannot support the use case you describe, again, due to type erasure.
Feel free to open an issue about it on swagger-core and we may be able to come up with a solution.
Reply all
Reply to author
Forward
0 new messages