[BUG][Kotlin] When using additional properties as an array it doesn't specify List's type correctly
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
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
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.
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
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]