diff --git a/README.md b/README.md index f9e8a88..cd699f4 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The client can be used with Java 1.8+ and pulled into Maven or Gradle projects. com.vertexvis api-client-java - 0.14.0 + 0.15.0 compile ``` @@ -25,13 +25,13 @@ The client can be used with Java 1.8+ and pulled into Maven or Gradle projects. ### Gradle ```groovy -compile "com.vertexvis:api-client-java:0.14.0" +compile "com.vertexvis:api-client-java:0.15.0" ``` ### Sbt ```sbt -libraryDependencies += "com.vertexvis" % "api-client-java" % "0.14.0" +libraryDependencies += "com.vertexvis" % "api-client-java" % "0.15.0" ``` ### Others @@ -44,7 +44,7 @@ mvn clean package Then manually install the following JARs. -- `target/api-client-java-0.14.0.jar` +- `target/api-client-java-0.15.0.jar` - `target/lib/*.jar` ## Usage @@ -104,7 +104,7 @@ To consume published snapshot versions in other projects, add the snapshot repos com.vertexvis api-client-java - 0.14.0-SNAPSHOT + 0.15.0-SNAPSHOT ``` @@ -119,7 +119,7 @@ repositories { } dependencies { - implementation 'com.vertexvis:api-client-java:0.14.0-SNAPSHOT' + implementation 'com.vertexvis:api-client-java:0.15.0-SNAPSHOT' } ``` diff --git a/build.gradle b/build.gradle index b4d8195..5b79e91 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id "io.github.gradle-nexus.publish-plugin" } -def projectVersion = '0.14.0' +def projectVersion = '0.15.0' def isSnapshot = project.hasProperty('isSnapshot') && project.isSnapshot.toBoolean() version = isSnapshot ? "${projectVersion}-SNAPSHOT" : projectVersion diff --git a/buildSrc/src/main/groovy/vertex.openapi-generator.gradle b/buildSrc/src/main/groovy/vertex.openapi-generator.gradle index 20b0530..9668f66 100644 --- a/buildSrc/src/main/groovy/vertex.openapi-generator.gradle +++ b/buildSrc/src/main/groovy/vertex.openapi-generator.gradle @@ -24,6 +24,10 @@ openApiGenerate { dateLibrary: "java8", hideGenerationTimestamp: "true", useRuntimeException: "true", + disallowAdditionalPropertiesIfNotPresent: "false", + failOnUnknownProperties: "false", + useOneOfDiscriminatorLookup: "true", + legacyDiscriminatorBehavior: "false", ] additionalProperties = [ skipValidationFor: "Part,PartData,PartDataAttributes,QueuedJobData,QueuedJob,QueuedJobDataAttributes" // Comma-separated list of models to skip validation for diff --git a/buildSrc/src/main/resources/vertex-java/oneof_model.mustache b/buildSrc/src/main/resources/vertex-java/oneof_model.mustache new file mode 100644 index 0000000..50e03ca --- /dev/null +++ b/buildSrc/src/main/resources/vertex-java/oneof_model.mustache @@ -0,0 +1,537 @@ + + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.JsonPrimitive; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonArray; +import com.google.gson.JsonParseException; + +import {{invokerPackage}}.JSON; + +// This source file was generated using a Vertex modified version of the oneof_model.mustache template. + +{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{>xmlAnnotation}} +public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}} implements {{{.}}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-implements}} { + private static final Logger log = Logger.getLogger({{classname}}.class.getName()); + + public static class CustomTypeAdapterFactory implements TypeAdapterFactory { + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (!{{classname}}.class.isAssignableFrom(type.getRawType())) { + return null; // this class only serializes '{{classname}}' and its subtypes + } + final TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class); + {{#composedSchemas}} + {{#oneOf}} + {{^isArray}} + {{^isMap}} + {{^vendorExtensions.x-duplicated-data-type}} + final TypeAdapter<{{{dataType}}}> adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = gson.getDelegateAdapter(this, TypeToken.get({{{dataType}}}.class)); + {{/vendorExtensions.x-duplicated-data-type}} + {{/isMap}} + {{/isArray}} + {{#isArray}} + + final Type typeInstance{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = new TypeToken<{{{dataType}}}>(){}.getType(); + final TypeAdapter<{{{dataType}}}> adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = (TypeAdapter<{{{dataType}}}>) gson.getDelegateAdapter(this, TypeToken.get(typeInstance{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}})); + {{/isArray}} + {{#isMap}} + final Type typeInstance{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = new TypeToken<{{{dataType}}}>(){}.getType(); + final TypeAdapter<{{{dataType}}}> adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}} = (TypeAdapter<{{{dataType}}}>) gson.getDelegateAdapter(this, TypeToken.get(typeInstance{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}})); + {{/isMap}} + {{/oneOf}} + {{/composedSchemas}} + + return (TypeAdapter) new TypeAdapter<{{classname}}>() { + @Override + public void write(JsonWriter out, {{classname}} value) throws IOException { + if (value == null || value.getActualInstance() == null) { + elementAdapter.write(out, null); + return; + } + + {{#composedSchemas}} + {{#oneOf}} + {{^vendorExtensions.x-duplicated-data-type}} + // check if the actual instance is of the type `{{{dataType}}}` + if (value.getActualInstance() instanceof {{#isArray}}List{{/isArray}}{{#isMap}}Map{{/isMap}}{{^isMap}}{{^isArray}}{{{dataType}}}{{/isArray}}{{/isMap}}) { + {{#isPrimitiveType}} + {{^isMap}} + JsonPrimitive primitive = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonPrimitive(); + elementAdapter.write(out, primitive); + return; + {{/isMap}} + {{#isMap}} + JsonObject object = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonObject(); + elementAdapter.write(out, object); + return; + {{/isMap}} + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{#isArray}} + List list = (List) value.getActualInstance(); + if (list.get(0) instanceof {{{items.dataType}}}) { + JsonArray array = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()).getAsJsonArray(); + elementAdapter.write(out, array); + return; + } + {{/isArray}} + {{/isPrimitiveType}} + {{^isMap}} + {{^isArray}} + {{^isPrimitiveType}} + JsonElement element = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.toJsonTree(({{{dataType}}})value.getActualInstance()); + elementAdapter.write(out, element); + return; + {{/isPrimitiveType}} + {{/isArray}} + {{/isMap}} + } + {{/vendorExtensions.x-duplicated-data-type}} + {{/oneOf}} + {{/composedSchemas}} + throw new IOException("Failed to serialize as the type doesn't match oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}"); + } + + @Override + public {{classname}} read(JsonReader in) throws IOException { + Object deserialized = null; + JsonElement jsonElement = elementAdapter.read(in); + + {{#useOneOfDiscriminatorLookup}} + {{#discriminator}} + JsonObject jsonObject = jsonElement.getAsJsonObject(); + + // use discriminator value for faster oneOf lookup + {{classname}} new{{classname}} = new {{classname}}(); + if (jsonObject.get("{{{propertyBaseName}}}") == null) { + 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}} + case "{{{mappingName}}}": + deserialized = adapter{{modelName}}.fromJsonTree(jsonObject); + new{{classname}}.setActualInstance(deserialized); + return new{{classname}}; + {{/mappedModels}} + default: + log.log(Level.WARNING, String.format("Failed to lookup discriminator value `%s` for {{classname}}. Possible values:{{#mappedModels}} {{{mappingName}}}{{/mappedModels}}", jsonObject.get("{{{propertyBaseName}}}").getAsString())); + } + } + + {{/discriminator}} + {{/useOneOfDiscriminatorLookup}} + int match = 0; + ArrayList errorMessages = new ArrayList<>(); + TypeAdapter actualAdapter = elementAdapter; + + {{#composedSchemas}} + {{#oneOf}} + {{^vendorExtensions.x-duplicated-data-type}} + {{^hasVars}} + // deserialize {{{dataType}}} + try { + // validate the JSON object to see if any exception is thrown + {{^isArray}} + {{^isMap}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!jsonElement.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format("Expected json element to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(jsonElement); + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; + {{/isPrimitiveType}} + {{/isNumber}} + {{/isMap}} + {{/isArray}} + {{#isArray}} + if (!jsonElement.isJsonArray()) { + throw new IllegalArgumentException(String.format("Expected json element to be a array type in the JSON string but got `%s`", jsonElement.toString())); + } + + JsonArray array = jsonElement.getAsJsonArray(); + // validate array items + for(JsonElement element : array) { + {{#items}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!element.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format("Expected array items to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(element); + {{/isPrimitiveType}} + {{/isNumber}} + {{/items}} + } + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; + {{/isArray}} + {{#isMap}} + if (!jsonElement.isJsonObject()) { + throw new IllegalArgumentException(String.format("Expected json element to be a object type in the JSON string but got `%s`", jsonElement.toString())); + } + + {{^isFreeFormObject}} + Map map = jsonElement.getAsJsonObject().asMap(); + // validate map items + for(JsonElement element : map.values()) { + {{#items}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!element.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format("Expected array items to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(element); + {{/isPrimitiveType}} + {{/isNumber}} + {{/items}} + } + {{/isFreeFormObject}} + actualAdapter = adapter{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}; + {{/isMap}} + match++; + log.log(Level.FINER, "Input data matches schema '{{{dataType}}}'"); + } catch (Exception e) { + // deserialization failed, continue + errorMessages.add(String.format("Deserialization for {{{dataType}}} failed with `%s`.", e.getMessage())); + log.log(Level.FINER, "Input data does not match schema '{{{dataType}}}'", e); + } + {{/hasVars}} + {{#hasVars}} + // deserialize {{{.}}} + try { + // validate the JSON object to see if any exception is thrown + {{.}}.validateJsonElement(jsonElement); + actualAdapter = adapter{{.}}; + match++; + log.log(Level.FINER, "Input data matches schema '{{{.}}}'"); + } catch (Exception e) { + // deserialization failed, continue + errorMessages.add(String.format("Deserialization for {{{.}}} failed with `%s`.", e.getMessage())); + log.log(Level.FINER, "Input data does not match schema '{{{.}}}'", e); + } + {{/hasVars}} + {{/vendorExtensions.x-duplicated-data-type}} + {{/oneOf}} + {{/composedSchemas}} + + if (match == 1) { + {{classname}} ret = new {{classname}}(); + ret.setActualInstance(actualAdapter.fromJsonTree(jsonElement)); + return ret; + } + + throw new IOException(String.format("Failed deserialization for {{classname}}: %d classes match result, expected 1. Detailed failure message for oneOf schemas: %s. JSON: %s", match, errorMessages, jsonElement.toString())); + } + }.nullSafe(); + } + } + + // store a list of schema names defined in oneOf + public static final Map> schemas = new HashMap>(); + + public {{classname}}() { + super("oneOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); + } + + public {{classname}}(Object o) { + super("oneOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); + setActualInstance(o); + } + + static { + {{#composedSchemas}} + {{#oneOf}} + {{^vendorExtensions.x-duplicated-data-type}} + schemas.put("{{{dataType}}}", {{{baseType}}}.class); + {{/vendorExtensions.x-duplicated-data-type}} + {{/oneOf}} + {{/composedSchemas}} + } + + @Override + public Map> getSchemas() { + return {{classname}}.schemas; + } + + /** + * Set the instance that matches the oneOf child schema, check + * the instance parameter is valid against the oneOf child schemas: + * {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}} + * + * It could be an instance of the 'oneOf' schemas. + */ + @Override + public void setActualInstance(Object instance) { + {{#isNullable}} + if (instance == null) { + super.setActualInstance(instance); + return; + } + + {{/isNullable}} + {{#composedSchemas}} + {{#oneOf}} + {{^vendorExtensions.x-duplicated-data-type}} + if (instance instanceof {{#isArray}}List{{/isArray}}{{#isMap}}Map{{/isMap}}{{^isMap}}{{^isArray}}{{{dataType}}}{{/isArray}}{{/isMap}}) { + {{#isArray}} + List list = (List) instance; + if (list.get(0) instanceof {{{items.dataType}}}) { + super.setActualInstance(instance); + return; + } + {{/isArray}} + {{^isArray}} + super.setActualInstance(instance); + return; + {{/isArray}} + } + + {{/vendorExtensions.x-duplicated-data-type}} + {{/oneOf}} + {{/composedSchemas}} + throw new RuntimeException("Invalid instance type. Must be {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}"); + } + + /** + * Get the actual instance, which can be the following: + * {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}} + * + * @return The actual instance ({{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}) + */ + @SuppressWarnings("unchecked") + @Override + public Object getActualInstance() { + return super.getActualInstance(); + } + + {{#composedSchemas}} + {{#oneOf}} + {{^vendorExtensions.x-duplicated-data-type-ignoring-erasure}} + /** + * Get the actual instance of `{{{dataType}}}`. If the actual instance is not `{{{dataType}}}`, + * the ClassCastException will be thrown. + * + * @return The actual instance of `{{{dataType}}}` + * @throws ClassCastException if the instance is not `{{{dataType}}}` + */ + public {{{dataType}}} get{{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}() throws ClassCastException { + return ({{{dataType}}})super.getActualInstance(); + } + + {{/vendorExtensions.x-duplicated-data-type-ignoring-erasure}} + {{/oneOf}} + {{/composedSchemas}} + /** + * Validates the JSON Element and throws an exception if issues found + * + * @param jsonElement JSON Element + * @throws IOException if the JSON Element is invalid with respect to {{classname}} + */ + public static void validateJsonElement(JsonElement jsonElement) throws IOException { + {{#useOneOfDiscriminatorLookup}} + {{#discriminator}} + // Vertex customization -- use the same behavior as the read method. + JsonObject jsonObject = jsonElement != null ? jsonElement.getAsJsonObject() : null; + + if (jsonObject != null) { + if (jsonObject.get("type") == null) { + 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}} + case "{{{mappingName}}}": + {{modelName}}.validateJsonElement(jsonElement); + return; + {{/mappedModels}} + default: + log.log(Level.WARNING, String.format("Failed to lookup discriminator value `%s` for {{classname}}. Possible values:{{#mappedModels}} {{{mappingName}}}{{/mappedModels}}", jsonObject.get("{{{propertyBaseName}}}").getAsString())); + } + } + } + {{/discriminator}} + {{/useOneOfDiscriminatorLookup}} + // validate oneOf schemas one by one + int validCount = 0; + ArrayList errorMessages = new ArrayList<>(); + {{#composedSchemas}} + {{#oneOf}} + {{^vendorExtensions.x-duplicated-data-type}} + // validate the json string with {{{dataType}}} + try { + {{^hasVars}} + {{^isMap}} + {{^isArray}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!jsonElement.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format("Expected json element to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(jsonElement); + {{/isPrimitiveType}} + {{/isNumber}} + {{/isArray}} + {{/isMap}} + {{#isArray}} + if (!jsonElement.isJsonArray()) { + throw new IllegalArgumentException(String.format("Expected json element to be a array type in the JSON string but got `%s`", jsonElement.toString())); + } + JsonArray array = jsonElement.getAsJsonArray(); + // validate array items + for(JsonElement element : array) { + {{#items}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!element.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format("Expected array items to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(element); + {{/isPrimitiveType}} + {{/isNumber}} + {{/items}} + } + {{/isArray}} + {{#isMap}} + if (!jsonElement.isJsonObject()) { + throw new IllegalArgumentException(String.format("Expected json element to be a object type in the JSON string but got `%s`", jsonElement.toString())); + } + + {{^isFreeFormObject}} + Map map = jsonElement.getAsJsonObject().asMap(); + // validate map items + for(JsonElement element : map.values()) { + {{#items}} + {{#isNumber}} + if (!jsonElement.getAsJsonPrimitive().isNumber()) { + throw new IllegalArgumentException(String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isNumber}} + {{^isNumber}} + {{#isPrimitiveType}} + if (!element.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) { + throw new IllegalArgumentException(String.format("Expected array items to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())); + } + {{/isPrimitiveType}} + {{/isNumber}} + {{^isNumber}} + {{^isPrimitiveType}} + {{#sanitizeDataType}}{{{dataType}}}{{/sanitizeDataType}}.validateJsonElement(element); + {{/isPrimitiveType}} + {{/isNumber}} + {{/items}} + } + {{/isFreeFormObject}} + {{/isMap}} + {{/hasVars}} + {{#hasVars}} + {{{.}}}.validateJsonElement(jsonElement); + validCount++; + {{/hasVars}} + validCount++; + } catch (Exception e) { + errorMessages.add(String.format("Deserialization for {{{dataType}}} failed with `%s`.", e.getMessage())); + // continue to the next one + } + {{/vendorExtensions.x-duplicated-data-type}} + {{/oneOf}} + {{/composedSchemas}} + if (validCount != 1) { + throw new IOException(String.format("The JSON string is invalid for {{classname}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. %d class(es) match the result, expected 1. Detailed failure message for oneOf schemas: %s. JSON: %s", validCount, errorMessages, jsonElement.toString())); + } + } + + /** + * Create an instance of {{classname}} given an JSON string + * + * @param jsonString JSON string + * @return An instance of {{classname}} + * @throws IOException if the JSON string is invalid with respect to {{classname}} + */ + public static {{{classname}}} fromJson(String jsonString) throws IOException { + return JSON.getGson().fromJson(jsonString, {{{classname}}}.class); + } + + /** + * Convert an instance of {{classname}} to an JSON string + * + * @return JSON string + */ + public String toJson() { + return JSON.getGson().toJson(this); + } +} diff --git a/buildSrc/src/main/resources/vertex-java/pojo.mustache b/buildSrc/src/main/resources/vertex-java/pojo.mustache index e1f16c3..af2ed13 100644 --- a/buildSrc/src/main/resources/vertex-java/pojo.mustache +++ b/buildSrc/src/main/resources/vertex-java/pojo.mustache @@ -21,6 +21,8 @@ import java.util.Set; import {{invokerPackage}}.JSON; +// This source file was generated using a Vertex modified version of the pojo.mustache template. + /** * {{description}}{{^description}}{{classname}}{{/description}}{{#isDeprecated}} * @deprecated{{/isDeprecated}} @@ -356,7 +358,6 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens } {{^hasChildren}} {{^isAdditionalPropertiesTrue}} - Set> entries = jsonElement.getAsJsonObject().entrySet(); // check to see if the JSON string contains additional fields for (Map.Entry entry : entries) {