Question

Flutter JSON Serialization - Not generating *.g.dart files

I am new to flutter and the objective is to serialise complex JSON objects which contain other smaller objects.

Using the json_serializable: ^2.0.0 and the pubspec.yaml file looks something like this.

dependencies:
  intl: ^0.15.7
  json_annotation: ^2.0.0
  built_value: ^6.7.1
  flutter:
    sdk: flutter

dev_dependencies:
  build_runner: ^1.0.0
  json_serializable: ^2.0.0
  built_value_generator: ^6.7.1
  flutter_test:
    sdk: flutter

The user.dart look like this

import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';

@JsonSerializable(nullable: false)
class User {
  final String firstName;
  final String lastName;
  final DateTime dateOfBirth;
  User({this.firstName, this.lastName, this.dateOfBirth});
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

I have tried flutter pub run build_runner build yet the file user.g.dart doesn't get created and I am facing issue with that.

I also have added the build.yaml file with following code

targets:
  $default:
    builders:
      built_value_generator|built_value:
        generate_for:
          - model/*.dart
      json_serializable|json_serializable:
        generate_for:
          - model/*.dart

Can anyone let me know what I am missing here. Thanks

 46  62229  46
1 Jan 1970

Solution

 81

Checklist

  • pubspec.yaml includes 3 packages:
dependencies:
  json_annotation: ^4.8.1
dev_dependencies:
  build_runner: ^2.4.7
  json_serializable: ^6.7.1
  • you've run flutter pub get
    • if you get errors about versions at this stage, try older versions of json_serializable, json_annotation, build_runner
      • note the Min Dart SDK listed on those linked pages
      • your pubspec.yaml environment: sdk: ">=a.bb.c < x.y.z", your a.bb.c version must be >= than the Min Dart SDK listed on pub.dev for the package
      • if your Dart version is lower, try an older version of the json package which matches or is below your Dart version
  • your class files are underneath /lib or /bin
    • can be subdirectories under those
    • json_serializable won't search every directory for files to generate.
  • added import for json_annotation:
    • import 'package:json_annotation/json_annotation.dart';
  • you've added a part directive after import statements
    • your part file is named after your class filename (not the Class name itself), with a g added
    • e.g. for CacheItem class with ...
    • cache-item.dart class filename ...
    • part 'cache-item.g.dart'; gets corresponding part directive.
    • the part directive is not named after your actual Class, but the class file name.
    • if Android Studio Dart Analyzer continues to give errors about missing .g.dart file after you're sure the file has been generated & named correctly, shut down Android Studio and restart it (analyzer sometimes goes insane & this may fix it. Restarting just the analyzer may not work.)
  • you've added @JsonSerializable() above the class name
  • you've created a default constructor for your class.
    • It can be empty, have optional named parameters, or positional parameters.
    • As long as your class fields are accessible (either through constructor or public setters & getters), json_serializable can handle it. (i.e. don't have only _private properties and an empty constructor)
  • you've written two methods calling private stub methods:
    • a factory fromJson method
      • e.g.: factory CacheItem.fromJson(Map<String,dynamic> json) => _CacheItemFromJson(json)
    • a toJson method
      • e.g.: Map<String,dynamic> toJson() => _$CacheItemToJson(this)
    • stub methods are private (start with _ underscore)
    • $tub methods have the $
    • stub methods have proper CaSe (i.e. Pascal Case)
    • stub factory supplies (Map<String,dynamic> json) as argument
    • stub toJson() returns Map<String,dynamic>

When all that is complete, try running the generator from the command line or terminal in the project root directory...

In Flutter:

flutter pub run build_runner build

In pure Dart, this depends on your version, but one of these should work:

dart run build_runner build
pub run build_runner build
dart pub run build_runner build

If all goes well, click around in your project file explorer or Reload from disk and new files should show up such as cache-item.g.dart for the above example.

Common Errors

Bad state: Unexpected diagnostics:

Seeing this output when running the build_runner is likely a problem with flutter and json_annotation depending on an incompatible version of analyzer. This happens with json_serializable versions prior to 3.5 requiring a dependency_override of analyzer to 0.39.14 or 0.39.17.

Your first move should be to try the latest version of json_serilizable from pub.dev which apparently doesn't have this dependency problem.

If you can't upgrade json_serializable you can try placing the override lines underneath dev_dependences:

dev_dependencies:
  build_runner: ^1.9.0
  flutter_test:
    sdk: flutter
  json_serializable: 3.3.0
  test: ^1.14.3

dependency_overrides:
  analyzer: '0.39.14'

[SEVERE] Nothing can be built, yet a build was requested.

This error can happen when performing a flutter pub run build_runner build when we've added dependency in pubspec.yaml for json_annotation but are missing a dependency/dev_dependency for json_serializable:

dependencies:
  flutter:
    sdk: flutter
  get:
  json_annotation: ^4.3.0
  some_other_packages:

Make sure you've got json_serializable package added as either a dependency or dev_dependency:

dependencies:
  flutter:
    sdk: flutter
  get:
  json_annotation: ^4.3.0

dev_dependencies:
  build_runner: ^2.1.4
  flutter_test:
    sdk: flutter
  json_serializable: ^6.0.1  #// ← do not forget
  test:

[SEVERE] Conflicting outputs were detected

pub finished with exit code 78

The full error is:

[SEVERE] Conflicting outputs were detected and the build
 is unable to prompt for permission to remove them. 
These outputs must be removed manually or the build 
can be run with `--delete-conflicting-outputs`. 
The outputs are: lib/some_generated_file.g.dart

pub finished with exit code 78

If one of the generated files could not be overwritten automatically, you'll get this error.

The fix is to re-run the same build command but with the added optional argument:

flutter pub run build_runner build --delete-conflicting-outputs

This will (hopefully) allow previously generated .g.dart files to be deleted and rewritten.


Could not generate fromJsoncode forsomeField.

If you're json serializing a class that contains someField which is a Type for another custom class you've created, have you @JsonSerializable() that other custom class?

@JsonSerializable(explicitToJson: true)
class BuildingStatus {
  final Building building; // another custom class

  BuildingStatus(Building building);

  factory BuildingStatus.fromJson(Map<String,dynamic> json) => _$BuildingStatusFromJson(json);
  Map<String,dynamic> toJson() => _$BuildingStatusToJson(this);
}


/// This guy needs serialization too.
@JsonSerializable()
class Building {
  final String name;

  const Building(this.name);

  factory Building.fromJson(Map<String,dynamic> json) => _$BuildingFromJson(json);
  Map<String,dynamic> toJson() => _$BuildingToJson(this);
}

Without serializing the nested Building class, we'd see an error when running build_runner like:

Could not generate `fromJson` code for `building` because of type `Building`.

Instance of 'SomeNestedClass'

If we have nested serializable classes, we generally want the serialization to occur recursively. i.e. the nested classes are also serialized.

To do that we would annotate our containing class with explicitToJson: true like: @JsonSerializable(explicitToJson: true)

So when we toJson() our BuildingStatus instance, instead of getting:

{"building": Instance of 'Building'}

... we would get:

{"building": {"name": "Empire State"}}

Notes

Subclass / Parent Class

If your class is a child of a parent class and you want to Serialize fields/properties of child only, you can annotate only the subclass. The parent class fields will automatically be found and included in the generated class files for the subclass.

If you want to be able to serialize/deserialize both parent and child separately, go ahead and annotate the base / parent classes with @JsonSerializable as well.

e.g. filename account.dart

import 'package:json_annotation/json_annotation.dart';

part 'account.g.dart';

class AccountBase {
  int? created;
  String? username;
  String? password;
}

@JsonSerializable()
class Account extends AccountBase {
  int? id;

  Account();

  factory Account.fromJson(Map<String,dynamic> json) => _$AccountFromJson(json);
  Map<String,dynamic> toJson() => _$AccountToJson(this);
}

Produces:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'account.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

Account _$AccountFromJson(Map<String, dynamic> json) => Account()
  ..created = json['created'] as int?
  ..username = json['username'] as String?
  ..password = json['password'] as String?
  ..id = json['id'] as int?;

Map<String, dynamic> _$AccountToJson(Account instance) => <String, dynamic>{
      'created': instance.created,
      'username': instance.username,
      'password': instance.password,
      'id': instance.id,
    };

Reference & Docs

Example

import 'package:json_annotation/json_annotation.dart';

part 'cache-item.g.dart';

@JsonSerializable()
class CacheItem {
  int created;
  String keywords;
  String response;

  CacheItem(this.created, this.keywords, this.response);

  factory CacheItem.fromJson(Map<String,dynamic> json) => _$CacheItemFromJson(json);
  Map<String,dynamic> toJson() => _$CacheItemToJson(this);
}

Output

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'cache-item.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

CacheItem _$CacheItemFromJson(Map<String, dynamic> json) => CacheItem(
      json['created'] as int,
      json['keywords'] as String,
      json['response'] as String,
    );

Map<String, dynamic> _$CacheItemToJson(CacheItem instance) => <String, dynamic>{
      'created': instance.created,
      'keywords': instance.keywords,
      'response': instance.response,
    };

Example Constructor Variant

This example is the same as above except the constructor is missing some fields and has response as optional.

It's fine.

The generator will just use the public (implicit) setters after instantiating the object to assign the values.

import 'package:json_annotation/json_annotation.dart';

part 'cache-item.g.dart';

@JsonSerializable()
class CacheItem {
  int? created;
  String? keywords;
  String? response;

  CacheItem({this.response});

  factory CacheItem.fromJson(Map<String,dynamic> json) => _$CacheItemFromJson(json);
  Map<String,dynamic> toJson() => _$CacheItemToJson(this);
}

Output

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'cache-item.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

CacheItem _$CacheItemFromJson(Map<String, dynamic> json) => CacheItem(
      response: json['response'] as String?,
    )
      ..created = json['created'] as int?
      ..keywords = json['keywords'] as String?;

Map<String, dynamic> _$CacheItemToJson(CacheItem instance) => <String, dynamic>{
      'created': instance.created,
      'keywords': instance.keywords,
      'response': instance.response,
    };
2020-08-18

Solution

 22

The constructor's argument shouldn't be optional

User({this.firstName, this.lastName, this.dateOfBirth});

They should be obligatory:

User(this.firstName, this.lastName, this.dateOfBirth);

And the part

'user.g.dart';

should be matching the Uppercase User class:

part 'User.g.dart';
2019-11-02