Json_serializable.dart: Use the extension methods feature to remove need of most boilerplate

Created on 4 Nov 2019  ·  26Comments  ·  Source: google/json_serializable.dart

Starting with Dart 2.6, json_serializable has the option to create the toJson() method for the user. Although extension methods don't support constructors, json_serializable could create a static generative method instead. This means that user code would look like this:

import 'package:json_annotation/json_annotation.dart';

part 'example.g.dart';

@JsonSerializable(nullable: false)
class Person {
  final String firstName;
  final String lastName;
  final DateTime dateOfBirth;
  Person({this.firstName, this.lastName, this.dateOfBirth});
}

The generated extension might look like this:

// in example.g.dart

extension on Person {
  static Person fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);

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

// ...

Rest of the code can keep the same.

help wanted enhancement

Most helpful comment

Why are we concerned about breaking changes? This simplifies everything by a bunch. Don't make the solution extra complicated to keep from breaking. Next we will just need native struct support and get rid of this all together. 😍

All 26 comments

The extension would need to be named, and the invocation of fromJson would need to reference that name. This would be a breaking change. Instead of Person.fromJson the usage would look like PersonExtension.fromJson.

Also the auto-magic support for jsonDecode(object) won't work because the
toJson call that's wired into dart:convert won't pick up to toJson
function on the extension

On Mon, Nov 4, 2019 at 1:08 PM Nate Bosch notifications@github.com wrote:

The extension would need to be named, and the invocation of fromJson
would need to reference that name. This would be a breaking change. Instead
of Person.fromJson the usage would look like PersonExtension.fromJson.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/dart-lang/json_serializable/issues/558?email_source=notifications&email_token=AAAEFCXWLJKUM64DSBC3UY3QSCFNZA5CNFSM4JIYECLKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEDAW6WI#issuecomment-549547865,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAAEFCW5RJADZHNZ4OKHGULQSCFNZANCNFSM4JIYECLA
.

I like the general idea as the current method of having to create the methods in the source class is messy.

What about using a with statement?

It doesn't remove the need to update the source class but does make the update more elegant.
It would also be non-breaking as the old technique would still work.
In team.dart

class Team with  _TeamJson {
  Customer owner;
}

In team.g.dart

class _TeamJson {
  factory _TeamJson.fromJson(Map<String, dynamic> json) => _$TeamFromJson(json);
  Map<String, dynamic> toJson() => _$TeamToJson(this as Team);
}

Team _$TeamFromJson(Map<String, dynamic> json) {
...

The 'this as Team' expression is a little dodgy but given its generated code it will always be correct.
class _TeamJson can be auto generated in the .g.dart file.

So now the implementation is two words with _TeamJson.

Elegant and non-breaking.

A with or extends does not bring constructors. The toJson() may be solvable with this approach, the fromJson would not be.

Why are we concerned about breaking changes? This simplifies everything by a bunch. Don't make the solution extra complicated to keep from breaking. Next we will just need native struct support and get rid of this all together. 😍

At the moment, there is only one full-blown solution with zero boilerplate in the model class, based on
@eernstg reflectable library

Almost two years I've looked for a clean serialization/deserialization solution, ended up creating it on my own 😄

Awesome, @k-paxian

This package is meant to be straight-forward and meet the needs of the Dart team.

Glad you're running with something that handles more cases in a different way!

@natebosch extensions allow to write exactly Person.fromJson, aren't they?

@natebosch extensions allow to write exactly Person.fromJson, aren't they?

Hrm...not sure...

Sad that this is closed.

@natebosch extensions allow to write exactly Person.fromJson, aren't they?

No, They'd allow us to write PersonExtension.fromJson. Extension methods add method to _instances_. They do not add static methods to a class. Adding members to the static interface of a class is a separate feature requested here: https://github.com/dart-lang/language/issues/723 You can add a thumbs up to the original comment to express support.

Sad that this is closed.

It is closed because it is not actionable. If the language adds new features like the ability to add static interface extensions we can certainly revisit.

Why are we concerned about breaking changes?

That is not the only concern. The PersonExtension.fromJson call site is also a worse and less discoverable UX.

We should consider re-opening this issue given a recent idea that @DaveShuckerow implemented for a hackathon, see the cereal package.

This adds extension methods to the JsonDecoder class which are named based on the class, so for instance json.decodeUser and json.encodeUser.

We could similarly just add top level methods jsonEncodeUser and jsonDecodeUser.

Both of these approaches remove all the boilerplate except for the generated part file reference, and result in something very similar to my previous jsonAutoDecode<T> proposal.

cc @kevmoo @natebosch

Another alternative would be to create a separate package which also depends on json_annotation but provides a different builder and uses this same strategy. It could then share all the same configuration but that wouldn't require a breaking change and the current approach would still be an option for those who like it.

We should consider re-opening this issue given a recent idea that @DaveShuckerow implemented for a hackathon, see the cereal package.

Thanks for the shout-out, @jakemac53!

All, if you're interested in seeing what JSON serialization in Dart looks like with no-boilerplate extension methods, please do try package:cereal. It's not production-ready, but it is a proof-of-concept.

If we find that this approach works well, that can help us justify the work to port the functionality back to the full library.

@jakemac53 – let's talk about this when I'm back in the office.

Any update on this? I did take a look at cereal, and it is an exciting approach. However, it only handles simple cases (no inheritance or generics).

Lets do this!

I'm willing to entertain a PR here. It'd be a lot of work!

Also: what's the suggestion for handling factories?

I'm willing to entertain a PR here. It'd be a lot of work!

Also: what's the suggestion for handling factories?

Couldn't you just use a static method? That way you can list.map(SomeStruct.fromJson) instead of list.map((json) => SomeStruct.fromJson(json))

Excited to see this soon!

Just to set expectations, NO ONE in the Dart org is working on this.

If we get a very clean PR, we'll consider it.

...we're very slammed working on null safety – while many of us are dealing with our wonderful children while we work from home. 😄

It really felt like you meant to say ""wonderful children"".

I tried to implement this feature, but I encountered the below issue.

Here's the generated code.

extension PersonExt on Person {
  static Person fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
  Map<String, dynamic> toJson() => _$PersonToJson(this);
}

When json.encode(person) was invoked, the following error occurred.

NoSuchMethodError (NoSuchMethodError: Class 'Person' has no instance method 'toJson'.
Receiver: Instance of 'Person'
Tried calling: toJson())

I did some investigation, it seems that toJson() in extension cannot be found by convert library.
https://github.com/dart-lang/sdk/blob/b43714854e4d68907ab2dd033353e234a690cc6e/sdk/lib/convert/json.dart#L521

I posted test code https://github.com/google/json_serializable.dart/pull/658#issuecomment-646010737 to demo this issue.

Any suggestion here?

Thanks for the reply. Clear get it.

Was this page helpful?
0 / 5 - 0 ratings