From 14fc61a293ef4c862671fd2651c4dfe6540df665 Mon Sep 17 00:00:00 2001 From: William Cheng Date: Tue, 20 Jun 2023 11:27:56 +0800 Subject: [PATCH] [java] fix default value - empty list `[]` (#15862) * fix empty default value in java okhttp-gson * add files --- .../languages/AbstractJavaCodegen.java | 4 +- ...points-models-for-testing-okhttp-gson.yaml | 11 + .../java/okhttp-gson/.openapi-generator/FILES | 2 + .../petstore/java/okhttp-gson/README.md | 1 + .../java/okhttp-gson/api/openapi.yaml | 11 + .../java/okhttp-gson/docs/ArrayDefault.md | 14 + .../java/org/openapitools/client/JSON.java | 1 + .../client/model/ArrayDefault.java | 334 ++++++++++++++++++ .../org/openapitools/client/JSONTest.java | 15 + .../client/model/ArrayDefaultTest.java | 57 +++ 10 files changed, 448 insertions(+), 2 deletions(-) create mode 100644 samples/client/petstore/java/okhttp-gson/docs/ArrayDefault.md create mode 100644 samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/ArrayDefault.java create mode 100644 samples/client/petstore/java/okhttp-gson/src/test/java/org/openapitools/client/model/ArrayDefaultTest.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java index 201b76c2a96..3b59e0448af 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java @@ -966,8 +966,8 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code if (schema.getDefault() instanceof ArrayNode) { // array of default values ArrayNode _default = (ArrayNode) schema.getDefault(); - if (_default.isEmpty()) { - return "null"; + if (_default.isEmpty()) { // e.g. default: [] + return "new ArrayList<>()"; } List final_values = _values; diff --git a/modules/openapi-generator/src/test/resources/3_0/java/petstore-with-fake-endpoints-models-for-testing-okhttp-gson.yaml b/modules/openapi-generator/src/test/resources/3_0/java/petstore-with-fake-endpoints-models-for-testing-okhttp-gson.yaml index 8a91d974b02..0008870a205 100644 --- a/modules/openapi-generator/src/test/resources/3_0/java/petstore-with-fake-endpoints-models-for-testing-okhttp-gson.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/java/petstore-with-fake-endpoints-models-for-testing-okhttp-gson.yaml @@ -2213,3 +2213,14 @@ components: enum: - type_a - type_b + ArrayDefault: + properties: + WithDefaultEmptyBracket: + default: [] + items: + type: string + type: array + WithoutDefault: + items: + type: string + type: array diff --git a/samples/client/petstore/java/okhttp-gson/.openapi-generator/FILES b/samples/client/petstore/java/okhttp-gson/.openapi-generator/FILES index 978674eab23..5382db19ae8 100644 --- a/samples/client/petstore/java/okhttp-gson/.openapi-generator/FILES +++ b/samples/client/petstore/java/okhttp-gson/.openapi-generator/FILES @@ -10,6 +10,7 @@ docs/Animal.md docs/AnotherFakeApi.md docs/Apple.md docs/AppleReq.md +docs/ArrayDefault.md docs/ArrayOfArrayOfNumberOnly.md docs/ArrayOfInlineAllOf.md docs/ArrayOfInlineAllOfArrayAllofDogPropertyInner.md @@ -130,6 +131,7 @@ src/main/java/org/openapitools/client/model/AdditionalPropertiesClass.java src/main/java/org/openapitools/client/model/Animal.java src/main/java/org/openapitools/client/model/Apple.java src/main/java/org/openapitools/client/model/AppleReq.java +src/main/java/org/openapitools/client/model/ArrayDefault.java src/main/java/org/openapitools/client/model/ArrayOfArrayOfNumberOnly.java src/main/java/org/openapitools/client/model/ArrayOfInlineAllOf.java src/main/java/org/openapitools/client/model/ArrayOfInlineAllOfArrayAllofDogPropertyInner.java diff --git a/samples/client/petstore/java/okhttp-gson/README.md b/samples/client/petstore/java/okhttp-gson/README.md index 4d763ed26ce..c6e92043eb7 100644 --- a/samples/client/petstore/java/okhttp-gson/README.md +++ b/samples/client/petstore/java/okhttp-gson/README.md @@ -160,6 +160,7 @@ Class | Method | HTTP request | Description - [Animal](docs/Animal.md) - [Apple](docs/Apple.md) - [AppleReq](docs/AppleReq.md) + - [ArrayDefault](docs/ArrayDefault.md) - [ArrayOfArrayOfNumberOnly](docs/ArrayOfArrayOfNumberOnly.md) - [ArrayOfInlineAllOf](docs/ArrayOfInlineAllOf.md) - [ArrayOfInlineAllOfArrayAllofDogPropertyInner](docs/ArrayOfInlineAllOfArrayAllofDogPropertyInner.md) diff --git a/samples/client/petstore/java/okhttp-gson/api/openapi.yaml b/samples/client/petstore/java/okhttp-gson/api/openapi.yaml index 191c6f2423f..37d63448ada 100644 --- a/samples/client/petstore/java/okhttp-gson/api/openapi.yaml +++ b/samples/client/petstore/java/okhttp-gson/api/openapi.yaml @@ -2206,6 +2206,17 @@ components: required: - enum_str_type type: object + ArrayDefault: + properties: + WithDefaultEmptyBracket: + default: [] + items: + type: string + type: array + WithoutDefault: + items: + type: string + type: array _foo_get_default_response: example: string: diff --git a/samples/client/petstore/java/okhttp-gson/docs/ArrayDefault.md b/samples/client/petstore/java/okhttp-gson/docs/ArrayDefault.md new file mode 100644 index 00000000000..b539cb294d7 --- /dev/null +++ b/samples/client/petstore/java/okhttp-gson/docs/ArrayDefault.md @@ -0,0 +1,14 @@ + + +# ArrayDefault + + +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +|**withDefaultEmptyBracket** | **List<String>** | | [optional] | +|**withoutDefault** | **List<String>** | | [optional] | + + + diff --git a/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/JSON.java b/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/JSON.java index f6df8fd08c5..8f3da91a013 100644 --- a/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/JSON.java +++ b/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/JSON.java @@ -232,6 +232,7 @@ public class JSON { gsonBuilder.registerTypeAdapterFactory(new org.openapitools.client.model.AdditionalPropertiesClass.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new org.openapitools.client.model.Apple.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new org.openapitools.client.model.AppleReq.CustomTypeAdapterFactory()); + gsonBuilder.registerTypeAdapterFactory(new org.openapitools.client.model.ArrayDefault.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new org.openapitools.client.model.ArrayOfArrayOfNumberOnly.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new org.openapitools.client.model.ArrayOfInlineAllOf.CustomTypeAdapterFactory()); gsonBuilder.registerTypeAdapterFactory(new org.openapitools.client.model.ArrayOfInlineAllOfArrayAllofDogPropertyInner.CustomTypeAdapterFactory()); diff --git a/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/ArrayDefault.java b/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/ArrayDefault.java new file mode 100644 index 00000000000..faf0066b0d3 --- /dev/null +++ b/samples/client/petstore/java/okhttp-gson/src/main/java/org/openapitools/client/model/ArrayDefault.java @@ -0,0 +1,334 @@ +/* + * OpenAPI Petstore + * This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\ + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.openapitools.client.model; + +import java.util.Objects; +import java.util.Arrays; +import com.google.gson.TypeAdapter; +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 java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.openapitools.client.JSON; + +/** + * ArrayDefault + */ +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class ArrayDefault { + public static final String SERIALIZED_NAME_WITH_DEFAULT_EMPTY_BRACKET = "WithDefaultEmptyBracket"; + @SerializedName(SERIALIZED_NAME_WITH_DEFAULT_EMPTY_BRACKET) + private List withDefaultEmptyBracket = new ArrayList<>(); + + public static final String SERIALIZED_NAME_WITHOUT_DEFAULT = "WithoutDefault"; + @SerializedName(SERIALIZED_NAME_WITHOUT_DEFAULT) + private List withoutDefault; + + public ArrayDefault() { + } + + public ArrayDefault withDefaultEmptyBracket(List withDefaultEmptyBracket) { + + this.withDefaultEmptyBracket = withDefaultEmptyBracket; + return this; + } + + public ArrayDefault addWithDefaultEmptyBracketItem(String withDefaultEmptyBracketItem) { + if (this.withDefaultEmptyBracket == null) { + this.withDefaultEmptyBracket = new ArrayList<>(); + } + this.withDefaultEmptyBracket.add(withDefaultEmptyBracketItem); + return this; + } + + /** + * Get withDefaultEmptyBracket + * @return withDefaultEmptyBracket + **/ + @javax.annotation.Nullable + public List getWithDefaultEmptyBracket() { + return withDefaultEmptyBracket; + } + + + public void setWithDefaultEmptyBracket(List withDefaultEmptyBracket) { + this.withDefaultEmptyBracket = withDefaultEmptyBracket; + } + + + public ArrayDefault withoutDefault(List withoutDefault) { + + this.withoutDefault = withoutDefault; + return this; + } + + public ArrayDefault addWithoutDefaultItem(String withoutDefaultItem) { + if (this.withoutDefault == null) { + this.withoutDefault = new ArrayList<>(); + } + this.withoutDefault.add(withoutDefaultItem); + return this; + } + + /** + * Get withoutDefault + * @return withoutDefault + **/ + @javax.annotation.Nullable + public List getWithoutDefault() { + return withoutDefault; + } + + + public void setWithoutDefault(List withoutDefault) { + this.withoutDefault = withoutDefault; + } + + /** + * A container for additional, undeclared properties. + * This is a holder for any undeclared properties as specified with + * the 'additionalProperties' keyword in the OAS document. + */ + private Map additionalProperties; + + /** + * Set the additional (undeclared) property with the specified name and value. + * If the property does not already exist, create it otherwise replace it. + * + * @param key name of the property + * @param value value of the property + * @return the ArrayDefault instance itself + */ + public ArrayDefault putAdditionalProperty(String key, Object value) { + if (this.additionalProperties == null) { + this.additionalProperties = new HashMap(); + } + this.additionalProperties.put(key, value); + return this; + } + + /** + * Return the additional (undeclared) property. + * + * @return a map of objects + */ + public Map getAdditionalProperties() { + return additionalProperties; + } + + /** + * Return the additional (undeclared) property with the specified name. + * + * @param key name of the property + * @return an object + */ + public Object getAdditionalProperty(String key) { + if (this.additionalProperties == null) { + return null; + } + return this.additionalProperties.get(key); + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ArrayDefault arrayDefault = (ArrayDefault) o; + return Objects.equals(this.withDefaultEmptyBracket, arrayDefault.withDefaultEmptyBracket) && + Objects.equals(this.withoutDefault, arrayDefault.withoutDefault)&& + Objects.equals(this.additionalProperties, arrayDefault.additionalProperties); + } + + @Override + public int hashCode() { + return Objects.hash(withDefaultEmptyBracket, withoutDefault, additionalProperties); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ArrayDefault {\n"); + sb.append(" withDefaultEmptyBracket: ").append(toIndentedString(withDefaultEmptyBracket)).append("\n"); + sb.append(" withoutDefault: ").append(toIndentedString(withoutDefault)).append("\n"); + sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + + public static HashSet openapiFields; + public static HashSet openapiRequiredFields; + + static { + // a set of all properties/fields (JSON key names) + openapiFields = new HashSet(); + openapiFields.add("WithDefaultEmptyBracket"); + openapiFields.add("WithoutDefault"); + + // a set of required properties/fields (JSON key names) + openapiRequiredFields = new HashSet(); + } + + /** + * Validates the JSON Object and throws an exception if issues found + * + * @param jsonObj JSON Object + * @throws IOException if the JSON Object is invalid with respect to ArrayDefault + */ + public static void validateJsonObject(JsonObject jsonObj) throws IOException { + if (jsonObj == null) { + if (!ArrayDefault.openapiRequiredFields.isEmpty()) { // has required fields but JSON object is null + throw new IllegalArgumentException(String.format("The required field(s) %s in ArrayDefault is not found in the empty JSON string", ArrayDefault.openapiRequiredFields.toString())); + } + } + // ensure the optional json data is an array if present + if (jsonObj.get("WithDefaultEmptyBracket") != null && !jsonObj.get("WithDefaultEmptyBracket").isJsonArray()) { + throw new IllegalArgumentException(String.format("Expected the field `WithDefaultEmptyBracket` to be an array in the JSON string but got `%s`", jsonObj.get("WithDefaultEmptyBracket").toString())); + } + // ensure the optional json data is an array if present + if (jsonObj.get("WithoutDefault") != null && !jsonObj.get("WithoutDefault").isJsonArray()) { + throw new IllegalArgumentException(String.format("Expected the field `WithoutDefault` to be an array in the JSON string but got `%s`", jsonObj.get("WithoutDefault").toString())); + } + } + + public static class CustomTypeAdapterFactory implements TypeAdapterFactory { + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (!ArrayDefault.class.isAssignableFrom(type.getRawType())) { + return null; // this class only serializes 'ArrayDefault' and its subtypes + } + final TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class); + final TypeAdapter thisAdapter + = gson.getDelegateAdapter(this, TypeToken.get(ArrayDefault.class)); + + return (TypeAdapter) new TypeAdapter() { + @Override + public void write(JsonWriter out, ArrayDefault value) throws IOException { + JsonObject obj = thisAdapter.toJsonTree(value).getAsJsonObject(); + obj.remove("additionalProperties"); + // serialize additional properties + if (value.getAdditionalProperties() != null) { + for (Map.Entry entry : value.getAdditionalProperties().entrySet()) { + if (entry.getValue() instanceof String) + obj.addProperty(entry.getKey(), (String) entry.getValue()); + else if (entry.getValue() instanceof Number) + obj.addProperty(entry.getKey(), (Number) entry.getValue()); + else if (entry.getValue() instanceof Boolean) + obj.addProperty(entry.getKey(), (Boolean) entry.getValue()); + else if (entry.getValue() instanceof Character) + obj.addProperty(entry.getKey(), (Character) entry.getValue()); + else { + obj.add(entry.getKey(), gson.toJsonTree(entry.getValue()).getAsJsonObject()); + } + } + } + elementAdapter.write(out, obj); + } + + @Override + public ArrayDefault read(JsonReader in) throws IOException { + JsonObject jsonObj = elementAdapter.read(in).getAsJsonObject(); + validateJsonObject(jsonObj); + // store additional fields in the deserialized instance + ArrayDefault instance = thisAdapter.fromJsonTree(jsonObj); + for (Map.Entry entry : jsonObj.entrySet()) { + if (!openapiFields.contains(entry.getKey())) { + if (entry.getValue().isJsonPrimitive()) { // primitive type + if (entry.getValue().getAsJsonPrimitive().isString()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsString()); + else if (entry.getValue().getAsJsonPrimitive().isNumber()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsNumber()); + else if (entry.getValue().getAsJsonPrimitive().isBoolean()) + instance.putAdditionalProperty(entry.getKey(), entry.getValue().getAsBoolean()); + else + throw new IllegalArgumentException(String.format("The field `%s` has unknown primitive type. Value: %s", entry.getKey(), entry.getValue().toString())); + } else if (entry.getValue().isJsonArray()) { + instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), List.class)); + } else { // JSON object + instance.putAdditionalProperty(entry.getKey(), gson.fromJson(entry.getValue(), HashMap.class)); + } + } + } + return instance; + } + + }.nullSafe(); + } + } + + /** + * Create an instance of ArrayDefault given an JSON string + * + * @param jsonString JSON string + * @return An instance of ArrayDefault + * @throws IOException if the JSON string is invalid with respect to ArrayDefault + */ + public static ArrayDefault fromJson(String jsonString) throws IOException { + return JSON.getGson().fromJson(jsonString, ArrayDefault.class); + } + + /** + * Convert an instance of ArrayDefault to an JSON string + * + * @return JSON string + */ + public String toJson() { + return JSON.getGson().toJson(this); + } +} + 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 8e042e92344..f6c6b02786a 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 @@ -574,4 +574,19 @@ public class JSONTest { z.putAdditionalProperty("new_object", t); assertEquals(z.toJson(), "{\"type\":\"plains\",\"className\":\"zebra\",\"new_key\":\"new_value\",\"new_boolean\":true,\"new_object\":{\"id\":34,\"name\":\"just a tag\"},\"from_json\":4567,\"from_json_map\":{\"nested_string\":\"nested_value\"},\"new_number\":1.23}"); } + + /** + * Test the default value in array properties. + */ + @Test + public void testDefaultValue() throws Exception { + // None of these should throw exceptions due to the list being null + // as the add*Itme method should initialise an empty list if it's set + // to null + ArrayDefault ad = new ArrayDefault(); + ad = ad.withDefaultEmptyBracket(null); + ad.addWithDefaultEmptyBracketItem("test"); + ad = ad.withoutDefault(null); + ad.addWithoutDefaultItem("hello world"); + } } diff --git a/samples/client/petstore/java/okhttp-gson/src/test/java/org/openapitools/client/model/ArrayDefaultTest.java b/samples/client/petstore/java/okhttp-gson/src/test/java/org/openapitools/client/model/ArrayDefaultTest.java new file mode 100644 index 00000000000..6d9dfb0a1a5 --- /dev/null +++ b/samples/client/petstore/java/okhttp-gson/src/test/java/org/openapitools/client/model/ArrayDefaultTest.java @@ -0,0 +1,57 @@ +/* + * OpenAPI Petstore + * This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\ + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package org.openapitools.client.model; + +import com.google.gson.TypeAdapter; +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 java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Model tests for ArrayDefault + */ +public class ArrayDefaultTest { + private final ArrayDefault model = new ArrayDefault(); + + /** + * Model tests for ArrayDefault + */ + @Test + public void testArrayDefault() { + // TODO: test ArrayDefault + } + + /** + * Test the property 'withDefaultEmptyBracket' + */ + @Test + public void withDefaultEmptyBracketTest() { + // TODO: test withDefaultEmptyBracket + } + + /** + * Test the property 'withDefaultOnly' + */ + @Test + public void withDefaultOnlyTest() { + // TODO: test withDefaultOnly + } + +}