OpenAPITools / OpenAPITools/openapi-generator

[BUG][JAVA] Generated model with generics doesn't compile with escaped code

Open
#15,852 3 comments 4 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

Description

In my OpenAPI specification I have an object:

    WithUnknownData:
      type: object
      title: WithUnknownData
      x-internal: true
      properties:
        data:
          oneOf:
            - type: object
            - type: array
              items: {}

I configured the generator to generate a Java client:

{
        "generatorName": "java",
        "inputSpec": "docs/openapi.yaml",
        "output": "generated-clients/java"
      }

When I look at the generated model, I get:

public class WithUnknownData {
  public static final String SERIALIZED_NAME_DATA = "data";
  @SerializedName(SERIALIZED_NAME_DATA)
  private WithUnknownDataData data;

  public WithUnknownData() {
  }

  public WithUnknownData data(WithUnknownDataData data) {
    
    this.data = data;
    return this;
  }
/* And more code*/
}

When I drill down to WithUnknownDataData I see it does not compile, because some of the generated code is escaped:

public static class CustomTypeAdapterFactory implements TypeAdapterFactory {
        @SuppressWarnings("unchecked")
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            if (!WithUnknownDataData.class.isAssignableFrom(type.getRawType())) {
                return null; // this class only serializes 'WithUnknownDataData' and its subtypes
            }
            final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
            final TypeAdapter<List&lt;Object&gt;> adapterList&lt;Object&gt; = gson.getDelegateAdapter(this, TypeToken.get(List&lt;Object&gt;.class));
            final TypeAdapter<Object> adapterObject = gson.getDelegateAdapter(this, TypeToken.get(Object.class));

            return (TypeAdapter<T>) new TypeAdapter<WithUnknownDataData>() {
                @Override
                public void write(JsonWriter out, WithUnknownDataData value) throws IOException {
                    if (value == null || value.getActualInstance() == null) {
                        elementAdapter.write(out, null);
                        return;
                    }

                    // check if the actual instance is of the type `List&lt;Object&gt;`
                    if (value.getActualInstance() instanceof List&lt;Object&gt;) {
                        JsonObject obj = adapterList&lt;Object&gt;.toJsonTree((List&lt;Object&gt;)value.getActualInstance()).getAsJsonObject();
                        elementAdapter.write(out, obj);
                        return;
                    }

                    // check if the actual instance is of the type `Object`
                    if (value.getActualInstance() instanceof Object) {
                        JsonObject obj = adapterObject.toJsonTree((Object)value.getActualInstance()).getAsJsonObject();
                        elementAdapter.write(out, obj);
                        return;
                    }

                    throw new IOException("Failed to serialize as the type doesn't match oneOf schemas: List<Object>, Object");
                }

                @Override
                public WithUnknownDataData read(JsonReader in) throws IOException {
                    Object deserialized = null;
                    JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject();

                    int match = 0;
                    ArrayList<String> errorMessages = new ArrayList<>();
                    TypeAdapter actualAdapter = elementAdapter;

                    // deserialize List<Object>
                    try {
                        // validate the JSON object to see if any exception is thrown
                        List&lt;Object&gt;.validateJsonObject(jsonObject);
                        actualAdapter = adapterList&lt;Object&gt;;
                        match++;
                        log.log(Level.FINER, "Input data matches schema 'List<Object>'");
                    } catch (Exception e) {
                        // deserialization failed, continue
                        errorMessages.add(String.format("Deserialization for List<Object> failed with `%s`.", e.getMessage()));
                        log.log(Level.FINER, "Input data does not match schema 'List<Object>'", e);
                    }

                    // deserialize Object
                    try {
                        // validate the JSON object to see if any exception is thrown
                        Object.validateJsonObject(jsonObject);
                        actualAdapter = adapterObject;
                        match++;
                        log.log(Level.FINER, "Input data matches schema 'Object'");
                    } catch (Exception e) {
                        // deserialization failed, continue
                        errorMessages.add(String.format("Deserialization for Object failed with `%s`.", e.getMessage()));
                        log.log(Level.FINER, "Input data does not match schema 'Object'", e);
                    }

                    if (match == 1) {
                        WithUnknownDataData ret = new WithUnknownDataData();
                        ret.setActualInstance(actualAdapter.fromJsonTree(jsonObject));
                        return ret;
                    }

                    throw new IOException(String.format("Failed deserialization for WithUnknownDataData: %d classes match result, expected 1. Detailed failure message for oneOf schemas: %s. JSON: %s", match, errorMessages, jsonObject.toString()));
                }
            }.nullSafe();
        }
    }

As you can see some the code looks like this:
final TypeAdapter<List**<Object>> adapterList<Object>**

I tried all sorts of additional properties hoping they would solve this issue, but nothing worked.

openapi-generator version

6.6.0

OpenAPI declaration file content or url
    WithUnknownData:
      type: object
      title: WithUnknownData
      x-internal: true
      properties:
        data:
          oneOf:
            - type: object
            - type: array
              items: {}
Generation Details

{
"generatorName": "java",
"inputSpec": "docs/openapi.yaml",
"output": "generated-clients/java"
}

Steps to reproduce
Related issues/PRs
Suggest a fix

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the issue with the supplied OpenAPI schema and Java generator configuration from version 6.6.0. Inspect the generated WithUnknownDataData source and the Java generation path that produces its escaped generic types. Done means the generated Java model compiles with the affected schema.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, openapi
Domain
api, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.