swagger-api / swagger-core

Examples and server integrations for generating the Swagger API Specification, which enables easy access to your REST API
http://swagger.io
Apache License 2.0
7.39k stars 2.18k forks source link

MediaType Example doesn't serialize Json output as expected #3827

Open rs-renato opened 3 years ago

rs-renato commented 3 years ago

I'm trying to write some default responses to my API, and I have a problem to show my json as MediaType Example on Swagger-UI.

In the codes bellow, the object MensagemRetorno is just a POJO, without any customization; and the ObjectMapper has customization of UpperCamelCase strategy as well.

I've tried two alternatives to configure the example:

1. Writing the MediaType Example as plain string:

Schema<MensagemRetorno> mensagemRetornoSchema = new Schema<>();
mensagemRetornoSchema.setName("MensagemRetorno");
mensagemRetornoSchema.set$ref("#/components/schemas/MensagemRetorno");

Content content = new Content()
  .addMediaType(MediaType.APPLICATION_JSON_VALUE,
      new io.swagger.v3.oas.models.media.MediaType()
        .schema(mensagemRetornoSchema)
        .example(objectMapper.writeValueAsString(mensagemRetorno)));

ApiResponse response = new ApiResponse()
    .description("some description")
    .content(content));

In this case, the problem is that plain string is serialized as a json string escaped:

Screen Shot 2020-12-07 at 10 40 48 AM

I've tried a lot of customizations on object mapper without any success.

2. Writing the MediaType Example as POJO:

Content content = new Content()
  .addMediaType(MediaType.APPLICATION_JSON_VALUE,
      new io.swagger.v3.oas.models.media.MediaType()
        .schema(mensagemRetornoSchema)
        .example(mensagemRetorno));

In this case, the problem is that the serialized json doesn't respect the object mapper strategy UpperCamelCase:

Screen Shot 2020-12-07 at 10 53 20 AM

The curious is, if I don't provide any MediaType Example, the json output example (default, auto generated), is serialized as expected:

Content content = new Content()
  .addMediaType(MediaType.APPLICATION_JSON_VALUE,
      new io.swagger.v3.oas.models.media.MediaType()
        .schema(mensagemRetornoSchema));
//     .example(mensagemRetorno));
Screen Shot 2020-12-07 at 11 00 22 AM

The biggest issue in this last case is that the media type example itself isn't generated on /v3/api-docs page:

Screen Shot 2020-12-07 at 11 16 02 AM

Any idea how to customize and provide a MediaType Example with a custom UpperCamelCase strategy?

I am using:

springdoc-openapi-ui:1.5.0
io.swagger.core.v3:swagger-* 2.1.5 (transitive)
org.webjars:swagger-ui 3.36.1 (transitive)
frantuma commented 3 years ago

This is related to swagger-model POJO serialization, as swagger-core uses it's own customized ObjectMapper (configured here) for the whole specification, therefore including any "free form" example.

To define a naming strategy limited e.g. to the example contents (as we cannot apply e.g. an UpperCase strategy to the whole doc, as it would break the OpenAPI specifications for OpenAPI properties - e.g. schema and not 'Schema), you would need to use e.g a @JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class) annotation at MensagemRetorno class level, or provide a custom naming strategy implementing a specific strategy e.g. based on package name, or use MixIns, JsonSerializer or other Jackson provided approaches for custom property naming/serialization.

If this affects multiple POJOs in your code base, possibly going for the custom naming strategy would be the best approach.

Please close ticket if this answers your question

rs-renato commented 3 years ago

@frantuma Unfortunately I've already done a lot of tries to customize this output, including POJO personalization with @JsonNaming and Mixins (with my custom ObjectMapper.

Sounds impossible to writes a UpperCamelCase in Schema Examples, because no matter what I've set in io.swagger.v3.oas.models.media.MediaType().example(object)) my customizations doesn't reflect on output.

Do you know if this MediaType Example is written by the internal ObjectMapper, I couldn't catch this point. You meant "free form" example, so now, I am in doubt. If what chunk is written by internals ObjectMapper, is that correct? Since that the Example is provided by us (SPI)

frantuma commented 3 years ago

The following seems to work ok with your example and @JsonNaming, but would also work with Mixins or strategy, the mapper to use is the Swagger provided one, or a similarly customized one.

...
...
        Schema<MensagemRetorno> mensagemRetornoSchema = new Schema<>();
        MensagemRetorno msg = new MensagemRetorno();
        msg.categoria = "ALERTA";
        msg.codigo = 400;
        msg.descricao = "Bad request";
        msg.detalhes = new String[]{"Descrição do(s) erro(s) de sistema, validacoes on mensagens informaivas relacionadas a 'Bad Request'"};
        mensagemRetornoSchema.setName("MensagemRetorno");
        mensagemRetornoSchema.set$ref("#/components/schemas/MensagemRetorno");

        Content content = new Content()
                .addMediaType(javax.ws.rs.core.MediaType.APPLICATION_JSON,
                        new io.swagger.v3.oas.models.media.MediaType()
                                .schema(mensagemRetornoSchema)
                                .example(msg));

        ApiResponse response = new ApiResponse()
                .description("some description")
                .content(content);
        ObjectMapper mapper = Yaml.mapper().copy();
        // you can use a custom naming strategy or mixins insted, e.g. mapper.setPropertyNamingStrategy(new UpperCaseNaming());
        System.err.println(mapper.writer(new DefaultPrettyPrinter()).writeValueAsString(response));

...
...
    @JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
    static class MensagemRetorno {
        public String categoria;
        public Integer codigo;
        public String descricao;
        public String[] detalhes = new String[]{};
    }
rs-renato commented 3 years ago

@frantuma thanks for yout reply! So, yes, in that case works in parts. I had similar configuration, but setting example as plain string, I have the issue said on: 1. Writing the MediaType Example as plain string: And setting as object, the issue was said in 2. Writing the MediaType Example as POJO:.

In your test case, you've tested the custom ObjectMapper as well, but I wonder that isn't the real test, since that the example isn't written by the custom ObjectMapper at all. As you seems to advice, sounds by written by internals and I am not sure, if that behavior is correct.

rs-renato commented 3 years ago

@frantuma thanks for yout reply! So, yes, in that case works in parts. I had similar configuration, but setting example as plain string, I have the issue said on: 1. Writing the MediaType Example as plain string: And setting as object, the issue was said in 2. Writing the MediaType Example as POJO:.

In your test case, you've tested the custom ObjectMapper as well, but I wonder that isn't the real test, since that the example isn't written by the custom ObjectMapper at all. As you seems to advice, sounds by written by internals and I am not sure, if that behavior is correct.

I am sorry, I just saw, the DefaultPrettyPrinter right now, I will run some tests cases with this 'detail' and I'll comeback here with results

rs-renato commented 3 years ago

@frantuma In fact, the configuration said in mapper.writer(new DefaultPrettyPrinter()).writeValueAsString(response) there is no new effect in output serialization in swagger output.

We can observe that the problem isn't related to custom object mapper at all, it's related about the impossibility the customize the output of mediaType example, based on custom POJO or custom String.

In short:

  1. When io.swagger.v3.oas.models.media.MediaType().example(STRING) is set, the plain string (as json) is serialized in escaped form;
  2. When io.swagger.v3.oas.models.media.MediaType().example(POJO) is set, no matter how customizations was done by custom object mapper, mixins or jackson's annotations;

Question: Is this behavior correct? I meant, if I want a customization of examples (eg. which relies on naming strategy), shouldn't I be able to customize them as I wish?

If you wish, I can provide a working application to illustrate this discussion.