google / json_serializable.dart

Generates utilities to aid in serializing to/from JSON.
https://pub.dev/packages/json_serializable
BSD 3-Clause "New" or "Revised" License
1.55k stars 397 forks source link

Handling empty nested json objects #1370

Open blechlem opened 10 months ago

blechlem commented 10 months ago

flutter --version output: Flutter 3.10.5 • channel stable • https://github.com/flutter/flutter.git Framework • revision 796c8ef792 (5 months ago) • 2023-06-13 15:51:02 -0700 Engine • revision 45f6e00911 Tools • Dart 3.0.5 • DevTools 2.23.1

My issue is basically the same as #538 , but I am not satisfied with the reasoning for closing the issue.

Suppose I have two classes, a parent class:

import 'package:json_annotation/json_annotation.dart';
import 'package:playground/child_object.dart';

part 'parent_object.g.dart';

@JsonSerializable(explicitToJson: true)
class ParentObject {

  @JsonKey(name: 'child')
  final ChildObject? child;

  ParentObject({this.child});

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

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

and a child class:

import 'package:json_annotation/json_annotation.dart';

part 'child_object.g.dart';

@JsonSerializable(explicitToJson: true)
class ChildObject {
  @JsonKey(name: 'testString')
  final String? testString;

  ChildObject({this.testString});

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

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

If I try to convert

Map<String, dynamic> testJson = {
  "child": {}
};

via ParentObject.fromJson(testJson); into my class I get the following exception:

Unhandled exception: type '_Map<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>' in type cast

0 _$ParentObjectFromJson (package:playground/parent_object.g.dart:12:48)

1 new ParentObject.fromJson (package:playground/parent_object.dart:14:63)

2 main (package:playground/main.dart:4:31)

3 _delayEntrypointInvocation. (dart:isolate-patch/isolate_patch.dart:296:19)

4 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:189:12)

because an empty {} is per default of type Map<dynamic, dynamic> and the cast in the autogenerated file subsequently throws an exception


ParentObject _$ParentObjectFromJson(Map<String, dynamic> json) => ParentObject(
      child: json['child'] == null
          ? null
          : ChildObject.fromJson(json['child'] as Map<String, dynamic>),
    );

Since my testJson is in a valid json format I do not think it is reasonable to expect the backend to change (which was the reasoning why issue #538 was closed).

If I change the autogenerated code and add an explicit cast

ParentObject _$ParentObjectFromJson(Map<String, dynamic> json) => ParentObject(
      child: json['child'] == null
          ? null
          : ChildObject.fromJson((json['child'] as Map).cast<String, dynamic>()),
    );

it seems to work and my result is a ParentObject with an ChildObject, where the 'testString' field in the ChildObject is null.

Additionally one could add a treatEmptyAsNull property as suggested in #538

If I change the autogenerated code and add a check to see if the map is empty

ParentObject _$ParentObjectFromJson(Map<String, dynamic> json) => ParentObject(
      child: json['child'] == null || (json['child'] as Map).isEmpty
          ? null
          : ChildObject.fromJson((json['child'] as Map).cast<String, dynamic>()),
    );

my result is a ParentObject where its ChildObject has value null

Are there any issues I don't recognize with the solutions I provide? Or is it possible to adapt the autogenerated code so that it can handle empty json objects

Thanks in advance