Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extension types to JSON page #5605

Open
MaryaBelanger opened this issue Feb 28, 2024 · 6 comments
Open

Add extension types to JSON page #5605

MaryaBelanger opened this issue Feb 28, 2024 · 6 comments
Assignees
Labels
a.language Relates to the Dart language tour d.enhancement Improves docs with specific ask e2-days Can complete in < 5 days of normal, not dedicated, work from.team Reported by Dash docs team member p3-low Valid but not urgent concern. Resolve when possible. Encourage upvote to surface.

Comments

@MaryaBelanger
Copy link
Contributor

MaryaBelanger commented Feb 28, 2024

Extension types are an option for JSON serialization/deserialization

Example from @jakemac53:

extension type User(Map<String, Object?> json) {
  String get name => json['name'] as String;
}

We could add "extension types" as a bullet on the JSON page, but we'd need to add another category since it doesn't fit under "Libraries", "Flutter", or "Web app". Maybe "Language"?

One option is to add a section to the extension types page under "Usage" that describes using them for JSON serialization. (Leaning towards this)

Or, we could include the above example or something similar just directly on that JSON page. Wouldn't leave a lot of room for elaboration, but maybe elaboration in this case is not too important.

@eernstg if you have any insights on a good way to do this!


Edit: We could add a "Manual serialization" section to the page and include both this extension types content, plus a link to the Patterns and records codelab that uses json deserialization as its example.

@MaryaBelanger MaryaBelanger self-assigned this Feb 28, 2024
@jakemac53
Copy link
Contributor

I think a section similar to this one would be great, https://docs.flutter.dev/data-and-backend/serialization/json#serializing-json-inside-model-classes.

On a related note, these pages really are not flutter specific, they could just as easily be on the dart site? But maybe moving it is hard, or it is there for visibility reasons or something.

@parlough parlough added d.enhancement Improves docs with specific ask a.language Relates to the Dart language tour p3-low Valid but not urgent concern. Resolve when possible. Encourage upvote to surface. e2-days Can complete in < 5 days of normal, not dedicated, work from.team Reported by Dash docs team member labels Feb 28, 2024
@MaryaBelanger
Copy link
Contributor Author

these pages really are not flutter specific, they could just as easily be on the dart site

Hmm that's a good point, it's definitely 99% Dart content. I'm sure it's there for visibility and moving it at this point would require a lot of redirecting and maybe be confusing for users...

But, it would be a great place to add this extension types content and any future major updates to the json serialization story in Dart. I think it's important to keep all that information together.

I think for now I'll just add a similar section to Dart's JSON page, but I'll raise consolidating/moving that page with the Flutter docs team.

@jakemac53
Copy link
Contributor

Yeah I definitely understand not wanting to move it - I do think it's a nice page though and I don't think we have an equivalent on the Dart site today? We could even link to it from the Dart site?

@parlough
Copy link
Member

I originally planned to replace our JSON content on dart.dev in #4286 in a similar fashion to the fetch data article, but I guess I never got to that...

I think for now I'll just add a similar section to Dart's JSON page.

I think this makes sense!

but I'll raise consolidating/moving that page with the Flutter docs team.

That was the plan once I finally worked on that new JSON content, but doing so still makes sense :)

Then the Flutter site's version could link to the consolidated dart.dev docs as a linked prerequisite, and then focus on how it can be integrated with or used in a Flutter application.

@MaryaBelanger
Copy link
Contributor Author

That was the plan once I finally worked on that new JSON content

Oh ok great! So @jakemac53 sounds like it's safe to move or at least pare down the Flutter page in favor of having that content on Dart's, and we'll continue that work in @parlough's existing issue. I'll keep this issue open just for adding the extension types section on JSON serialization in the mean time.

Thanks all!

@eernstg
Copy link
Member

eernstg commented Feb 29, 2024

About the original consideration (using extension types to handle JSON data safely), here's an example that illustrates the basic idea that I've mentioned on earlier occasions:

The starting point would be that we have some information about the expected structure of the JSON data. For instance, we might have a JSON schema. Here's something that resembles a JSON schema (I'm skipping stuff like "$id", "$schema", "description", and just including the elements of interest to us):

{
  "title": "StreetAddress",
  "type": "object",
  "properties": {
    "number": { "type": "number" },
    "street_name": { "type": "string" },
    "street_type": { "enum": ["Street", "Avenue", "Boulevard"] }
  }
}

In general we would have several schemas and they would depend on each other. That's a straightforward generalization, so I'll just go with one schema.

Data which is expected to conform to this schema would be received as text, and we'd use something like jsonDecode to obtain a representation of the data consisting of objects of the types Map<String, Object?>, List<Object?>, int, double, String, bool, Null. Note that this object graph is weakly typed (we have to downcast all the time when we navigate it).

We then use extension types (plus perhaps some enumerated types) to navigate the weakly typed object graph in a statically typed manner. I'd expect these declarations to be produced by code generation in many cases, but we can of course also write them manually:

import 'dart:convert';

enum StreetType {
  street,
  avenue,
  boulevard;

  factory StreetType.fromJson(String json) {
    return switch (json) {
      "Street" => street,
      "Avenue" => avenue,
      "Boulevard" => boulevard,
      _ => throw "Bad JSON, expected StreetType"
    };
  }
}

extension type StreetAddress.fromJson(Map<String, Object?> it) {
  factory StreetAddress.fromSource(String source) {
    Object? json = jsonDecode(source);
    if (json is! Map<String, Object?>) {
      throw "Invalid JSON, expected StreetAddress";
    }
    return StreetAddress.fromJson(json);
  }

  int get number => it["number"] as int;
  String get streetName => it["street_name"] as String;
  StreetType get streetType => StreetType.fromJson(it["street_type"] as String);
}

The point is that the weakly typed object graph is "wrapped" by the extension types and enums, and the navigation is now statically typed. If something is wrong in the weakly typed object graph then we'll handle it in one place, which is in this wrapping layer.

We can now use the JSON data in a statically type checked manner:

void main() {
  var source = """
    {
      "number": 1600,
      "street_name": "Pennsylvania",
      "street_type": "Avenue"
    }""";
  var address = StreetAddress.fromSource(source);

  var suffix = switch (address.streetType) {
    StreetType.street => 'St',
    StreetType.avenue => 'Ave',
    StreetType.boulevard => 'Blvd'
  };
  print('Neighbour: ${address.number + 2} ${address.streetName} $suffix.');
}

This approach is useful if the navigation of the weakly typed object graph occurs a limited number of times, or perhaps not at all in some parts because we're only performing some lookups into the data, and then discarding it without looking at the rest.

We should note, though, that this approach will use a Map lookup and a dynamic type check for every member access. If we're going to navigate the JSON data millions of times then it's a better trade off to create the typed version of the JSON data as regular Dart class instances, where navigation is a mere instance variable lookup.

The main benefit of using extension types is that we would have very performant support for obtaining the weakly typed object graph (so we do want to use something like jsonDecode), and we don't want to create a complete copy of the entire weakly typed object graph in order to build a typed version of it. With extension types we can do this: We get the statically checked typing, and we don't create a single object in addition to the ones returned by jsonDecode (even the enum objects are a one-time cost that has nothing to do with the size of data).

It could make sense for the code generation to generate the extension types as described above, and also generate regular Dart classes (same thing, essentially, just using class rather than extension type), and then generate some members in the extension types that would deliver the class version. We then have the option to work on a given amount of JSON data using the extension types if we're only going to use them sparsely, and we can create the class based version if we're going to navigate the structure intensively.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a.language Relates to the Dart language tour d.enhancement Improves docs with specific ask e2-days Can complete in < 5 days of normal, not dedicated, work from.team Reported by Dash docs team member p3-low Valid but not urgent concern. Resolve when possible. Encourage upvote to surface.
Projects
None yet
Development

No branches or pull requests

4 participants