I'm using the oneOf feature to define several possible schemas that can go into a request body property of my service. In the generated Java client code, the Java implementations of these schemas implement an interface, but when I send a request through, Jackson is trying to create an instance of the interface, instead of the concrete class.
<groupId>io.swagger.codegen.v3</groupId>
<artifactId>swagger-codegen-maven-plugin</artifactId>
<version>3.0.14</version>
schemas:
TestRequest:
description:
Test request
type:
object
required:
- criteria
properties:
criteria:
oneOf:
- $ref: '#/components/schemas/CriteriaA'
- $ref: '#/components/schemas/CriteriaB'
discriminator:
propertyName: type
mapping:
CriteriaA: '#/components/schemas/CriteriaA'
...
CriteriaA:
description: Criteria A
type: object
required:
- type
- query
properties:
type:
description: A description
type: string
enum:
- CriteriaA
query:
description: A query.
type: object
Maven plugin specified above used for generation.
The Java client code generated by swagger codegen looks like this:
Interface:
public interface OneOfTestRequestCriteria {}
Concrete class:
@Schema(description = "")
@javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.JavaClientCodegen", date = "2020-01-28T13:06:29.942Z[Europe/London]")
public class CriteriaA implements OneOfTestRequestCriteria {
@JsonAdapter(TypeEnum.Adapter.class)
public enum TypeEnum {
CriteriaA("CriteriaA");
private String value;
TypeEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
@Override
public String toString() {
return String.valueOf(value);
}
public static TypeEnum fromValue(String text) {
for (TypeEnum b : TypeEnum.values()) {
if (String.valueOf(b.value).equals(text)) {
return b;
}
}
return null;
}
public static class Adapter extends TypeAdapter<TypeEnum> {
@Override
public void write(final JsonWriter jsonWriter, final TypeEnum enumeration) throws IOException {
jsonWriter.value(enumeration.getValue());
}
@Override
public TypeEnum read(final JsonReader jsonReader) throws IOException {
String value = jsonReader.nextString();
return TypeEnum.fromValue(String.valueOf(value));
}
}
} @SerializedName("type")
private TypeEnum type = null;
@SerializedName("query")
private Object query = null;
public CriteriaA type(TypeEnum type) {
this.type = type;
return this;
}
@Schema(required = true, description = "")
public TypeEnum getType() {
return type;
}
public void setType(TypeEnum type) {
this.type = type;
}
public CriteriaA query(Object query) {
this.query = query;
return this;
}
@Schema(required = true, description = "")
public Object getQuery() {
return query;
}
public void setQuery(Object query) {
this.query = query;
}
@Override
public boolean equals(java.lang.Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CriteriaA criteriaA = (CriteriaA ) o;
return Objects.equals(this.type, criteriaA.type) &&
Objects.equals(this.query, criteriaA.query);
}
@Override
public int hashCode() {
return Objects.hash(type, query);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class CriteriaA {\n");
sb.append(" type: ").append(toIndentedString(type)).append("\n");
sb.append(" query: ").append(toIndentedString(query)).append("\n");
sb.append("}");
return sb.toString();
}
private String toIndentedString(java.lang.Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}
I'm trying to use this generated client code to send a request:
final TestRequest testRequest = new TestRequest();
final CriteriaA criteriaA = new CriteriaA ();
criteriaA .setType(CriteriaA .TypeEnum.CriteriaA);
criteriaA .setQuery("a query");
testRequest .setCriteria(criteriaA );
final ApiResponse<Void> apiResponse = testApi.createOrUpdateTestWithHttpInfo(testRequest);
Running the above client code results in this error when Jackson tries to deserialize it. It seems to be trying to construct an instance of the interface OneOfTestRequestCriteria, instead of the concrete implementation of the interface; CriteriaA:
[Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.acme.tag.models.OneOfTestRequestCriteria]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.acme.tag.models.OneOfTestRequestCriteria (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type informationn
I've run into a very similar issue, if i use anyOf or oneOf and generate a java client I run into issues where it has created an interface called AnyOfBody and then a basic implementation of it called Body. All of my generated classes are implementations of AnyOfBody but the api client methods are all expecting the input of type Body so all my request objects do not match. Because of this i am unable to execute any of the api calls that use oneOf or anyOf in the OAS.
So far i haven't found a solution
If I annotate the generated interface:
public interface OneOfTestRequestCriteria {}
with the following:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonSubTypes({
@Type(value = CriteriaA.class, name = "CriteriaA")
})
public interface OneOfTestRequestCriteria {
}
Then the request gets deserialized correctly into CriteriaA - am I missing something in my swagger.yaml that would result in this interface not getting annotated by the codegen tool?
@rorytorneymf I have the same issue. Were you able to identify the issue?
I have the same issue.
@rorytorneymf, thanks, adding annotations helps.
Any plans for implementing this? @gracekarina @frantuma
Same problem here. I can make do with a custom deserializer but it would be nice if generation took care of it ^^
I had the same problem until I configured MixIn. I mapped the generated interface to implemented superclass with existing @JsonSubTypes and @JsonTypeInfo.
It seems like objectMapper.addMixIn(OneOfTestRequestCriteria.class, CriteriaA.class)
I've run into a very similar issue, if i use anyOf or oneOf and generate a java client I run into issues where it has created an interface called AnyOfBody and then a basic implementation of it called Body. All of my generated classes are implementations of AnyOfBody but the api client methods are all expecting the input of type Body so all my request objects do not match. Because of this i am unable to execute any of the api calls that use oneOf or anyOf in the OAS.
So far i haven't found a solution
Facing exact same issue
Has anyone found the solution ?
Most helpful comment
If I annotate the generated interface:
with the following:
Then the request gets deserialized correctly into CriteriaA - am I missing something in my swagger.yaml that would result in this interface not getting annotated by the codegen tool?