diff --git a/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/anyof_model.mustache b/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/anyof_model.mustache index 2fc12731cece..e1760876459d 100644 --- a/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/anyof_model.mustache +++ b/modules/openapi-generator/src/main/resources/Java/libraries/okhttp-gson/anyof_model.mustache @@ -107,6 +107,34 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im Object deserialized = null; JsonElement jsonElement = elementAdapter.read(in); + {{#useOneOfDiscriminatorLookup}} + {{#discriminator}} + // non-object payloads can still match a non-object anyOf schema below + if (jsonElement.isJsonObject()) { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + JsonElement discriminatorElement = jsonObject.get("{{{propertyBaseName}}}"); + + // use discriminator value for faster anyOf lookup + {{classname}} new{{classname}} = new {{classname}}(); + if (discriminatorElement == null || !discriminatorElement.isJsonPrimitive()) { + log.log(Level.WARNING, "Failed to lookup discriminator value for {{classname}} as `{{{propertyBaseName}}}` is missing or is not a primitive type in the payload."); + } else { + // look up the discriminator value in the field `{{{propertyBaseName}}}` + switch (discriminatorElement.getAsString()) { + {{#mappedModels}} + case "{{{mappingName}}}": + deserialized = adapter{{modelName}}.fromJsonTree(jsonObject); + new{{classname}}.setActualInstance(deserialized); + return new{{classname}}; + {{/mappedModels}} + default: + log.log(Level.WARNING, String.format(java.util.Locale.ROOT, "Failed to lookup discriminator value `%s` for {{classname}}. Possible values:{{#mappedModels}} {{{mappingName}}}{{/mappedModels}}", discriminatorElement.getAsString())); + } + } + } + + {{/discriminator}} + {{/useOneOfDiscriminatorLookup}} ArrayList errorMessages = new ArrayList<>(); TypeAdapter actualAdapter = elementAdapter; diff --git a/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/MammalAnyof.java b/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/MammalAnyof.java index 54c607cac57a..ef912255d3d1 100644 --- a/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/MammalAnyof.java +++ b/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/MammalAnyof.java @@ -110,6 +110,36 @@ public MammalAnyof read(JsonReader in) throws IOException { Object deserialized = null; JsonElement jsonElement = elementAdapter.read(in); + // non-object payloads can still match a non-object anyOf schema below + if (jsonElement.isJsonObject()) { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + JsonElement discriminatorElement = jsonObject.get("className"); + + // use discriminator value for faster anyOf lookup + MammalAnyof newMammalAnyof = new MammalAnyof(); + if (discriminatorElement == null || !discriminatorElement.isJsonPrimitive()) { + log.log(Level.WARNING, "Failed to lookup discriminator value for MammalAnyof as `className` is missing or is not a primitive type in the payload."); + } else { + // look up the discriminator value in the field `className` + switch (discriminatorElement.getAsString()) { + case "Pig": + deserialized = adapterPig.fromJsonTree(jsonObject); + newMammalAnyof.setActualInstance(deserialized); + return newMammalAnyof; + case "whale": + deserialized = adapterWhale.fromJsonTree(jsonObject); + newMammalAnyof.setActualInstance(deserialized); + return newMammalAnyof; + case "zebra": + deserialized = adapterZebra.fromJsonTree(jsonObject); + newMammalAnyof.setActualInstance(deserialized); + return newMammalAnyof; + default: + log.log(Level.WARNING, String.format(java.util.Locale.ROOT, "Failed to lookup discriminator value `%s` for MammalAnyof. Possible values: Pig whale zebra", discriminatorElement.getAsString())); + } + } + } + ArrayList errorMessages = new ArrayList<>(); TypeAdapter actualAdapter = elementAdapter; diff --git a/samples/client/petstore/java/okhttp-gson/src/test/java/org/openapitools/client/JSONTest.java b/samples/client/petstore/java/okhttp-gson/src/test/java/org/openapitools/client/JSONTest.java index 5b6b2241ce28..7ad6263dbfca 100644 --- a/samples/client/petstore/java/okhttp-gson/src/test/java/org/openapitools/client/JSONTest.java +++ b/samples/client/petstore/java/okhttp-gson/src/test/java/org/openapitools/client/JSONTest.java @@ -499,6 +499,59 @@ public void testOneOfSchemaWithDiscriminator() throws Exception { } } + /** + * Validate an anyOf schema can be deserialized into the expected class. + * The anyOf schema has a discriminator. + */ + @Test + public void testAnyOfSchemaWithDiscriminator() throws Exception { + { + // Zebra is listed after Whale in anyOf, and a zebra payload also satisfies Whale + // (className is Whale's only required field), so without the discriminator lookup + // this deserializes as Whale. + String str = "{ \"className\": \"zebra\", \"type\": \"plains\" }"; + + MammalAnyof o = json.getGson().fromJson(str, MammalAnyof.class); + assertTrue(o.getActualInstance() instanceof Zebra); + Zebra inst = (Zebra) o.getActualInstance(); + assertEquals(inst.getClassName(), "zebra"); + assertEquals(inst.toJson(), "{\"type\":\"plains\",\"className\":\"zebra\"}"); + assertEquals(o.toJson(), "{\"type\":\"plains\",\"className\":\"zebra\"}"); + } + { + String str = "{ \"className\": \"whale\", \"hasBaleen\": false, \"hasTeeth\": false }"; + + MammalAnyof o = json.getGson().fromJson(str, MammalAnyof.class); + assertTrue(o.getActualInstance() instanceof Whale); + Whale inst = (Whale) o.getActualInstance(); + assertEquals(inst.getClassName(), "whale"); + assertEquals(inst.getHasBaleen(), false); + assertEquals(inst.getHasTeeth(), false); + 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\"}"); + } + { + // an unmapped discriminator value falls back to matching the anyOf schemas in order + String str = "{ \"className\": \"dolphin\", \"hasBaleen\": true }"; + + MammalAnyof o = json.getGson().fromJson(str, MammalAnyof.class); + assertTrue(o.getActualInstance() instanceof Whale); + Whale inst = (Whale) o.getActualInstance(); + assertEquals(inst.getClassName(), "dolphin"); + assertEquals(inst.getHasBaleen(), true); + } + { + // Try to deserialize empty object. This should fail 'anyOf' because none will match + // whale, zebra or Pig. + String str = "{ }"; + Exception exception = assertThrows(com.google.gson.JsonSyntaxException.class, () -> { + json.getGson().fromJson(str, MammalAnyof.class); + }); + assertTrue(exception.getMessage().contains("java.io.IOException: Failed deserialization for MammalAnyof")); + } + } + /** * Test JSON validation method */