OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
22.02k stars 6.61k forks source link

[BUG][Kotlin] When using additional properties as an array it doesn't specify List's type correctly #12400

Open sky-andremartins opened 2 years ago

sky-andremartins commented 2 years ago

Bug Report Checklist

Description

When trying to specify additional properties to handle dynamic keys which will always be an array of a certain type, the generator doesn't specify the List's type correctly.

For the declaration below it generates model as following:

data class Item(val id: String, val type: String)

class Responses (

) : kotlin.collections.HashMap<String, kotlin.collections.List>()

Which is incorrect as the expected one would be something like this:

data class Item(val id: String, val type: String)

class Responses (

) : kotlin.collections.HashMap<String, kotlin.collections.List<Item>()
openapi-generator version

Gradle plugin 5.4.0

OpenAPI declaration file content or url
Responses:
  type: object
  additionalProperties:
    type: array
    items:
      $ref: '#/components/schemas/Item'

Item:
  type: object
  properties:
    id:
      type: string
    type:
      type: string
Generation Details

Gradle generator task config

group = "dependencies"
generatorName = "kotlin"
generateAliasAsModel = false
configOptions = [
        dateLibrary                      : "java8",
        library                          : "multiplatform",
        sourceFolder                     : "source/kotlin",
        sortModelPropertiesByRequiredFlag: "false",
        sortParamsByRequiredFlag         : "false",
        enumPropertyNaming               : "UPPERCASE",
        serializationLibrary             : "jackson",
        interfaceOnly                    : "true",
        modelMutable                     : "false"
]
Steps to reproduce

Generate the models using the above described spec

Related issues/PRs

NA

Suggest a fix

NA

benjaminsaljooghi commented 2 years ago

I have the same problem so I did some debugging. These are my initial thoughts.

The relevant code is DefaultCodegen.toInstantiationType which does not recursively build the generic arguments as is done in AbstractKotlinCodegen.getTypeDeclaration.

A simple code fix for this would be (see inline comments for added parts):

    public String toInstantiationType(Schema schema) {
        if (ModelUtils.isMapSchema(schema)) {
            Schema additionalProperties = getAdditionalProperties(schema);

            String inner;
            if (ModelUtils.isArraySchema(additionalProperties)) {
                // recursive step to get generic arguments for the List
                inner = toInstantiationType(additionalProperties);
            } else {
                // inner type is not a List, no need for recursion
                inner = getSchemaType(additionalProperties);
            }
            return instantiationTypes.get("map") + "<String, " + inner + ">";
        } else if (ModelUtils.isArraySchema(schema)) {
            ArraySchema arraySchema = (ArraySchema) schema;
            String inner = getSchemaType(getSchemaItems(arraySchema));
            String parentType;
            if (ModelUtils.isSet(schema)) {
                parentType = "set";
            } else {
                parentType = "array";
            }
            return instantiationTypes.get(parentType) + "<" + inner + ">";
        } else {
            return null;
        }
    }

It's not flexible for other inner types but it solves this specific case and generates perfectly valid code like this:

data class Item(val id: String, val type: String)

class Responses (

) : kotlin.collections.HashMap<String, kotlin.collections.List<Item>>()

But I have trouble using it in my app because I cannot cast a HashMap to the generated model class Responses:

class java.util.HashMap cannot be cast to class com.etc.Responses (java.util.HashMap is in module java.base of loader 'bootstrap'; com.etc.Responses is in unnamed module of loader 'app')

(edited to use your example)

A fundamental problem here is that generating the alias using inheritance requires instantiation types (HashMap, ArrayList) (which is what the code generator does) because otherwise the generated model classes would have to implement the interface types (Map, List).

But anyway I don't fully understand what's going on with the loaders, so I am wondering if it would be better if the "generate alias as model" option simply generated:

data class Item(val id: String, val type: String)

typealias Responses = Map<String, List<Item>>

Advantages of this approach:

Disadvantages:

Stiuil06 commented 2 years ago

Issue still exists in latest 6.2.0 version. It is problematic because I needed use .openapi-generator-ignore file and ignore some types during generating and later build it manually.

coopstah13 commented 1 year ago

this affects jax-rs spec as well, so I'm guessing the problem is not limited to kotlin, so please don't just fix it there

julian-perge commented 8 months ago

Giving my +1 to this. I have an API endpoint that returns a Map<String, List<MyObject>>, and unfortunately the python code generated ends up saying Dict[str, object]