fix oneOf discriminator lookup in java okhttp-gson client (#11735)

This commit is contained in:
William Cheng 2022-02-27 15:42:54 +08:00 committed by GitHub
parent 33b89148e5
commit b0877a112d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 222 additions and 10 deletions

View File

@ -6,3 +6,4 @@ templateDir: modules/openapi-generator/src/main/resources/Java
additionalProperties: additionalProperties:
artifactId: petstore-okhttp-gson artifactId: petstore-okhttp-gson
hideGenerationTimestamp: "true" hideGenerationTimestamp: "true"
useOneOfDiscriminatorLookup: "true"

View File

@ -76,8 +76,11 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
{{#discriminator}} {{#discriminator}}
// use discriminator value for faster oneOf lookup // use discriminator value for faster oneOf lookup
{{classname}} new{{classname}} = new {{classname}}(); {{classname}} new{{classname}} = new {{classname}}();
String discriminatorValue = elementAdapter.read(in).getAsJsonObject().get("{{{propertyBaseName}}}").getAsString(); if (jsonObject.get("{{{propertyBaseName}}}") == null) {
switch (discriminatorValue) { log.log(Level.WARNING, "Failed to lookup discriminator value for {{classname}} as `{{{propertyBaseName}}}` was not found in the payload or the payload is empty.");
} else {
// look up the discriminator value in the field `{{{propertyBaseName}}}`
switch (jsonObject.get("{{{propertyBaseName}}}").getAsString()) {
{{#mappedModels}} {{#mappedModels}}
case "{{{mappingName}}}": case "{{{mappingName}}}":
deserialized = adapter{{modelName}}.fromJsonTree(jsonObject); deserialized = adapter{{modelName}}.fromJsonTree(jsonObject);
@ -85,7 +88,8 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
return new{{classname}}; return new{{classname}};
{{/mappedModels}} {{/mappedModels}}
default: default:
log.log(Level.WARNING, String.format("Failed to lookup discriminator value `%s` for {{classname}}. Possible values:{{#mappedModels}} {{{mappingName}}}{{/mappedModels}}", discriminatorValue)); log.log(Level.WARNING, String.format("Failed to lookup discriminator value `%s` for {{classname}}. Possible values:{{#mappedModels}} {{{mappingName}}}{{/mappedModels}}", jsonObject.get("{{{propertyBaseName}}}").getAsString()));
}
} }
{{/discriminator}} {{/discriminator}}

View File

@ -113,6 +113,30 @@ public class Mammal extends AbstractOpenApiSchema {
Object deserialized = null; Object deserialized = null;
JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject(); JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject();
// use discriminator value for faster oneOf lookup
Mammal newMammal = new Mammal();
if (jsonObject.get("className") == null) {
log.log(Level.WARNING, "Failed to lookup discriminator value for Mammal as `className` was not found in the payload or the payload is empty.");
} else {
// look up the discriminator value in the field `className`
switch (jsonObject.get("className").getAsString()) {
case "Pig":
deserialized = adapterPig.fromJsonTree(jsonObject);
newMammal.setActualInstance(deserialized);
return newMammal;
case "whale":
deserialized = adapterWhale.fromJsonTree(jsonObject);
newMammal.setActualInstance(deserialized);
return newMammal;
case "zebra":
deserialized = adapterZebra.fromJsonTree(jsonObject);
newMammal.setActualInstance(deserialized);
return newMammal;
default:
log.log(Level.WARNING, String.format("Failed to lookup discriminator value `%s` for Mammal. Possible values: Pig whale zebra", jsonObject.get("className").getAsString()));
}
}
int match = 0; int match = 0;
TypeAdapter actualAdapter = elementAdapter; TypeAdapter actualAdapter = elementAdapter;

View File

@ -104,6 +104,26 @@ public class NullableShape extends AbstractOpenApiSchema {
Object deserialized = null; Object deserialized = null;
JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject(); JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject();
// use discriminator value for faster oneOf lookup
NullableShape newNullableShape = new NullableShape();
if (jsonObject.get("shapeType") == null) {
log.log(Level.WARNING, "Failed to lookup discriminator value for NullableShape as `shapeType` was not found in the payload or the payload is empty.");
} else {
// look up the discriminator value in the field `shapeType`
switch (jsonObject.get("shapeType").getAsString()) {
case "Quadrilateral":
deserialized = adapterQuadrilateral.fromJsonTree(jsonObject);
newNullableShape.setActualInstance(deserialized);
return newNullableShape;
case "Triangle":
deserialized = adapterTriangle.fromJsonTree(jsonObject);
newNullableShape.setActualInstance(deserialized);
return newNullableShape;
default:
log.log(Level.WARNING, String.format("Failed to lookup discriminator value `%s` for NullableShape. Possible values: Quadrilateral Triangle", jsonObject.get("shapeType").getAsString()));
}
}
int match = 0; int match = 0;
TypeAdapter actualAdapter = elementAdapter; TypeAdapter actualAdapter = elementAdapter;

View File

@ -104,6 +104,26 @@ public class Pig extends AbstractOpenApiSchema {
Object deserialized = null; Object deserialized = null;
JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject(); JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject();
// use discriminator value for faster oneOf lookup
Pig newPig = new Pig();
if (jsonObject.get("className") == null) {
log.log(Level.WARNING, "Failed to lookup discriminator value for Pig as `className` was not found in the payload or the payload is empty.");
} else {
// look up the discriminator value in the field `className`
switch (jsonObject.get("className").getAsString()) {
case "BasquePig":
deserialized = adapterBasquePig.fromJsonTree(jsonObject);
newPig.setActualInstance(deserialized);
return newPig;
case "DanishPig":
deserialized = adapterDanishPig.fromJsonTree(jsonObject);
newPig.setActualInstance(deserialized);
return newPig;
default:
log.log(Level.WARNING, String.format("Failed to lookup discriminator value `%s` for Pig. Possible values: BasquePig DanishPig", jsonObject.get("className").getAsString()));
}
}
int match = 0; int match = 0;
TypeAdapter actualAdapter = elementAdapter; TypeAdapter actualAdapter = elementAdapter;

View File

@ -104,6 +104,26 @@ public class Quadrilateral extends AbstractOpenApiSchema {
Object deserialized = null; Object deserialized = null;
JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject(); JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject();
// use discriminator value for faster oneOf lookup
Quadrilateral newQuadrilateral = new Quadrilateral();
if (jsonObject.get("quadrilateralType") == null) {
log.log(Level.WARNING, "Failed to lookup discriminator value for Quadrilateral as `quadrilateralType` was not found in the payload or the payload is empty.");
} else {
// look up the discriminator value in the field `quadrilateralType`
switch (jsonObject.get("quadrilateralType").getAsString()) {
case "ComplexQuadrilateral":
deserialized = adapterComplexQuadrilateral.fromJsonTree(jsonObject);
newQuadrilateral.setActualInstance(deserialized);
return newQuadrilateral;
case "SimpleQuadrilateral":
deserialized = adapterSimpleQuadrilateral.fromJsonTree(jsonObject);
newQuadrilateral.setActualInstance(deserialized);
return newQuadrilateral;
default:
log.log(Level.WARNING, String.format("Failed to lookup discriminator value `%s` for Quadrilateral. Possible values: ComplexQuadrilateral SimpleQuadrilateral", jsonObject.get("quadrilateralType").getAsString()));
}
}
int match = 0; int match = 0;
TypeAdapter actualAdapter = elementAdapter; TypeAdapter actualAdapter = elementAdapter;

View File

@ -104,6 +104,26 @@ public class Shape extends AbstractOpenApiSchema {
Object deserialized = null; Object deserialized = null;
JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject(); JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject();
// use discriminator value for faster oneOf lookup
Shape newShape = new Shape();
if (jsonObject.get("shapeType") == null) {
log.log(Level.WARNING, "Failed to lookup discriminator value for Shape as `shapeType` was not found in the payload or the payload is empty.");
} else {
// look up the discriminator value in the field `shapeType`
switch (jsonObject.get("shapeType").getAsString()) {
case "Quadrilateral":
deserialized = adapterQuadrilateral.fromJsonTree(jsonObject);
newShape.setActualInstance(deserialized);
return newShape;
case "Triangle":
deserialized = adapterTriangle.fromJsonTree(jsonObject);
newShape.setActualInstance(deserialized);
return newShape;
default:
log.log(Level.WARNING, String.format("Failed to lookup discriminator value `%s` for Shape. Possible values: Quadrilateral Triangle", jsonObject.get("shapeType").getAsString()));
}
}
int match = 0; int match = 0;
TypeAdapter actualAdapter = elementAdapter; TypeAdapter actualAdapter = elementAdapter;

View File

@ -104,6 +104,26 @@ public class ShapeOrNull extends AbstractOpenApiSchema {
Object deserialized = null; Object deserialized = null;
JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject(); JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject();
// use discriminator value for faster oneOf lookup
ShapeOrNull newShapeOrNull = new ShapeOrNull();
if (jsonObject.get("shapeType") == null) {
log.log(Level.WARNING, "Failed to lookup discriminator value for ShapeOrNull as `shapeType` was not found in the payload or the payload is empty.");
} else {
// look up the discriminator value in the field `shapeType`
switch (jsonObject.get("shapeType").getAsString()) {
case "Quadrilateral":
deserialized = adapterQuadrilateral.fromJsonTree(jsonObject);
newShapeOrNull.setActualInstance(deserialized);
return newShapeOrNull;
case "Triangle":
deserialized = adapterTriangle.fromJsonTree(jsonObject);
newShapeOrNull.setActualInstance(deserialized);
return newShapeOrNull;
default:
log.log(Level.WARNING, String.format("Failed to lookup discriminator value `%s` for ShapeOrNull. Possible values: Quadrilateral Triangle", jsonObject.get("shapeType").getAsString()));
}
}
int match = 0; int match = 0;
TypeAdapter actualAdapter = elementAdapter; TypeAdapter actualAdapter = elementAdapter;

View File

@ -113,6 +113,30 @@ public class Triangle extends AbstractOpenApiSchema {
Object deserialized = null; Object deserialized = null;
JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject(); JsonObject jsonObject = elementAdapter.read(in).getAsJsonObject();
// use discriminator value for faster oneOf lookup
Triangle newTriangle = new Triangle();
if (jsonObject.get("triangleType") == null) {
log.log(Level.WARNING, "Failed to lookup discriminator value for Triangle as `triangleType` was not found in the payload or the payload is empty.");
} else {
// look up the discriminator value in the field `triangleType`
switch (jsonObject.get("triangleType").getAsString()) {
case "EquilateralTriangle":
deserialized = adapterEquilateralTriangle.fromJsonTree(jsonObject);
newTriangle.setActualInstance(deserialized);
return newTriangle;
case "IsoscelesTriangle":
deserialized = adapterIsoscelesTriangle.fromJsonTree(jsonObject);
newTriangle.setActualInstance(deserialized);
return newTriangle;
case "ScaleneTriangle":
deserialized = adapterScaleneTriangle.fromJsonTree(jsonObject);
newTriangle.setActualInstance(deserialized);
return newTriangle;
default:
log.log(Level.WARNING, String.format("Failed to lookup discriminator value `%s` for Triangle. Possible values: EquilateralTriangle IsoscelesTriangle ScaleneTriangle", jsonObject.get("triangleType").getAsString()));
}
}
int match = 0; int match = 0;
TypeAdapter actualAdapter = elementAdapter; TypeAdapter actualAdapter = elementAdapter;

View File

@ -364,6 +364,65 @@ public class JSONTest {
} }
} }
/**
* Validate a oneOf schema can be deserialized into the expected class.
* The oneOf schema has a discriminator.
*/
@Test
public void testOneOfSchemaWithDiscriminator() throws Exception {
{
String str = "{ \"className\": \"whale\", \"hasBaleen\": false, \"hasTeeth\": false }";
// make sure deserialization works for pojo object
Whale w = json.getGson().fromJson(str, Whale.class);
assertEquals(w.getClassName(), "whale");
assertEquals(w.getHasBaleen(), false);
assertEquals(w.getHasTeeth(), false);
Mammal o = json.getGson().fromJson(str, Mammal.class);
assertTrue(o.getActualInstance() instanceof Whale);
Whale inst = (Whale) o.getActualInstance();
assertEquals(inst.getClassName(), "whale");
assertEquals(inst.getHasBaleen(), false);
assertEquals(inst.getHasTeeth(), false);
assertEquals(json.getGson().toJson(inst), "{\"hasBaleen\":false,\"hasTeeth\":false,\"className\":\"whale\"}");
assertEquals(inst.toJson(), "{\"hasBaleen\":false,\"hasTeeth\":false,\"className\":\"whale\"}");
assertEquals(json.getGson().toJson(o), "{\"hasBaleen\":false,\"hasTeeth\":false,\"className\":\"whale\"}");
assertEquals(o.toJson(), "{\"hasBaleen\":false,\"hasTeeth\":false,\"className\":\"whale\"}");
String str2 = "{ \"className\": \"zebra\", \"type\": \"plains\" }";
// make sure deserialization works for pojo object
Zebra z = Zebra.fromJson(str2);
assertEquals(z.toJson(), "{\"className\":\"zebra\",\"type\":\"plains\"}");
Mammal o2 = json.getGson().fromJson(str2, Mammal.class);
assertTrue(o2.getActualInstance() instanceof Zebra);
Zebra inst2 = (Zebra) o2.getActualInstance();
assertEquals(json.getGson().toJson(inst2), "{\"className\":\"zebra\",\"type\":\"plains\"}");
assertEquals(inst2.toJson(), "{\"className\":\"zebra\",\"type\":\"plains\"}");
assertEquals(json.getGson().toJson(o2), "{\"className\":\"zebra\",\"type\":\"plains\"}");
assertEquals(o2.toJson(), "{\"className\":\"zebra\",\"type\":\"plains\"}");
}
{
// incorrect payload results in exception
String str = "{ \"cultivar\": \"golden delicious\", \"mealy\": false, \"garbage_prop\": \"abc\" }";
Exception exception = assertThrows(com.google.gson.JsonSyntaxException.class, () -> {
Mammal o = json.getGson().fromJson(str, Mammal.class);
});
assertTrue(exception.getMessage().contains("Failed deserialization for Mammal: 0 classes match result, expected 1. JSON: {\"cultivar\":\"golden delicious\",\"mealy\":false,\"garbage_prop\":\"abc\"}"));
}
{
// Try to deserialize empty object. This should fail 'oneOf' because none will match
// whale or zebra.
String str = "{ }";
Exception exception = assertThrows(com.google.gson.JsonSyntaxException.class, () -> {
json.getGson().fromJson(str, Mammal.class);
});
assertTrue(exception.getMessage().contains("Failed deserialization for Mammal: 0 classes match result, expected 1"));
}
}
/** /**
* Validate a oneOf schema can be deserialized into the expected class. * Validate a oneOf schema can be deserialized into the expected class.
* The oneOf schema does not have a discriminator. * The oneOf schema does not have a discriminator.