Patrick Lumenus
Patrick Lumenus

Reputation: 1722

Flutter json_serializable Build failing

I am using json_serializable, json_annotation, and build to generate serialization/deserialization functionality for my models. When I run the build though, I get this error.

Error running JsonSerializableGenerator Cannot populate the required constructor argument: created. package:explorer/models/Account/account.dart:46:3

The line it is referring to is my model constructor which is this.

Account(String id, String firstName, String lastName, String email,
  DateTime dob, DateTime created, DateTime updated,
  {String accessTkn, String refreshTkn}) {}

Why am I getting this error?

As requested, here is my model class.

import "package:json_annotation/json_annotation.dart";

part "account.g.dart";

@JsonSerializable(nullable: true)
class Account {
  @JsonKey(name: "id")
  String _id;

  @JsonKey(name: "first_name")
  String _firstName;

  @JsonKey(name: "last_name")
  String _lastName;

  @JsonKey(name: "email")
  String _email;

  @JsonKey(
      name: "dob", fromJson: _isoStringToDateTime, toJson: _dateTimeToIsoString)
  DateTime _dob;

  @JsonKey(
      name: "created",
      fromJson: _isoStringToDateTime,
      toJson: _dateTimeToIsoString)
  DateTime _created;

  @JsonKey(
      name: "updated",
      fromJson: _isoStringToDateTime,
      toJson: _dateTimeToIsoString)
  DateTime _updated;

  @JsonKey(name: "access_token")
  String _accessToken;

  @JsonKey(name: "refresh_token")
  String _refreshToken;

  Account(String id, String firstName, String lastName, String email,
      DateTime dob, DateTime created, DateTime updated,
      {String accessTkn, String refreshTkn}) {
    this._id = id;
    this._firstName = firstName;
    this._lastName = lastName;
    this._email = email;
    this._dob = dob;
    this._created = created;
    this._updated = updated;
    this._accessToken = accessToken;
    this._refreshToken = refreshTkn;
  }

  factory Account.fromJson(Map<String, dynamic> json) {
    _$AccountFromJson(json);
  }

  // converts a DateTime to a ISO string
  static String _dateTimeToIsoString(DateTime date) {
    return date.toIso8601String();
  }

  // convert back to date time
  static DateTime _isoStringToDateTime(String iso) {
    return DateTime.parse(iso);
  }

  /// get the account id
  String get id {
    return this._id;
  }

  /// get the account first name
  String get firstName {
    return this._firstName;
  }

  /// get the account last name
  String get lastName {
    return this._lastName;
  }

  /// get the account email.
  String get email {
    return this._email;
  }

  /// get the account owner's date of birth
  DateTime get dob {
   return this._dob;
  }

  /// Get the date the account was created.
  DateTime get createdAt {
    return this._created;
  }

  /// get teh date the account was last updated.
  DateTime get updatedAt {
   return this._updated;
  }

  // get the account access token.
  String get accessToken {
    return this._accessToken;
  }

  // get the account refresh token.
  String get refreshToken {
    return this._refreshToken;
  }

  /// clones the account instance
  Account clone() {
    return Account(this.id, this.firstName, this.lastName, this.email, this.dob,
        this.createdAt, this.updatedAt,
        accessTkn: this.accessToken, refreshTkn: this.refreshToken);
  }

  Map<String, dynamic> toJson() {
    _$AccountToJson(this);
  }
}

Upvotes: 6

Views: 11862

Answers (2)

Roland van der Linden
Roland van der Linden

Reputation: 684

For future reference, I would like to explain the problem above with an example and suggest a general solution for it:

json_serializable + json_annotation use the constructor parameter names as the json field keys. So there is a distinct difference between the two examples below:

@JsonSerializable()
class User {
  @JsonKey(name: "first_name") final String firstName;

  // In this case, the json key becomes 'first_name',
  // extracted from the explicitly referenced field annotation.
  const User(this.firstName);
}

@JsonSerializable()
class User {
  @JsonKey(name: "first_name") String _firstName;
  String get firstName => _firstName?.trim();

  // In this case, the json key becomes 'firstName',
  // extracted from the constructor parameter name. 
  // For reflection, the field and its annotation are not involved.
  User(String firstName) {
    this._firstName = firstName;
  }
}

The reason we want to hide a field is twofold; We don't want others to be able to update its value, and we want to provide a 'corrected' (in this case, trimmed) value rather than the unvalidated value retrieved from an external source. Since we are unable to neatly hide the unvalidated value, I'd suggest we do expose it but explicitly mention its shortcomings, like so:

@JsonSerializable()
class User {
  // The field is final, so its value cannot be altered as if private.
  // It is exposed (sadly), but clearly mentions potential issues.
  @JsonKey(name: "first_name") final String firstNameUntrimmed;
  
  // This is the 'corrected' version available with a more pleasant field name.
  String get firstName => firstNameUntrimmed?.trim();

  const User(this.firstNameUntrimmed);
}

Upvotes: 3

dubace
dubace

Reputation: 1621

You are getting the error because you didn't initialize passed parameters, you have an empty constructor.

You have to initialize every parameter you have in your class, or allow them to be nullable with JsonSerializable(nullable: true) or JsonKey(nullable: true)

Please share all code in your class if that solution wouldn't work for you

EDIT:

The library works with reflection, I understand where was an error.

  1. Attributes should be named the same as your parameters.
  2. Your getters should be named the same as your parameters

Change your code with next fixes:

Account(String id, String firstName, String lastName, String email,
    DateTime dob, DateTime created, DateTime updated,
    String accessToken, String refreshToken) {
    this._id = id;
    this._firstName = firstName;
    this._lastName = lastName;
    this._email = email;
    this._dob = dob;
    this._created = created;
    this._updated = updated;
    this._accessToken = accessToken;
    this._refreshToken = refreshToken;
  }

  /// Get the date the account was created.
  DateTime get created {
    return this._created;
  }

  /// get teh date the account was last updated.
  DateTime get updated {
    return this._updated;
  }

Upvotes: 5

Related Questions