From d563d8fb7d351704425d5e0677b9bd445cb917f5 Mon Sep 17 00:00:00 2001 From: Edwin Nyawoli Date: Wed, 22 Jul 2026 13:22:27 +0000 Subject: [PATCH 1/3] Fix Dart native nullable number deserialization (#20238) --- .../serialization/native/native_class.mustache | 4 +++- .../codegen/dart/DartClientCodegenTest.java | 16 ++++++++++++++++ .../dart/dart-native-deserialization-bugs.yaml | 5 +++++ 3 files changed, 24 insertions(+), 1 deletion(-) 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 10defcbae9d8..33ae2497f2d1 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 @@ -361,7 +361,9 @@ class {{{classname}}} { {{^vendorExtensions.x-is-optional}} {{{name}}}: {{#isNullable}}json[r'{{{baseName}}}'] == null ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} - : {{/isNullable}}{{{datatypeWithEnum}}}.parse('${json[r'{{{baseName}}}']}'), + : {{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}json[r'{{{baseName}}}'] == null + ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} + : {{/defaultValue}}{{/required}}{{/isNullable}}{{{datatypeWithEnum}}}.parse('${json[r'{{{baseName}}}']}'), {{/vendorExtensions.x-is-optional}} {{/isNumber}} {{^isNumber}} 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 99211638302c..357f6a9a72f5 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 @@ -170,6 +170,22 @@ public void testRequiredNullableFieldsDoNotAssertNonNull() throws Exception { "json[r'nickname'] != null"); } + @Test(description = "Optional numeric fields should guard against null values before parsing") + public void testOptionalNumericFieldsGuardAgainstNull() 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("optional_number_model.dart")) + .findFirst() + .orElseThrow(() -> new AssertionError("optional_number_model.dart not found in generated files")); + + TestUtils.assertFileContains(modelFile.toPath(), + "problem: json[r'problem'] == null"); + TestUtils.assertFileContains(modelFile.toPath(), + ": num.parse('${json[r'problem']}')"); + } + @Test(description = "Nullable nested arrays of complex types should preserve null entries") public void testNullableNestedComplexArraysPreserveNullEntries() throws Exception { List files = generateDartNativeFromSpec( 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 981378a5254d..53f2172adee4 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,6 +32,11 @@ components: nickname: type: string nullable: true + OptionalNumberModel: + type: object + properties: + problem: + type: number ComplexNestedArrayNullableModel: type: object properties: From 662902c63ac6c453de1540902c75f2dea01a7aa6 Mon Sep 17 00:00:00 2001 From: Edwin Nyawoli Date: Fri, 24 Jul 2026 11:46:58 +0000 Subject: [PATCH 2/3] Update modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache This is actually valid Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --- .../dart2/serialization/native/native_class.mustache | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 33ae2497f2d1..ecae5ef6e026 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 @@ -361,9 +361,9 @@ class {{{classname}}} { {{^vendorExtensions.x-is-optional}} {{{name}}}: {{#isNullable}}json[r'{{{baseName}}}'] == null ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} - : {{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}json[r'{{{baseName}}}'] == null + : {{/isNullable}}{{^isNullable}}{{^required}}json[r'{{{baseName}}}'] == null ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} - : {{/defaultValue}}{{/required}}{{/isNullable}}{{{datatypeWithEnum}}}.parse('${json[r'{{{baseName}}}']}'), + : {{/required}}{{/isNullable}}{{{datatypeWithEnum}}}.parse('${json[r'{{{baseName}}}']}'), {{/vendorExtensions.x-is-optional}} {{/isNumber}} {{^isNumber}} From b37508c41f6ac325be2fdafdd7dd1d5d972cc721 Mon Sep 17 00:00:00 2001 From: Edwin Nyawoli Date: Tue, 28 Jul 2026 22:04:02 +0000 Subject: [PATCH 3/3] Update samples --- .../lib/model/fake_big_decimal_map200_response.dart | 4 +++- .../dart2/petstore_client_lib_fake/lib/model/number_only.dart | 4 +++- .../lib/model/object_with_deprecated_fields.dart | 4 +++- .../petstore_client_lib_fake/lib/model/outer_composite.dart | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/fake_big_decimal_map200_response.dart b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/fake_big_decimal_map200_response.dart index 86eff453eff9..281cca5a8c98 100644 --- a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/fake_big_decimal_map200_response.dart +++ b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/fake_big_decimal_map200_response.dart @@ -67,7 +67,9 @@ class FakeBigDecimalMap200Response { }()); return FakeBigDecimalMap200Response( - someId: num.parse('${json[r'someId']}'), + someId: json[r'someId'] == null + ? null + : num.parse('${json[r'someId']}'), someMap: mapCastOfType(json, r'someMap') ?? const {}, ); } diff --git a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/number_only.dart b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/number_only.dart index 124d620f5834..924e3d4f052f 100644 --- a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/number_only.dart +++ b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/number_only.dart @@ -61,7 +61,9 @@ class NumberOnly { }()); return NumberOnly( - justNumber: num.parse('${json[r'JustNumber']}'), + justNumber: json[r'JustNumber'] == null + ? null + : num.parse('${json[r'JustNumber']}'), ); } return null; diff --git a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/object_with_deprecated_fields.dart b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/object_with_deprecated_fields.dart index 4df1a906d978..78ecdc40fbb0 100644 --- a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/object_with_deprecated_fields.dart +++ b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/object_with_deprecated_fields.dart @@ -100,7 +100,9 @@ class ObjectWithDeprecatedFields { return ObjectWithDeprecatedFields( uuid: mapValueOfType(json, r'uuid'), - id: num.parse('${json[r'id']}'), + id: json[r'id'] == null + ? null + : num.parse('${json[r'id']}'), deprecatedRef: DeprecatedObject.fromJson(json[r'deprecatedRef']), bars: json[r'bars'] is Iterable ? (json[r'bars'] as Iterable).cast().toList(growable: false) diff --git a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/outer_composite.dart b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/outer_composite.dart index ee0cb9628b82..9efe7578ce53 100644 --- a/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/outer_composite.dart +++ b/samples/openapi3/client/petstore/dart2/petstore_client_lib_fake/lib/model/outer_composite.dart @@ -93,7 +93,9 @@ class OuterComposite { }()); return OuterComposite( - myNumber: num.parse('${json[r'my_number']}'), + myNumber: json[r'my_number'] == null + ? null + : num.parse('${json[r'my_number']}'), myString: mapValueOfType(json, r'my_string'), myBoolean: mapValueOfType(json, r'my_boolean'), );