OpenAPITools / OpenAPITools/openapi-generator

[BUG] Dart incorrect nullable when required

Open
#21,510 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

Bug Report Checklist
  • [ X] Have you provided a full/minimal spec to reproduce the issue?
  • [ X] Have you validated the input using an OpenAPI validator?
  • [ X] Have you tested with the latest master to confirm the issue still exists?
  • [ X] Have you searched for related issues/PRs?
  • [ X] What's the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

Right now nullable but required properties are marked as not nullable despite being clearly set to nullable. #21508 references the inverse issue of this with includeIfNull.

If the field is marked as nullable, then the property generated in dart should be nullable. There is no case where a field marked as nullable shouldn't, yet it is flagging repeatCycle as not nullable in dart while it clearly knows thati t was set to nullable.

openapi-generator version

7.12.0

OpenAPI declaration file content or url
"TodoDto": {
        "type": "object",
        "description": "Todo",
        "additionalProperties": false,
        "required": [
          "id",
          "createdOn",
          "createdBy",
          "callLogId",
          "subject",
          "description",
          "contacts",
          "categories",
          "reminderDateTime",
          "repeatCycle",
          "remindLocation",
          "status",
          "priority"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "createdOn": {
            "type": "string",
            "description": "Created On",
            "format": "date-time",
            "nullable": false
          },
          "createdBy": {
            "description": "Created By",
            "nullable": false,
            "$ref": "#/components/schemas/ContactRefDto"
          },
          "callLogId": {
            "type": "string",
            "description": "Call Log Referenced",
            "format": "uuid",
            "nullable": true
          },
          "subject": {
            "type": "string",
            "description": "Subject",
            "nullable": false
          },
          "description": {
            "type": "string",
            "description": "Description",
            "nullable": true
          },
          "contacts": {
            "type": "array",
            "description": "Contacts",
            "nullable": false,
            "items": {
              "$ref": "#/components/schemas/ContactRefDto"
            }
          },
          "categories": {
            "type": "array",
            "description": "Categories",
            "nullable": false,
            "items": {
              "$ref": "#/components/schemas/CategoryRefDto"
            }
          },
          "reminderDateTime": {
            "type": "string",
            "description": "Remind On",
            "format": "date-time",
            "nullable": true
          },
          "repeatCycle": {
            "description": "Repeat Cycle",
            "nullable": true,
            "$ref": "#/components/schemas/RepeatCycles"
          },
          "remindLocation": {
            "description": "Remind Location",
            "nullable": true,
            "$ref": "#/components/schemas/Location"
          },
          "status": {
            "description": "Status",
            "nullable": false,
            "$ref": "#/components/schemas/TodoStatuses"
          },
          "priority": {
            "description": "Priority",
            "nullable": false,
            "$ref": "#/components/schemas/Priorities"
          }
        }
      },

Results in:

@JsonSerializable(
  checked: true,
  createToJson: true,
  disallowUnrecognizedKeys: false,
  explicitToJson: true,
)
class TodoDto {
  /// Returns a new [TodoDto] instance.
  TodoDto({
    required this.id,
    required this.createdOn,
    required this.createdBy,
    required this.callLogId,
    required this.subject,
    required this.description,
    required this.contacts,
    required this.categories,
    required this.reminderDateTime,
    required this.repeatCycle,
    required this.remindLocation,
    required this.status,
    required this.priority,
  });

  @JsonKey(
    name: r'id',
    required: true,
    includeIfNull: true,
  )
  final String? id;

  /// Created On
  @JsonKey(
    name: r'createdOn',
    required: true,
    includeIfNull: true,
  )
  final DateTime createdOn;

  @JsonKey(
    name: r'createdBy',
    required: true,
    includeIfNull: true,
  )
  final ContactRefDto createdBy;

  /// Call Log Referenced
  @JsonKey(
    name: r'callLogId',
    required: true,
    includeIfNull: true,
  )
  final String? callLogId;

  /// Subject
  @JsonKey(
    name: r'subject',
    required: true,
    includeIfNull: true,
  )
  final String subject;

  /// Description
  @JsonKey(
    name: r'description',
    required: true,
    includeIfNull: true,
  )
  final String? description;

  /// Contacts
  @JsonKey(
    name: r'contacts',
    required: true,
    includeIfNull: true,
  )
  final List<ContactRefDto> contacts;

  /// Categories
  @JsonKey(
    name: r'categories',
    required: true,
    includeIfNull: true,
  )
  final List<CategoryRefDto> categories;

  /// Remind On
  @JsonKey(
    name: r'reminderDateTime',
    required: true,
    includeIfNull: true,
  )
  final DateTime? reminderDateTime;

  @JsonKey(
    name: r'repeatCycle',
    required: true,
    includeIfNull: true,
  )
  final RepeatCycles repeatCycle;

  @JsonKey(
    name: r'remindLocation',
    required: true,
    includeIfNull: true,
  )
  final Location remindLocation;

  @JsonKey(
    name: r'status',
    required: true,
    includeIfNull: true,
  )
  final TodoStatuses status;

  @JsonKey(
    name: r'priority',
    required: true,
    includeIfNull: true,
  )
  final Priorities priority;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is TodoDto &&
          other.id == id &&
          other.createdOn == createdOn &&
          other.createdBy == createdBy &&
          other.callLogId == callLogId &&
          other.subject == subject &&
          other.description == description &&
          other.contacts == contacts &&
          other.categories == categories &&
          other.reminderDateTime == reminderDateTime &&
          other.repeatCycle == repeatCycle &&
          other.remindLocation == remindLocation &&
          other.status == status &&
          other.priority == priority;

  @override
  int get hashCode =>
      (id == null ? 0 : id.hashCode) +
      createdOn.hashCode +
      createdBy.hashCode +
      (callLogId == null ? 0 : callLogId.hashCode) +
      subject.hashCode +
      (description == null ? 0 : description.hashCode) +
      contacts.hashCode +
      categories.hashCode +
      (reminderDateTime == null ? 0 : reminderDateTime.hashCode) +
      repeatCycle.hashCode +
      remindLocation.hashCode +
      status.hashCode +
      priority.hashCode;

  factory TodoDto.fromJson(Map<String, dynamic> json) =>
      _$TodoDtoFromJson(json);

  Map<String, dynamic> toJson() => _$TodoDtoToJson(this);

  @override
  String toString() {
    return toJson().toString();
  }
}

Generation Details
				"@openapitools/openapi-generator-cli",
				"generate",
				"-i",
				"http://localhost:16049/help/v1/openapi.json",
				"-g",
				"dart-dio",
				"-c",
				"open-generator-config.yaml",
				"--enable-post-process-file"

Steps to reproduce

Generate with the above command against the snippet provided.

Related issues/PRs

#21508

Suggest a fix

If the field is defined as "nullable": true, then the property generated 100% of the time should be nullable. Per #21508, the required array defines if it should be sent even if null, not the status as nullable. If OpenAPI had a default value then that is what it would be set to if not required and not nullable.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the issue with the provided OpenAPI schema using the dart-dio generator and open-generator-config.yaml; begin by tracing how required and nullable properties are handled. Done means fields marked nullable: true, including required fields such as repeatCycle, are generated with nullable Dart types while remaining required for serialization.

Written by the indexing model from the issue text.

Assessment

Tech stack
dart, openapi
Domain
api, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.