openapi-generator
openapi-generator copied to clipboard
[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]