Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) + "<String, " + getTypeDeclaration(inner) + ">";
typeDeclaration = getSchemaType(target) + "<String, " + getTypeDeclaration(inner, true) + ">";
} else {
typeDeclaration = super.getTypeDeclaration(p);
}
return super.getTypeDeclaration(p);

if (includeNullableSuffix && ModelUtils.isNullable(schema)) {
return typeDeclaration + "?";
}

return typeDeclaration;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}},
Expand All @@ -219,7 +219,7 @@ class {{{classname}}} {
: {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}']),
{{/items.complexType}}
{{^items.complexType}}
: (json[r'{{{baseName}}}'] as Map<String, dynamic>).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<String, dynamic>).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}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<File> 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)");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String?>");
}

@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<List<String?>>");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String?>");
}

@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<BuiltList<String?>>");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ Name | Type | Description | Notes
**dateProp** | [**DateTime**](DateTime.md) | | [optional]
**datetimeProp** | [**DateTime**](DateTime.md) | | [optional]
**arrayNullableProp** | **List&lt;Object&gt;** | | [optional]
**arrayAndItemsNullableProp** | **List&lt;Object&gt;** | | [optional]
**arrayItemsNullable** | **List&lt;Object&gt;** | | [optional]
**arrayAndItemsNullableProp** | **List&lt;Object?&gt;** | | [optional]
**arrayItemsNullable** | **List&lt;Object?&gt;** | | [optional]
**objectNullableProp** | **Map&lt;String, Object&gt;** | | [optional]
**objectAndItemsNullableProp** | **Map&lt;String, Object&gt;** | | [optional]
**objectItemsNullable** | **Map&lt;String, Object&gt;** | | [optional]
**objectAndItemsNullableProp** | **Map&lt;String, Object?&gt;** | | [optional]
**objectItemsNullable** | **Map&lt;String, Object?&gt;** | | [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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class NullableClass {
)


final List<Object>? arrayAndItemsNullableProp;
final List<Object?>? arrayAndItemsNullableProp;



Expand All @@ -149,7 +149,7 @@ class NullableClass {
)


final List<Object>? arrayItemsNullable;
final List<Object?>? arrayItemsNullable;



Expand All @@ -173,7 +173,7 @@ class NullableClass {
)


final Map<String, Object>? objectAndItemsNullableProp;
final Map<String, Object?>? objectAndItemsNullableProp;



Expand All @@ -185,7 +185,7 @@ class NullableClass {
)


final Map<String, Object>? objectItemsNullable;
final Map<String, Object?>? objectItemsNullable;



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ Name | Type | Description | Notes
**dateProp** | [**Date**](Date.md) | | [optional]
**datetimeProp** | [**DateTime**](DateTime.md) | | [optional]
**arrayNullableProp** | [**BuiltList&lt;JsonObject&gt;**](JsonObject.md) | | [optional]
**arrayAndItemsNullableProp** | [**BuiltList&lt;JsonObject&gt;**](JsonObject.md) | | [optional]
**arrayItemsNullable** | [**BuiltList&lt;JsonObject&gt;**](JsonObject.md) | | [optional]
**arrayAndItemsNullableProp** | [**BuiltList&lt;JsonObject?&gt;**](JsonObject.md) | | [optional]
**arrayItemsNullable** | [**BuiltList&lt;JsonObject?&gt;**](JsonObject.md) | | [optional]
**objectNullableProp** | [**BuiltMap&lt;String, JsonObject&gt;**](JsonObject.md) | | [optional]
**objectAndItemsNullableProp** | [**BuiltMap&lt;String, JsonObject&gt;**](JsonObject.md) | | [optional]
**objectItemsNullable** | [**BuiltMap&lt;String, JsonObject&gt;**](JsonObject.md) | | [optional]
**objectAndItemsNullableProp** | [**BuiltMap&lt;String, JsonObject?&gt;**](JsonObject.md) | | [optional]
**objectItemsNullable** | [**BuiltMap&lt;String, JsonObject?&gt;**](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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ Name | Type | Description | Notes
**dateProp** | [**DateTime**](DateTime.md) | | [optional]
**datetimeProp** | [**DateTime**](DateTime.md) | | [optional]
**arrayNullableProp** | **List<Object>** | | [optional] [default to const []]
**arrayAndItemsNullableProp** | **List<Object>** | | [optional] [default to const []]
**arrayItemsNullable** | **List<Object>** | | [optional] [default to const []]
**arrayAndItemsNullableProp** | **List<Object?>** | | [optional] [default to const []]
**arrayItemsNullable** | **List<Object?>** | | [optional] [default to const []]
**objectNullableProp** | **Map<String, Object>** | | [optional] [default to const {}]
**objectAndItemsNullableProp** | **Map<String, Object>** | | [optional] [default to const {}]
**objectItemsNullable** | **Map<String, Object>** | | [optional] [default to const {}]
**objectAndItemsNullableProp** | **Map<String, Object?>** | | [optional] [default to const {}]
**objectItemsNullable** | **Map<String, Object?>** | | [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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class AdditionalPropertiesClass {
mapOfMapProperty: mapCastOfType<String, dynamic>(json, r'map_of_map_property') ?? const {},
mapOfArrayInteger: json[r'map_of_array_integer'] == null
? const {}
: (json[r'map_of_array_integer'] as Map<String, dynamic>).map((k, v) => MapEntry(k, v == null ? const <int>[] : (v as List).cast<int>())),
: (json[r'map_of_array_integer'] as Map<String, dynamic>).map((k, v) => MapEntry(k, v == null ? const <int>[] : (v as List).map((value) => value as int).toList(growable: false))),
);
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class ArrayOfArrayOfNumberOnly {
return ArrayOfArrayOfNumberOnly(
arrayArrayNumber: json[r'ArrayArrayNumber'] is List
? (json[r'ArrayArrayNumber'] as List).map((e) =>
e == null ? const <num>[] : (e as List).cast<num>()
e == null ? const <num>[] : (e as List).map((value) => value as num).toList(growable: false)
).toList()
: const [],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <int>[] : (e as List).cast<int>()
e == null ? const <int>[] : (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>[] : ReadOnlyFirst.listFromJson(e)
).toList()
: const [],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ class NullableClass {

List<Object>? arrayNullableProp;

List<Object>? arrayAndItemsNullableProp;
List<Object?>? arrayAndItemsNullableProp;

List<Object> arrayItemsNullable;
List<Object?> arrayItemsNullable;

Map<String, Object>? objectNullableProp;

Map<String, Object>? objectAndItemsNullableProp;
Map<String, Object?>? objectAndItemsNullableProp;

Map<String, Object> objectItemsNullable;
Map<String, Object?> objectItemsNullable;

@override
bool operator ==(Object other) => identical(this, other) || other is NullableClass &&
Expand Down
Loading