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

8.4 AlternateTypeProvider #315

Closed
trycatchblock opened this issue May 28, 2014 · 17 comments
Closed

8.4 AlternateTypeProvider #315

trycatchblock opened this issue May 28, 2014 · 17 comments
Labels

Comments

@trycatchblock
Copy link

Hi,

I've added the example code from the readme to swaggerGlobalSettings() in my swagger configuration class. However, I am still seeing LocalDate in my models. Any ideas?

AlternateTypeProvider alternateTypeProvider = new AlternateTypeProvider();
TypeResolver typeResolver = new TypeResolver();

alternateTypeProvider.addRule(newRule(typeResolver.resolve(LocalDate.class), typeResolver.resolve(Date.class)));

swaggerGlobalSettings.setAlternateTypeProvider(alternateTypeProvider);


return swaggerGlobalSettings;

Thanks!

@dilipkrish
Copy link
Member

@trycatchblock Its possible this may be a bug. Do you know if there are some LocalDate properties on any of your beans that are nested deeper within the model?

@trycatchblock
Copy link
Author

Not deep. Probably one level deeper. Request has an object which contains LocalDate

@dilipkrish
Copy link
Member

@trycatchblock do you have an example model that I could use to demostrate the problem?

@trycatchblock
Copy link
Author

Sure, this should help you out. The request has an object "Business" which contains a localdate so it's not too deep within th emodel.

    "Request" : {
        "id" : "Request",
        "description" : "",
        "extends" : "",
        "properties" : {
            "owner" : {
                "$ref" : "Owner"
            },
            "business" : {
                "$ref" : "Business"
            }
        }
    }
    "Business" : {
        "id" : "Business",
        "description" : "",
        "extends" : "",
        "properties" : {
            "address" : {
                "$ref" : "Address"
            },
            "name" : {
                "type" : "string"
            },
            "businessInceptionDate" : {
                "$ref" : "LocalDate"
            }
        }
    }

@trycatchblock
Copy link
Author

Also tried overriding with fasterxml's local date serializer and deserializers in config. No luck.

public ObjectMapper objectMapper() {
ObjectMapper result = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addSerializer(LocalDate.class, new LocalDateSerializer());
sm.addDeserializer(LocalDate.class, new LocalDateDeserializer());
result.registerModule(sm);
return result;
}

@jtheuer
Copy link

jtheuer commented Jun 3, 2014

I'm also having issues with this, one question to understand the issue: Does Swagger support custom de/serializers of jackson? If so, how?

@dilipkrish
Copy link
Member

@jtheuer It absolutely does. Whatever customizations you do with the models via jackson attributes should absolutely be respected. Once you have access to the ObjectMapper that is being used by your application (there should only be one for your application, the swagger stuff should be using the same ObjectMapper that is being used by the application itself), you can customize it just like you normally would.

@jtheuer
Copy link

jtheuer commented Jun 4, 2014

I might use the "wrong" way of custom serialization because It don't think that you can infer the document structure from this, should I use something else? I doubt that you can infer from this that the result is an {"lon":12.0,"lat":54.0} object

    public class JtsWgs84Serializer extends StdSerializer<Geometry> {

    public JtsWgs84Serializer() {
        super(Geometry.class);
    }

    @Override
    public void serialize(Geometry value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
        if(value instanceof Point) {
            Point pt = (Point) value;
            Coordinate c = pt.getCoordinate();
            serialize(c.y, c.x, c.z, jgen);
        } else if(value instanceof LineString) {
            CoordinateSequence sequence = ((LineString) value).getCoordinateSequence();
            for(int i = 0; i < sequence.size(); i++) {
                jgen.writeStartArray();
                serialize(sequence.getY(i), sequence.getX(i), sequence.getOrdinate(i, CoordinateSequence.Z), jgen);
                jgen.writeEndArray();
            }
        }
    }

    private void serialize(double lat, double lon, double alt, JsonGenerator jgen) throws IOException {
        jgen.writeStartObject();
        jgen.writeNumberField("lat", lat);
        jgen.writeNumberField("lng", lon);
        if(Double.isNaN(alt) == false) {
            jgen.writeNumberField("alt", alt);
        }
        jgen.writeEndObject();
    }
}

@dilipkrish
Copy link
Member

@jtheuer Not sure I follow. If I understand correctly, you want to serialize Geometry as a lat long? If thats the case what you'd do is set up a serializer, just like you have it, and in addition also setup an alternate rule to tell swagger to render the model schema of Geometry using an alternate type you define JtsWgs84 which looks like this

class JtsWgs84 {
     private double lat;
     private double lon;

     //getters and setters 
}

@trycatchblock From your previous example, your ObjectMapper method may not have worked as it may have been a different instance of the object mapper than the one that swagger is using.

@trycatchblock
Copy link
Author

Guessing the first part was directed to @jtheuer.

That method is using the bean definition in my SwaggerConfig. It says in the readme that I can customize my ObjectMapper and have swagger pick up the bean from there. Is this wrong?

https://github.com/martypitt/swagger-springmvc/blob/c92e3fc9e2b6d292b13a28f652042bf315ac97b5/readme.md

@dilipkrish
Copy link
Member

@trycatchblock Its correct... just a word of caution that if you have multiple object mappers there is a possibility that it may not have worked. We had a few bug fixes related to that so I was just making sure.

@trycatchblock
Copy link
Author

I was unable to find in the code where it was using objectMapper's readValue and writeValue.

I also noticed from swagger-core that custom deserializer/serializers are not supported via this issue:
swagger-api/swagger-core#481

Just to make sure I'm not going down the wrong path, can you confirm that custom serialization/deserialization via objectMapper is actually NOT SUPPORTED?

If it is, can you point me to the piece of code where objectmapper is being used to read/write?

Thanks!

@dilipkrish
Copy link
Member

@trycatchblock I'm not very sure if this is the answer to your question, but here goes. There are two aspects to this.

  • First, what you render to the swagger ui so that it can intelligently show the right widgets
  • Second, what you actually use in your application to serialize and deserialize input via the swagger ui.

So given that, lets take a can example of LocalDate. Lets say we decide that we want to represent that date as a ISO8601 format (yyyy-MM-dd) string. In order to do that you need to do two things

  • Tell swagger that you intended to use LocalDate as a string and that you (as in the application) is responsible for the conversion of that string to a LocalDate. You'd do this by providing an alternate type rule that maps a LocalDate to a String
  • Secondly actually create/use the serializers and deserializers (in this case it would be applying the Joda Module to the object mapper or modify the serialization settings accordingly) to infer the right datatypes and parse the string.

Now while the swagger-core may not support this, this library totally supports keeping parity between the swagger representation and what the application uses. Hope that answers your question. If you have a specific example let me know.

@trycatchblock
Copy link
Author

hey, thanks for the detailed writeup above, but the objectmapper and the alternatetypeprovider is still not working for me.

I'm happy to debug it myself, but what I wanted to know was where exactly in the library is it using the serializer/deserializer registered in the module for objectwrapper?

The only class I see using objectwrapper is the DefaultModelPropertiesProvider which only uses getSerializationConfig and getDeserializationConfig in order to retrieve the properties/fields (to be used to create the [incorrect] models later).

I cannot find the actual serializing/deserializing methods (writeValue/readValue) being used anywhere.

@dilipkrish
Copy link
Member

@trycatchblock you'd never see the read and write, because thats not the responsibility of the library. If you wanted to register a custom serializer or deserializer you just have to configure the ObjectMapper that the library uses, just like you would when swagger was not in play.

Secondly; possibly why you're geting incorrect models; if you have a custom serializer/deserializer there is no way for swagger to know the shape of the model. So you'd have to provide an alternate type in swagger, or provide the object mapper with a Mixin to provide the shape information.

If you can create a fork with a failing test, of your expection, I could take a look at what the bug might be

@trycatchblock
Copy link
Author

Thanks for the above. I just debugged the library and it's not parsing beyond the top level of the request for alternate types so LocalDate is not converted over to string. I'll take a look at the mixin.

@dilipkrish
Copy link
Member

@trycatchblock yes theres a bug fix for that that should be coming up this weekend :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants