Json_serializable.dart: Generic argument factories not working for nullable properties with null-safety

Created on 17 Feb 2021  Â·  12Comments  Â·  Source: google/json_serializable.dart

When using generic argument factories for properties with a nullable generic type, the generated code expects the value to be non-null.

Example

Source file

import 'package:json_annotation/json_annotation.dart';

part 'nullable_generic.g.dart';

@JsonSerializable(genericArgumentFactories: true)
class NullableGenericExample<T> {
  NullableGenericExample({this.maybeNull});

  factory NullableGenericExample.fromJson(
          Map<String, dynamic> json, T Function(Object? json) fromJsonT) =>
      _$NullableGenericExampleFromJson<T>(json, fromJsonT);

  final T? maybeNull;
}

Generated File

The generated file attempts to call fromJsonT and toJsonT regardless of whether or not the property is null. In the case of toJsonT, this will fail to compile when using null-safety as T? is being assigned to T.

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'nullable_generic.dart';

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

NullableGenericExample<T> _$NullableGenericExampleFromJson<T>(
  Map<String, dynamic> json,
  T Function(Object? json) fromJsonT,
) {
  return NullableGenericExample<T>(
    maybeNull: fromJsonT(json['maybeNull']),
  );
}

Map<String, dynamic> _$NullableGenericExampleToJson<T>(
  NullableGenericExample<T> instance,
  Object Function(T value) toJsonT,
) =>
    <String, dynamic>{
      'maybeNull': toJsonT(instance.maybeNull),
    };

Expected Output

Something like this would fix the compile errors and prevent fromJsonT and toJsonT being called when the value is null:

NullableGenericExample<T> _$NullableGenericExampleFromJson<T>(
  Map<String, dynamic> json,
  T Function(Object? json) fromJsonT,
) {
  return NullableGenericExample<T>(
    maybeNull: json['maybeNull'] != null ? fromJsonT(json['maybeNull']) : null,
  );
}

Map<String, dynamic> _$NullableGenericExampleToJson<T>(
  NullableGenericExample<T> instance,
  Object Function(T value) toJsonT,
) =>
    <String, dynamic>{
      'maybeNull': instance.maybeNull != null ? toJsonT(instance.maybeNull!) : null,
    };

Flutter version output:

Flutter 1.26.0-17.5.pre • channel beta • https://github.com/flutter/flutter.git
Framework • revision 1fe38dcb5f (7 days ago) • 2021-02-10 16:25:47 -0800       
Engine • revision d4453f6018
Tools • Dart 2.12.0 (build 2.12.0-259.9.beta)
bug

Most helpful comment

All 12 comments

I have same issue.

Expected Output

NullableGenericExample<T> _$NullableGenericExampleFromJson<T>(
  Map<String, dynamic> json,
  T Function(Object json) fromJsonT,
) {
  return NullableGenericExample<T>(
    maybeNull: json['maybeNull'] != null ? fromJsonT?.call(json['maybeNull']) : null,
  );
}

Map<String, dynamic> _$NullableGenericExampleToJson<T>(
  NullableGenericExample<T> instance,
  Object Function(T value) toJsonT,
) =>
    <String, dynamic>{
      'maybeNull': instance.maybeNull != null ? toJsonT?.call(instance.maybeNull) : null,
    };

Your fromJson functions need to handle the null case. That's the intended behavior.

@kevmoo With the code that's currently being generated I don't believe it's possible to handle the null case in the fromJson and toJson functions, as they both expect the generic type to be non-null.

To handle the null case in fromJson and toJson, I think the generated code needs to be something like this:

NullableGenericExample<T> _$NullableGenericExampleFromJson<T>(
  Map<String, dynamic> json,
  T? Function(Object? json) fromJsonT,
) {
  return NullableGenericExample<T>(
    maybeNull: fromJsonT(json['maybeNull']),
  );
}

Map<String, dynamic> _$NullableGenericExampleToJson<T>(
  NullableGenericExample<T> instance,
  Object? Function(T? value) toJsonT,
) =>
    <String, dynamic>{
      'maybeNull': toJsonT(instance.maybeNull),
    };

Oh! Will look...

I am also encountering the same issue. The problem seems to be a as List cast here:

    shard: Tuple2Ext.fromJsonNullable(json['shard'] as List),

@kevmoo @anirudhb what you observing is reported in #806 by me as well. Seems like both of this can be linked issues. @kevmoo this is happening in https://github.com/google/json_serializable.dart/blob/master/json_serializable/lib/src/decode_helper.dart the default value needs to be passed into the Helpers for expression creation rather then outside. If you want based on my investigation i can submit a pull request?

@kevmoo @anirudhb the fix seems to be massive! I need this, so I am working thru it. Just wanted to make sure I am not wasting time. If @kevmoo you are already on it.

I wish you luck. It's gnarly. I hope to get to it this week...but you're
welcome to take a stab.

On Sat, Feb 27, 2021 at 12:35 PM Pratik Parikh notifications@github.com
wrote:

@kevmoo https://github.com/kevmoo @anirudhb
https://github.com/anirudhb the fix seems to be massive! I need this,
so I am working thru it. Just wanted to make sure I am not wasting time. If
@kevmoo https://github.com/kevmoo you are already on it.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/google/json_serializable.dart/issues/803#issuecomment-787131488,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAAEFCTXGNBNDZPU2NAHDRLTBFJRRANCNFSM4XZEJTOQ
.

@kevmoo I tired to add full support for null-safety as it seemed like this change is quite intertwined with it. But now I can't get it due to other packages are not yet supporting null-safety fully.

Unable to spawn isolate: Error: Cannot run with sound null safety, because the following dependencies
don't support null safety:

I guess will have to stash the changes for now till other packages are fully supporting null safety.

@pratikpparikh – keep in mind, supporting the feature you want and migrating the json_serializable code to run w/ null safety are different things.

I need to make a blog post about this, I think.

@kevmoo I understand that is why I am back to just moving only on the feature. I initially thought, if I can help and do both as it seems doable but I was wrong. On the same page, hope you won't use this as an example :(

another reason of trying to give null safety a shot was desire to use 2.12 sdk and stop maintaining multiple branches of codebase.

Was this page helpful?
0 / 5 - 0 ratings