json_serializable.dart icon indicating copy to clipboard operation
json_serializable.dart copied to clipboard

Serializer ignores custom `fromJson` for private field

Open komape opened this issue 3 years ago • 3 comments

If a field is private and the @JsonKey annotation field fromJson is set, the serializer ignores this. Here my example model class:

part 'comment.g.dart';

@JsonSerializable()
class Comment {

  String comment;

  @JsonKey(fromJson: _dateFromJson)
  DateTime _commentedAt;

  static DateTime _dateFromJson(dynamic date) {
    // do something with the date
  }

  DateTime get commentedAt => _commentedAt;

  set commentedAt(DateTime date) => _commentedAt = date;

}

The generated comment.g.dart looks as follows:

part of 'comment.dart';

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

Comment _$CommentFromJson(Map<String, dynamic> json) {
  return Comment()
    ..comment = json['comment'] as String
    ..commentedAt = json['commentedAt'] == null
        ? null
        : DateTime.parse(json['commentedAt'] as String);
}

Map<String, dynamic> _$CommentToJson(Comment instance) => <String, dynamic>{
      'comment': instance.comment,
      'commentedAt': instance.commentedAt?.toIso8601String(),
    };

But it should look as follows:

part of 'comment.dart';

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

Comment _$CommentFromJson(Map<String, dynamic> json) {
  return Comment()
    ..comment = json['comment'] as String
    ..commentedAt = Comment._dateFromJson(json['commentedAt'])
}

Map<String, dynamic> _$CommentToJson(Comment instance) => <String, dynamic>{
      'comment': instance.comment,
      'commentedAt': instance.commentedAt?.toIso8601String(),
    };

So I have a workaround but I cannot make the field private....

Output of flutter doctor -v

[✓] Flutter (Channel stable, 1.22.6, on Linux, locale en_US.UTF-8)
    • Flutter version 1.22.6 at /home/user/tools/flutter
    • Framework revision 9b2d32b605 (3 weeks ago), 2021-01-22 14:36:39 -0800
    • Engine revision 2f0af37152
    • Dart version 2.10.5

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
    • Android SDK at /home/user/tools/Android/Sdk
    • Platform android-30, build-tools 30.0.2
    • ANDROID_HOME = /home/user/tools/Android/Sdk
    • Java binary at: /usr/local/android-studio/jre/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
    • All Android licenses accepted.

[✓] Android Studio (version 3.6)
    • Android Studio at /usr/local/android-studio
    • Flutter plugin version 49.0.1
    • Dart plugin version 192.8052
    • Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)

• No issues found!

komape avatar Feb 10 '21 17:02 komape

+1

valle-xyz avatar Apr 19 '21 13:04 valle-xyz

I'm afraid this is impossible for several reasons. I'll try to sum that up:

  1. In dart, a method or field of class beginning with _ is private, this means it is not accessible from outside, so producing a Comment._dateFromJson invocation is not possible.
  2. Since the annotation was placed on the private field _commentedAt it's impossible for the generator to map it to another field
  3. Since, I suppose, the default json_serializable settings are on, non annotated field are converted with default converters, hence you find the DateTime.parse call.

What you can actually do to achieve what you want is

@JsonSerializable()
class Comment {
  Comment({
    required this.comment,
    required DateTime commentedAt,
  }) : _commentedAt = commentedAt;

  String comment;

  DateTime _commentedAt;

  static DateTime dateFromJson(dynamic date) {
    // do something with the date
  }

  @JsonKey(fromJson: dateFromJson)
  DateTime get commentedAt => _commentedAt;

  set commentedAt(DateTime date) => _commentedAt = date;
}

Which will result in

Comment _$CommentFromJson(Map<String, dynamic> json) => Comment(
      comment: json['comment'] as String,
      commentedAt: Comment.dateFromJson(json['commentedAt']),
    );

Skogsfrae avatar Oct 07 '21 12:10 Skogsfrae

@kevmoo Maybe it's time Dart extends their accessory skills beyond 'public-or-private-only' 😉

komape avatar Jan 24 '22 10:01 komape

Duplicate of #569

Trying to align all of these requests into one place!

kevmoo avatar Nov 17 '22 23:11 kevmoo

I have a (draft) PR out on this. I'd LOVE (early) feedback about if this works for what folks need here: https://github.com/google/json_serializable.dart/pull/1256

I still need to do some documentation and testing...

kevmoo avatar Nov 22 '22 01:11 kevmoo