Giter Club home page Giter Club logo

json_serializable_example's Introduction

Json Serializable Example

This project was created to illustrate an issue you can face when migrating partially your project to dart 2.12+ (null safety), in which requires --no-sound-null-safety or //@dart=2.10 to run the project in a compatibility mode to work with legacy packages during the migration process.

Furthermore, this is also useful to learn more about @JsonKey properties.

Problem statement

Consider that you are working in dart project for a big company with hundred packages and you need to bump dart version to 2.12+ in order to make use of null safety benefits, but this process will take too long and you need to interoperate with legacy dart packages while the bump is not completed.

Old Json Serializable - No null safety support

foo.dart is a class with four fields but only two fields are nullable (in order to compare the generated code):

part 'foo.g.dart'
...
  @JsonKey(nullable: true)
  final String fooNullable;

  @JsonKey(nullable: false)
  final String foo;

  @JsonKey(nullable: true)
  final Bar barNullable;

  @JsonKey(nullable: false)
  final Bar bar;

After running the build runner to build the generated files $ make build-generators, the foo.g.dart will be generated:

part of 'foo.dart';
...
Foo _$FooFromJson(Map<String, dynamic> json) {
  return Foo(
    fooNullable: json['foo_nullable'] as String,
    foo: json['foo'] as String,
    barNullable: json['bar_nullable'] == null
        ? null
        : Bar.fromJson(json['bar_nullable'] as Map<String, dynamic>),
    bar: Bar.fromJson(json['bar'] as Map<String, dynamic>),
  );
}

As you can see, the parser function will try to get a field from json called foo_nullable and cast to String type.

If foo_nullable is absent on json (key doesn't exist), both json['foo_nullable'] and json['foo_nullable'] will be null, because the null as String is equal to null in dart version <2.12 (no null safety support).

In order to check it, you can run the old json serializable example:

$ dart old_json_serializable_example/bin/old_json_serializable_example.dart 
0: null
1: foo = null
2: foo = null

Json Serializable with null safety support

Now, consider that you already migrated foo.dart:

part 'foo.g.dart'
...
  @JsonKey()
  final String? fooNullable;

  @JsonKey()
  final String foo;

  @JsonKey()
  final Bar? barNullable;

  @JsonKey()
  final Bar bar;

The nullable: true property is not required for JsonKey because the new version uses dart null safety to ensure the nullability, because optional type will be used to cast nullable values:

part of 'foo.dart';
...
Foo _$FooFromJson(Map<String, dynamic> json) {
  return Foo(
    fooNullable: json['foo_nullable'] as String?, // Nullable type
    foo: json['foo'] as String, // Non-nullable type
    barNullable: json['bar_nullable'] == null
        ? null
        : Bar.fromJson(json['bar_nullable'] as Map<String, dynamic>),
    bar: Bar.fromJson(json['bar'] as Map<String, dynamic>),
  );
}

If we run the sound_example.dart it will works properly:

...
  try {
    print('0: ${null as String}');
  } catch (e) {
    print('0: Ops! ${e.toString()}');
  }

  final emptyJson = <String, dynamic>{};

  try {
    // ignore: omit_local_variable_types
    final String? foo = emptyJson['foo'] as String?;
    print('1: foo = $foo');

    // ignore: omit_local_variable_types
    final String bar = emptyJson['bar'] as String;
    print('1: bar = $bar');
  } catch (e) {
    print('1: Ops! ${e.toString()}');
  }
...

As the map does not contains the foo key, it will throw an error on the cast, because for dart with null safety null cannot be cast as String:

$ dart ns_json_serializable_example/bin/sound_example.dart 
0: Ops! type 'Null' is not a subtype of type 'String' in type cast
1: foo = null
1: type 'Null' is not a subtype of type 'String' in type cast

Ok, it makes sense... but... what if we run the code with --no-sound-null-safety?!

$ dart --no-sound-null-safety ns_json_serializable_example/bin/sound_example.dart
0: null
1: value.foo = null
...

Oh, no! It also makes sense, because before null safety null as String is equals to null. What could we do to ensure the project throws an error instead of propagating null?

json_serializable and json_annotations provide some useful key checks that can be used to solve this problem. Let's check the foo_fixed.dart:

part 'foo_fixed.g.dart';
...
  @JsonKey()
  final String? fooNullable;

  @JsonKey(required: true, disallowNullValue: true)
  final String foo;

  @JsonKey()
  final Bar? barNullable;

  @JsonKey(required: true, disallowNullValue: true)
  final Bar bar;
...

When you run $ make build-generators it will generate the foo_fixed.g.dart:

part of 'foo_fixed.dart';

...

FooFixed _$FooFixedFromJson(Map<String, dynamic> json) {
  $checkKeys(json,
      requiredKeys: const ['foo', 'bar'],
      disallowNullValues: const ['foo', 'bar']);
  return FooFixed(
    fooNullable: json['foo_nullable'] as String?,
    foo: json['foo'] as String,
    barNullable: json['bar_nullable'] == null
        ? null
        : Bar.fromJson(json['bar_nullable'] as Map<String, dynamic>),
    bar: Bar.fromJson(json['bar'] as Map<String, dynamic>),
  );
}

It will basically call the $checkKeys function to ensure that the required keys are present on json with a value that is not null.

So, if we run the example case again with both dart null-safety and --no-sound-null-safety it will throw an exception when the keys are absent.

...
  try {
    print('0: ${null as String}');
  } catch (e) {
    print('0: Ops! ${e.toString()}');
  }

  final emptyJson = <String, dynamic>{'bar': <String, dynamic>{}};
  try {
    final value = Foo.fromJson(emptyJson);
    print('1: value.foo = ${value.foo}');
  } catch (e) {
    print('1: Ops! ${e.toString()}');
  }

  try {
    final fixedValue = FooFixed.fromJson(emptyJson);
    print('2: fixedValue.foo = ${fixedValue.foo}');
  } catch (e) {
    print('2: Ops! ${e.toString()}');
  }
$ dart --no-sound-null-safety ns_json_serializable_example/bin/sound_example.dart
0: null
1: value.foo = null
2: Ops! Instance of 'MissingRequiredKeysException'
$ dart ns_json_serializable_example/bin/sound_example.dart
0: Ops! type 'Null' is not a subtype of type 'String' in type cast
1: Ops! type 'Null' is not a subtype of type 'String' in type cast
2: Ops! Instance of 'MissingRequiredKeysException'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.