openapi-generator icon indicating copy to clipboard operation
openapi-generator copied to clipboard

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

Open sky-andremartins opened this issue 2 years ago • 2 comments

Bug Report Checklist

  • [x] Have you provided a full/minimal spec to reproduce the issue?
  • [x] Have you validated the input using an OpenAPI validator (example)?
  • [x] Have you tested with the latest master to confirm the issue still exists?
  • [x] Have you searched for related issues/PRs?
  • [x] What's the actual output vs expected output?
  • [ ] [Optional] Sponsorship to speed up the bug fix or feature request (example)
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

sky-andremartins avatar May 19 '22 15:05 sky-andremartins

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:

  • no inheritance
  • can use Kotlin interface classes instead of Java instantiation classes

Disadvantages:

  • typing is now less strict

benjaminsaljooghi avatar Jun 21 '22 00:06 benjaminsaljooghi

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.

Stiuil06 avatar Oct 10 '22 13:10 Stiuil06

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

coopstah13 avatar Oct 03 '23 16:10 coopstah13

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]

julian-perge avatar Mar 08 '24 12:03 julian-perge