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

Serialisation of OpenAPI is not stable #2828

Closed
frantuma opened this issue Jun 1, 2018 · 6 comments
Closed

Serialisation of OpenAPI is not stable #2828

frantuma opened this issue Jun 1, 2018 · 6 comments

Comments

@frantuma
Copy link
Member

frantuma commented Jun 1, 2018

moved from swagger-api/swagger-parser/issues/728 reported by @jmini

We have observed a strange behaviour with the Serialisation of an OpenAPI instance with Yaml.mapper().writeValueAsString(openAPI) produces different results:

--- a/src/main/resources/openapi.yaml
+++ b/src/main/resources/openapi.yaml
@@ -97,11 +97,11 @@ paths:
           type: array
           items:
             type: string
-            default: available
             enum:
             - available
             - pending
             - sold
+            default: available
       responses:
         200:
           description: successful operation
@@ -678,20 +678,20 @@ paths:
           type: array
           items:
             type: string
-            default: $
             enum:
             - '>'
             - $
+            default: $
       - name: enum_header_string
         in: header
         description: Header parameter enum test (string)
         schema:
           type: string
-          default: -efg
           enum:
           - _abc
           - -efg
           - (xyz)
+          default: -efg
       - name: enum_query_string_array
         in: query
         description: Query parameter enum test (string array)
@@ -701,20 +701,20 @@ paths:
           type: array
           items:
             type: string
-            default: $
             enum:
             - '>'
             - $
+            default: $
       - name: enum_query_string
         in: query
         description: Query parameter enum test (string)
         schema:
           type: string
-          default: -efg
           enum:
           - _abc
           - -efg
           - (xyz)
+          default: -efg
       - name: enum_query_integer
         in: query
         description: Query parameter enum test (double)
@@ -743,18 +743,18 @@ paths:
                   description: Form parameter enum test (string array)
                   items:
                     type: string
-                    default: $
                     enum:
                     - '>'
                     - $
+                    default: $
                 enum_form_string:
                   type: string
                   description: Form parameter enum test (string)
-                  default: -efg
                   enum:
                   - _abc
                   - -efg
                   - (xyz)
+                  default: -efg
       responses:
         400:
           description: Invalid request
@@ -1214,11 +1214,11 @@ components:
         name: Name
     EnumClass:
       type: string
-      default: -efg
       enum:
       - _abc
       - -efg
       - (xyz)
+      default: -efg
     List:
       type: object
       properties:

From a semantic point of view, the content is the same. But my expectation is to get the content always serialised the same way.

I did not understood what is producing the difference.
Maybe it depends from the JVM.

In my opinion io.swagger.v3.core.jackson.SchemaSerializer could and should be improved. It seems to me that the defaultSerializer is not working as expected.

I am open for suggestions.

@jmini
Copy link

jmini commented Jun 18, 2018

Work around until this is solved:

I am using MapperFeature.SORT_PROPERTIES_ALPHABETICALLY feature like this:

 Yaml.mapper()
    .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
    .writeValueAsString(openAPI);

And because this is not suitable for the OpenAPI instance (the root object) itself, an OpenAPISerializer is registered (to force order for the OpenAPI serialization)

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import io.swagger.v3.oas.models.OpenAPI;

import java.io.IOException;
import java.util.Map.Entry;

public class OpenAPISerializer extends JsonSerializer<OpenAPI> {

    @Override
    public void serialize(OpenAPI value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (value != null) {
            gen.writeStartObject();
            gen.writeStringField("openapi", value.getOpenapi());
            if(value.getInfo() != null) {
                gen.writeObjectField("info", value.getInfo());
            }
            if(value.getExternalDocs() != null) {
                gen.writeObjectField("externalDocs", value.getExternalDocs());
            }
            if(value.getServers() != null) {
                gen.writeObjectField("servers", value.getServers());
            }
            if(value.getSecurity() != null) {
                gen.writeObjectField("security", value.getSecurity());
            }
            if(value.getTags() != null) {
                gen.writeObjectField("tags", value.getTags());
            }
            if(value.getPaths() != null) {
                gen.writeObjectField("paths", value.getPaths());
            }
            if(value.getComponents() != null) {
                gen.writeObjectField("components", value.getComponents());
            }
            if(value.getExtensions() != null) {
                for (Entry<String, Object> e : value.getExtensions().entrySet()) {
                    gen.writeObjectField(e.getKey(), e.getValue());
                }
            }
            gen.writeEndObject();
        }
    }
}

Like this:

SimpleModule module = new SimpleModule("OpenAPIModule");
module.addSerializer(OpenAPI.class, new OpenAPISerializer());
   
 Yaml.mapper()
    .registerModule(module)
    .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
    .writeValueAsString(openAPI);

I think a solution for stable serialization should be provided in the core.

Any feedback?

@masc3d
Copy link

masc3d commented Jun 3, 2019

MapperFeature.SORT_PROPERTIES_ALPHABETICALLY does not work for some cases, eg. parameter schema properties, which still appear in arbitrary order.

current result with sorting enabled:

  "schema" : {
    "type" : "string",
    "enum" : [ "gpx", "sql" ],
    "default" : "gpx"
  }

@masc3d
Copy link

masc3d commented Jun 6, 2019

turns out when using gradle plugin with objectMapperProcessor the callback is invoked, but applied options have no effect.

using a custom modelConverter / ModelResolver works.

@masc3d
Copy link

masc3d commented Jun 19, 2019

I think a solution for stable serialization should be provided in the core.

as long as the solution is not alpha sorting @jmini.

model structure / order is important and can't just be discarded.
there has to be a better way.

@jmini
Copy link

jmini commented Jun 27, 2019

FYI:
For serialization, I am now using https://openapitools.github.io/empoa/#serializer (gson or jackson) and because the MicroProfile-OpenAPI models are only interfaces, I need to bridge the swagger-core instance into the interfaces with: https://openapitools.github.io/empoa/#empoa-swagger-core

@frantuma
Copy link
Member Author

frantuma commented Dec 3, 2020

A solution has been implemented in #3740, see PR comment for details and usage scenarios. Closing this ticket, any further issues in this area should be reported in a new or not closed ticket.

@frantuma frantuma closed this as completed Dec 3, 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

3 participants