diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java index 4d7eb4dbe7c1..f3b5d7f327d4 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java @@ -554,13 +554,17 @@ public String toDefaultValue(Schema schema) { @Override public String getTypeDeclaration(Schema p) { + return getTypeDeclaration(p, false); + } + + private String getTypeDeclaration(Schema p, boolean includeNullableSuffix) { Schema schema = unaliasSchema(p); Schema target = ModelUtils.isGenerateAliasAsModel() ? p : schema; + String typeDeclaration; if (ModelUtils.isArraySchema(target)) { Schema items = ModelUtils.getSchemaItems(schema); - return getSchemaType(target) + "<" + getTypeDeclaration(items) + ">"; - } - if (ModelUtils.isMapSchema(target)) { + typeDeclaration = getSchemaType(target) + "<" + getTypeDeclaration(items, true) + ">"; + } else if (ModelUtils.isMapSchema(target)) { // Note: ModelUtils.isMapSchema(p) returns true when p is a composed schema that also defines // additionalproperties: true Schema inner = ModelUtils.getAdditionalProperties(target); @@ -569,9 +573,16 @@ public String getTypeDeclaration(Schema p) { inner = new StringSchema().description("TODO default missing map inner type to string"); p.setAdditionalProperties(inner); } - return getSchemaType(target) + ""; + typeDeclaration = getSchemaType(target) + ""; + } else { + typeDeclaration = super.getTypeDeclaration(p); } - return super.getTypeDeclaration(p); + + if (includeNullableSuffix && ModelUtils.isNullable(schema)) { + return typeDeclaration + "?"; + } + + return typeDeclaration; } @Override diff --git a/modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache b/modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache index ddc6d6c06133..07c0b5b9b411 100644 --- a/modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache +++ b/modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache @@ -198,10 +198,10 @@ class {{{classname}}} { {{{name}}}: json[r'{{{baseName}}}'] is List ? (json[r'{{{baseName}}}'] as List).map((e) => {{#items.complexType}} - {{items.complexType}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}} + e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.complexType}}>[]{{/items.isNullable}} : {{items.complexType}}.listFromJson(e){{#uniqueItems}}.toSet(){{/uniqueItems}} {{/items.complexType}} {{^items.complexType}} - e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}>[]{{/items.isNullable}} : (e as List).cast<{{items.items.dataType}}>() + e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (e as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false) {{/items.complexType}} ).toList() : {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}}, @@ -219,7 +219,7 @@ class {{{classname}}} { : {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}']), {{/items.complexType}} {{^items.complexType}} - : (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k, v == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}>[]{{/items.isNullable}} : (v as List).cast<{{items.items.dataType}}>())), + : (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k, v == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (v as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false))), {{/items.complexType}} {{/items.isArray}} {{^items.isArray}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartClientCodegenTest.java index 14348f8854da..99211638302c 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartClientCodegenTest.java @@ -169,4 +169,18 @@ public void testRequiredNullableFieldsDoNotAssertNonNull() throws Exception { TestUtils.assertFileNotContains(modelFile.toPath(), "json[r'nickname'] != null"); } + + @Test(description = "Nullable nested arrays of complex types should preserve null entries") + public void testNullableNestedComplexArraysPreserveNullEntries() throws Exception { + List files = generateDartNativeFromSpec( + "src/test/resources/3_0/dart/dart-native-deserialization-bugs.yaml"); + + File modelFile = files.stream() + .filter(f -> f.getName().equals("complex_nested_array_nullable_model.dart")) + .findFirst() + .orElseThrow(() -> new AssertionError("complex_nested_array_nullable_model.dart not found in generated files")); + + TestUtils.assertFileContains(modelFile.toPath(), + "e == null ? null : NullableRequiredModel.listFromJson(e)"); + } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java index b8cfdcd6dde5..6ecc265588ef 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java @@ -716,4 +716,28 @@ public void testParameterUnwrappingBehavior() { Assert.assertFalse(filterParam.dataType.startsWith("Optional<"), "Query parameter should not be wrapped with Optional"); } + + @Test(description = "array items can be nullable") + public void arrayItemsCanBeNullable() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/array-nullable-items.yaml"); + final DefaultCodegen codegen = new DartClientCodegen(); + codegen.setOpenAPI(openAPI); + final ArraySchema schema = (ArraySchema) openAPI.getComponents().getSchemas().get("ArrayWithNullableItemsModel") + .getProperties() + .get("foo"); + + Assert.assertEquals(codegen.getTypeDeclaration(schema), "List"); + } + + @Test(description = "nested array items can be nullable") + public void nestedArrayItemsCanBeNullable() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/nested-array-nullable-items.yaml"); + final DefaultCodegen codegen = new DartClientCodegen(); + codegen.setOpenAPI(openAPI); + final ArraySchema schema = (ArraySchema) openAPI.getComponents().getSchemas().get("NestedArrayWithNullableItemsModel") + .getProperties() + .get("foo"); + + Assert.assertEquals(codegen.getTypeDeclaration(schema), "List>"); + } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/dio/DartDioModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/dio/DartDioModelTest.java index b23213fd74ac..0d602450214c 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/dio/DartDioModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/dio/DartDioModelTest.java @@ -492,4 +492,32 @@ public void dateDefaultValues() { Assert.assertEquals(dateTimeDefault.name, "dateTime"); Assert.assertNull(dateTimeDefault.defaultValue); } + + @Test(description = "array items can be nullable") + public void arrayItemsCanBeNullable() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/array-nullable-items.yaml"); + final DartDioClientCodegen codegen = new DartDioClientCodegen(); + codegen.additionalProperties().put(CodegenConstants.SERIALIZATION_LIBRARY, DartDioClientCodegen.SERIALIZATION_LIBRARY_BUILT_VALUE); + codegen.processOpts(); + codegen.setOpenAPI(openAPI); + final ArraySchema schema = (ArraySchema) openAPI.getComponents().getSchemas().get("ArrayWithNullableItemsModel") + .getProperties() + .get("foo"); + + Assert.assertEquals(codegen.getTypeDeclaration(schema), "BuiltList"); + } + + @Test(description = "nested array items can be nullable") + public void nestedArrayItemsCanBeNullable() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/nested-array-nullable-items.yaml"); + final DartDioClientCodegen codegen = new DartDioClientCodegen(); + codegen.additionalProperties().put(CodegenConstants.SERIALIZATION_LIBRARY, DartDioClientCodegen.SERIALIZATION_LIBRARY_BUILT_VALUE); + codegen.processOpts(); + codegen.setOpenAPI(openAPI); + final ArraySchema schema = (ArraySchema) openAPI.getComponents().getSchemas().get("NestedArrayWithNullableItemsModel") + .getProperties() + .get("foo"); + + Assert.assertEquals(codegen.getTypeDeclaration(schema), "BuiltList>"); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/dart/dart-native-deserialization-bugs.yaml b/modules/openapi-generator/src/test/resources/3_0/dart/dart-native-deserialization-bugs.yaml index bf492a594140..981378a5254d 100644 --- a/modules/openapi-generator/src/test/resources/3_0/dart/dart-native-deserialization-bugs.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/dart/dart-native-deserialization-bugs.yaml @@ -32,3 +32,13 @@ components: nickname: type: string nullable: true + ComplexNestedArrayNullableModel: + type: object + properties: + matrix: + type: array + items: + type: array + nullable: true + items: + $ref: '#/components/schemas/NullableRequiredModel' diff --git a/modules/openapi-generator/src/test/resources/3_0/nested-array-nullable-items.yaml b/modules/openapi-generator/src/test/resources/3_0/nested-array-nullable-items.yaml new file mode 100644 index 000000000000..3d7872c0a1bb --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/nested-array-nullable-items.yaml @@ -0,0 +1,28 @@ +openapi: 3.0.0 +info: + title: Nested array nullable items + version: latest +paths: + '/': + get: + operationId: operation + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/NestedArrayWithNullableItemsModel' +components: + schemas: + NestedArrayWithNullableItemsModel: + required: + - foo + properties: + foo: + type: array + items: + type: array + items: + type: string + nullable: true diff --git a/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake-json_serializable/doc/NullableClass.md b/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake-json_serializable/doc/NullableClass.md index 70ac1091d417..63d27592eec9 100644 --- a/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake-json_serializable/doc/NullableClass.md +++ b/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake-json_serializable/doc/NullableClass.md @@ -15,11 +15,11 @@ Name | Type | Description | Notes **dateProp** | [**DateTime**](DateTime.md) | | [optional] **datetimeProp** | [**DateTime**](DateTime.md) | | [optional] **arrayNullableProp** | **List<Object>** | | [optional] -**arrayAndItemsNullableProp** | **List<Object>** | | [optional] -**arrayItemsNullable** | **List<Object>** | | [optional] +**arrayAndItemsNullableProp** | **List<Object?>** | | [optional] +**arrayItemsNullable** | **List<Object?>** | | [optional] **objectNullableProp** | **Map<String, Object>** | | [optional] -**objectAndItemsNullableProp** | **Map<String, Object>** | | [optional] -**objectItemsNullable** | **Map<String, Object>** | | [optional] +**objectAndItemsNullableProp** | **Map<String, Object?>** | | [optional] +**objectItemsNullable** | **Map<String, Object?>** | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake-json_serializable/lib/src/model/nullable_class.dart b/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake-json_serializable/lib/src/model/nullable_class.dart index 0ffb6db0f33f..400e9b311e03 100644 --- a/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake-json_serializable/lib/src/model/nullable_class.dart +++ b/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake-json_serializable/lib/src/model/nullable_class.dart @@ -137,7 +137,7 @@ class NullableClass { ) - final List? arrayAndItemsNullableProp; + final List? arrayAndItemsNullableProp; @@ -149,7 +149,7 @@ class NullableClass { ) - final List? arrayItemsNullable; + final List? arrayItemsNullable; @@ -173,7 +173,7 @@ class NullableClass { ) - final Map? objectAndItemsNullableProp; + final Map? objectAndItemsNullableProp; @@ -185,7 +185,7 @@ class NullableClass { ) - final Map? objectItemsNullable; + final Map? objectItemsNullable; diff --git a/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake/doc/NullableClass.md b/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake/doc/NullableClass.md index 4ce8d5e17576..bcacecb17bb7 100644 --- a/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake/doc/NullableClass.md +++ b/samples/openapi3/client/petstore/dart-dio/petstore_client_lib_fake/doc/NullableClass.md @@ -15,11 +15,11 @@ Name | Type | Description | Notes **dateProp** | [**Date**](Date.md) | | [optional] **datetimeProp** | [**DateTime**](DateTime.md) | | [optional] **arrayNullableProp** | [**BuiltList<JsonObject>**](JsonObject.md) | | [optional] -**arrayAndItemsNullableProp** | [**BuiltList<JsonObject>**](JsonObject.md) | | [optional] -**arrayItemsNullable** | [**BuiltList<JsonObject>**](JsonObject.md) | | [optional] +**arrayAndItemsNullableProp** | [**BuiltList<JsonObject?>**](JsonObject.md) | | [optional] +**arrayItemsNullable** | [**BuiltList<JsonObject?>**](JsonObject.md) | | [optional] **objectNullableProp** | [**BuiltMap<String, JsonObject>**](JsonObject.md) | | [optional] -**objectAndItemsNullableProp** | [**BuiltMap<String, JsonObject>**](JsonObject.md) | | [optional] -**objectItemsNullable** | [**BuiltMap<String, JsonObject>**](JsonObject.md) | | [optional] +**objectAndItemsNullableProp** | [**BuiltMap<String, JsonObject?>**](JsonObject.md) | | [optional] +**objectItemsNullable** | [**BuiltMap<String, JsonObject?>**](JsonObject.md) | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/doc/NullableClass.md b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/doc/NullableClass.md index 0d4f5e68385c..d6290020f8b8 100644 --- a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/doc/NullableClass.md +++ b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/doc/NullableClass.md @@ -15,11 +15,11 @@ Name | Type | Description | Notes **dateProp** | [**DateTime**](DateTime.md) | | [optional] **datetimeProp** | [**DateTime**](DateTime.md) | | [optional] **arrayNullableProp** | **List** | | [optional] [default to const []] -**arrayAndItemsNullableProp** | **List** | | [optional] [default to const []] -**arrayItemsNullable** | **List** | | [optional] [default to const []] +**arrayAndItemsNullableProp** | **List** | | [optional] [default to const []] +**arrayItemsNullable** | **List** | | [optional] [default to const []] **objectNullableProp** | **Map** | | [optional] [default to const {}] -**objectAndItemsNullableProp** | **Map** | | [optional] [default to const {}] -**objectItemsNullable** | **Map** | | [optional] [default to const {}] +**objectAndItemsNullableProp** | **Map** | | [optional] [default to const {}] +**objectItemsNullable** | **Map** | | [optional] [default to const {}] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/additional_properties_class.dart b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/additional_properties_class.dart index 5bc1fc5c7802..ecf59f46537c 100644 --- a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/additional_properties_class.dart +++ b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/additional_properties_class.dart @@ -67,7 +67,7 @@ class AdditionalPropertiesClass { mapOfMapProperty: mapCastOfType(json, r'map_of_map_property') ?? const {}, mapOfArrayInteger: json[r'map_of_array_integer'] == null ? const {} - : (json[r'map_of_array_integer'] as Map).map((k, v) => MapEntry(k, v == null ? const [] : (v as List).cast())), + : (json[r'map_of_array_integer'] as Map).map((k, v) => MapEntry(k, v == null ? const [] : (v as List).map((value) => value as int).toList(growable: false))), ); } return null; diff --git a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/array_of_array_of_number_only.dart b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/array_of_array_of_number_only.dart index a45ea2f3af1d..ce07e0c9447b 100644 --- a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/array_of_array_of_number_only.dart +++ b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/array_of_array_of_number_only.dart @@ -53,7 +53,7 @@ class ArrayOfArrayOfNumberOnly { return ArrayOfArrayOfNumberOnly( arrayArrayNumber: json[r'ArrayArrayNumber'] is List ? (json[r'ArrayArrayNumber'] as List).map((e) => - e == null ? const [] : (e as List).cast() + e == null ? const [] : (e as List).map((value) => value as num).toList(growable: false) ).toList() : const [], ); diff --git a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/array_test.dart b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/array_test.dart index 8e73cd62b943..022d694c9e35 100644 --- a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/array_test.dart +++ b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/array_test.dart @@ -68,12 +68,12 @@ class ArrayTest { : const [], arrayArrayOfInteger: json[r'array_array_of_integer'] is List ? (json[r'array_array_of_integer'] as List).map((e) => - e == null ? const [] : (e as List).cast() + e == null ? const [] : (e as List).map((value) => value as int).toList(growable: false) ).toList() : const [], arrayArrayOfModel: json[r'array_array_of_model'] is List ? (json[r'array_array_of_model'] as List).map((e) => - ReadOnlyFirst.listFromJson(json[r'array_array_of_model']) + e == null ? const [] : ReadOnlyFirst.listFromJson(e) ).toList() : const [], ); diff --git a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/nullable_class.dart b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/nullable_class.dart index 650652778f91..7dc16ebaf5bc 100644 --- a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/nullable_class.dart +++ b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/nullable_class.dart @@ -41,15 +41,15 @@ class NullableClass { List? arrayNullableProp; - List? arrayAndItemsNullableProp; + List? arrayAndItemsNullableProp; - List arrayItemsNullable; + List arrayItemsNullable; Map? objectNullableProp; - Map? objectAndItemsNullableProp; + Map? objectAndItemsNullableProp; - Map objectItemsNullable; + Map objectItemsNullable; @override bool operator ==(Object other) => identical(this, other) || other is NullableClass &&