-
Notifications
You must be signed in to change notification settings - Fork 6k
Description
Description
I've tried to get the Android Client to run with polymorphic JSON deserialization and stumbled across some issues.
Swagger-codegen version
From editor.swagger.io (new version, two days ago)
Swagger declaration file content or url
Here the swagger definition to the Objects:
definitions:
Device:
type: object
properties:
device_id:
type: string
type_id:
type: string
room_ids:
type: array
items:
type: string
group_ids:
type: array
items:
type: string
functions:
type: array
items:
$ref: '#/definitions/Function'
required:
- device_id
- room_ids
- group_ids
- functions
Function:
description: Base Class for all functions
type: object
properties:
function_id:
type: string
required:
- function_id
OnOff:
description: Generic On-Off switch
allOf:
- $ref: '#/definitions/Function'
- properties:
isOn:
type: boolean
default: false
timestamp:
type: string
format: date-time
Dimmer:
description: Generic slider switch
allOf:
- $ref: '#/definitions/Function'
- properties:
value:
type: integer
minimum: 0
maximum: 255
default: 0
timestamp:
type: string
format: date-time
Temperature:
description: Generic temperature sensor
allOf:
- $ref: '#/definitions/Function'
- properties:
value:
description: Temperature value in Kelvin
type: number
minimum: 0
maximum: 511
default: 0
timestamp:
type: string
format: date-time
Then the models have all the type 'function_id'
Steps to reproduce
My research has shown, Gson is not able to do polymorphic mapping out of the box, but the only in the repository provided TypeAdapter RuntimeTypeAdapterFactory.java can do that by adding the class to the project and enhancing the generated JsonUtil.java like this:
static {
final RuntimeTypeAdapterFactory<Function> typeFactory = RuntimeTypeAdapterFactory
.of(Function.class, "function_id") // Define the parent class and the identifier key for each subclass
.registerSubtype(OnOff.class, "onoff")
.registerSubtype(Dimmer.class, "dimmer")
.registerSubtype(Temperature.class, "temperature");
gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapterFactory(typeFactory);
[...]
}
But now I got a JsonParseException: cannot serialize Function because it already defines a field named onoff. Then I looked inside the models and saw this:
public class Function {
@SerializedName("function_id")
private String functionId = null;
/**
**/
@ApiModelProperty(required = true, value = "")
public String getFunctionId() {
return functionId;
}
public void setFunctionId(String functionId) {
this.functionId = functionId;
}
[...]
}
@ApiModel(description = "Generic On-Off switch")
public class OnOff extends Function {
@SerializedName("function_id")
private String functionId = null;
@SerializedName("isOn")
private Boolean isOn = null;
@SerializedName("timestamp")
private Date timestamp = null;
/**
**/
@ApiModelProperty(required = true, value = "")
public String getFunctionId() {
return functionId;
}
public void setFunctionId(String functionId) {
this.functionId = functionId;
}
[...]
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
OnOff onOff = (OnOff) o;
return (this.functionId == null ? onOff.functionId == null : this.functionId.equals(onOff.functionId)) &&
(this.isOn == null ? onOff.isOn == null : this.isOn.equals(onOff.isOn)) &&
(this.timestamp == null ? onOff.timestamp == null : this.timestamp.equals(onOff.timestamp));
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (this.functionId == null ? 0: this.functionId.hashCode());
result = 31 * result + (this.isOn == null ? 0: this.isOn.hashCode());
result = 31 * result + (this.timestamp == null ? 0: this.timestamp.hashCode());
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class OnOff {\n");
sb.append(" " + super.toString()).append("\n");
sb.append(" functionId: ").append(functionId).append("\n");
sb.append(" isOn: ").append(isOn).append("\n");
sb.append(" timestamp: ").append(timestamp).append("\n");
sb.append("}\n");
return sb.toString();
}
}
Suggest a Fix
So the subclasses have their own function_id class identifier key and the parent class gets just nested inside the subclass from swagger codegen. After deleting the subclass property String functionId and changing every 'this.functionId' to 'super.getFunctionId()' the polymorphic mapping with RuntimeTypeAdapterFactory.java worked fine!
I'm not sure if changing this in codegen could have side effects to non-polymorphic Gson mapping, but this looks obviously bad ;)
Deserialization now works for me, but there is still a issue with serializing objects with the registered RuntimeTypeAdapter. For me the workaround using a second Gson object for serialization where the RuntimeTypeAdapter isn't registered works so far.
I hope this will help to improve the codegen project because I'm very enthusiastic about it!