From d7f048d6b26fb4a09b1135571d4f1626b81ba70d Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Thu, 4 Jan 2018 17:23:09 +0200 Subject: [PATCH 01/11] Initial draft for the JSON article. --- _includes/sidebar.html | 1 + json.md | 301 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 302 insertions(+) create mode 100644 json.md diff --git a/_includes/sidebar.html b/_includes/sidebar.html index 72fe2a3cf99..41b3a2fdfe0 100644 --- a/_includes/sidebar.html +++ b/_includes/sidebar.html @@ -38,6 +38,7 @@
  • Platform-specific code
  • Read and write files
  • Network and HTTP
  • +
  • Serializing JSON objects
  • Firebase for Flutter - Codelab
  • diff --git a/json.md b/json.md new file mode 100644 index 00000000000..11814c8bdbd --- /dev/null +++ b/json.md @@ -0,0 +1,301 @@ +--- +layout: page +title: TODO - JSON article title +permalink: /json/ +--- + +It is hard to think of a mobile app that doesn't need to communicate with a web server at some point. When making network-connected apps, chances are that we need to consume some good old JSON, sooner or later. + +In this tutorial, we look into ways of doing JSON with Flutter. We will go over what JSON solution to use in different scenarios and why. + +* TOC Placeholder +{:toc} + +## Which JSON serialization method is right for me? + +There isn't a one size fits all JSON solution for every project. + +Different projects come with different complexities and use cases. For smaller proof-of-concept projects or quick prototypes, using code generators might be overkill. For apps with several JSON models with more complexity, doing serialization by hand can quickly become tedious, repetitive and lend itself to lots of small errors. + +### Use manual serialization for smaller projects + +Manual JSON serialization refers to using the built-in JSON decoder in `dart:convert`. It involves passing the raw JSON string to the `JSON.decode()` method, and then looking up the values you need in the `Map` the method returns. It involves no external dependencies or particular setup process, and it is good for quick proof of concepts. + +Where the manual serialization does not perform well is when your project becomes bigger. Writing the serialization logic by hand can become hard to manage and error-prone. If you have a typo when accessing an unexisting JSON field, your code throws an error during runtime. + +If you do not have many JSON models in your project and are looking to test a concept quickly, manual serialization might be the way you want to start. + +### Use code generation for medium to large projects + +JSON serialization with code generation means having an external library generate the serialization boilerplate for you. They involve some initial setup and running a file watcher that generates the code from your model classes. For example, [json_serializable](https://pub.dartlang.org/packages/json_serializable) and [built_value](https://pub.dartlang.org/packages/built_value) are these kind of libraries. + +This approach scales well for a larger project. There are no hand-written boilerplate needed, and typos when accessing JSON fields are caught at compile-time. The downside with code generation is that it involves some initial setup. Also, the generated source files may produce visual clutter in your project navigator + +You might want to use generated code for JSON serialization when you have a medium or a larger project. + +## Is there a GSON/Jackson/Moshi equivalent in Flutter? + +Dart has supported _tree shaking_ for quite a long time. With tree shaking, we can “shake off” unused code from our release builds. Tree shaking allows us to optimize the size of our applications significantly. + +While Dart users might have gotten used to [dartson](https://pub.dartlang.org/packages/dartson) to (de)serialize their JSON, it does not work with Flutter. The reason behind this is that `dartson`, like [GSON](https://github.com/google/gson) in Java, uses _runtime reflection_. In Flutter, reflection (also known as `dart:mirrors`) is disabled. + +Since reflection makes all code implicitly used by default, it interferes with tree shaking. The tools cannot know what parts are unused at runtime; the redundant code is impossible to strip away. App sizes cannot be optimized when using reflection. + +Although we cannot use dartson, there’s a library that gives us a simple to use API, without using reflection. That library is called [json_serializable](https://pub.dartlang.org/packages/json_serializable). + +## Serializing JSON manually using dart:convert + +Basic JSON serialization in Flutter is very simple. Dart comes with the `dart:convert` library built in, and using the JSON decoder API is straightforward. + +Let's say we need to deserialize a JSON representing a user object. For sample purposes, we have a real simple JSON model. + +```json +{ + "name": "John Smith", + "email": "john@example.com" +} +``` + +With `dart:convert`, we can serialize this JSON model in two ways. Let's have a look at both. + +### Serializing JSON inline + +By looking at [the dart:convert JSON documentation](https://api.dartlang.org/stable/1.24.3/dart-convert/JsonCodec-class.html), we see that we can parse the JSON by calling the `JSON.decode` method, with our JSON string as the method argument. + +```dart +Map user = JSON.decode(json); + +print('Howdy, ${user['name']}!'); +print('We sent the verification link to ${user['email']}.'); +``` + +Unfortunately, `JSON.decode()` merely returns a `Map`, meaning that we do not know the types of the values until runtime. With this approach, we lose most of the statically typed language features: type safety, autocompletion and most importantly, compile-time exceptions. Our code can become instantly more error-prone. + +For example, whenever we access the `name` or `email` fields, we could quickly introduce a typo. A typo which our compiler does not know of since our entire JSON merely lives in a map structure. + +### Serializing JSON inside model classes + +We can combat the previously mentioned problems by introducing a plain model class, which we call `User`. Inside the `User` class, we have: + +* a `User.fromJson` constructor, for constructing a new `User` instance from a map structure +* a `toJson` method, which converts a `User` instance into a map. + +This way, the calling code can now have type safety, autocompletion for the `name` and `email` fields and compile-time exceptions. If we make typos or treat the fields as `int`s instead of `String`s, instead of crashing on runtime, our app will not even compile. + +**user.dart** + +```dart +class User { + User(this.name, this.email); + + String name; + String email; + + User.fromJson(Map json) + : name = json['name'], + email = json['email']; + + Map toJson() { + return { + 'name': name, + 'email': email, + }; + } +} +``` + +Now the calling code does not have to care about the serialization logic. With this new approach, we can deserialize a user quite easily. + +```dart +Map map = JSON.decode(json); +User user = new User.fromJson(map); + +print('Howdy, ${user.name}!'); +print('We sent the verification link to ${user.email}.'); +``` + +To serialize a user, we just call the `toJson` method in the `User` class. + +```dart +String json = JSON.encode(user.toJson()); +``` + +While with this new approach the calling code does not have to worry about JSON serialization at all, the model class still definitely has. In a production app, we would want to be sure that the serialization works. In practice, this means that the `User.fromJson` and `User.toJson` both need to have unit tests in place to verify correct behavior. + +Also, real-world scenarios are not usually that simple. It is unlikely that we can get by with such small JSON responses. Nested JSON objects are not that uncommon either. + +It would be nice if there were something that handled the JSON serialization for us. Luckily, there is! + +## Serializing JSON using json_serializable + +The `json_serializable` package is an automated source code generator that can generate the JSON serializing boilerplate for us. + +Since the serialization code is not handwritten and maintained by us anymore, we minimize the risk of having JSON serialization exceptions at runtime. If the sources are generated, and the app compiles, we should be fine. + +### How does a json_serializable class look? + +Let's see how to convert our `User` class to a `json_serializable` one. For the sake of simplicity, we use the dumbed-down JSON model from the previous samples. + +**user.dart** + +{% prettify dart %} +/// This allows the generated code access our class members. +/// The value for this is the same as the source file name, +/// in this case, user.dart without the .dart file extension. +library [[highlight]]user[[/highlight]]; + +import 'package:json_annotation/json_annotation.dart'; + +/// This allows our `User` class to access private members in +/// the generated file. The value for this is *.g.dart, where +/// the star denotes the source file name. +part '[[highlight]]user[[/highlight]].g.dart'; + +/// An annotation for the code generator to know that this class needs the +/// source generator to generate JSON serialization logic. +[[highlight]]@JsonSerializable()[[/highlight]] + +/// Every json_serializable class must have the serializer mixin. +/// It makes the generated toJson() method to be usable for the class. +/// The mixin's name follows the source class, in this case, User. +class User extends Object with _$[[highlight]]User[[/highlight]]SerializerMixin { + User(this.name, this.email); + + String name; + String email; + + /// A necessary factory constructor for creating a new User instance + /// from a map. We pass the map to the generated _$UserFromJson constructor. + /// The constructor is named after the source class, in this case User. + factory User.fromJson(Map json) => _$[[highlight]]User[[/highlight]]FromJson(json); +} +{% endprettify %} + +To deserialize a JSON string `json_serializable` way, we do not have actually to make any changes to our previous code. + +```dart +Map map = JSON.decode(json); +User user = new User.fromJson(map); +``` + +Same goes for serialization. The calling API is the same as before. + +```dart +String json = JSON.encode(user.toJson()); +``` + +With `json_serializable`, we can forget any manual JSON serialization in the `User` class. The source code generator creates a file called `user.g.dart`, which has all the necessary serialization logic. Now we do not necessarily have to write automated tests to be sure that the serialization works - it is now _the library's responsibility_ to make sure the serialization works appropriately. + +Now that we have learned how the model classes look, let's go through the required steps to introduce `json_serializable` to our project. + +### Setting up json_serializable in a project + +To have `json_serializable` up and running, we have to go through some steps first. We need to do this only ** once per project**. After the initial setup, creating JSON model classes is trivial. + +#### Setting up the pubspec dependencies + +To include `json_serializable` in our project, we need one regular and two _dev\ dependencies_. In short, _dev dependencies_ are dependencies that are not included in our app source code. + +The latest versions of these required dependencies can be seen by following [this link](https://github.com/dart-lang/json_serializable/blob/master/example/pubspec.yaml). + +**pubspec.yaml** + +```yaml +dependencies: + # Your other regular dependencies here + json_annotation: ^0.2.2 + +dev_dependencies: + # Your other dev_dependencies here + build_runner: ^0.6.1 + json_serializable: ^0.3.0 +``` + +After declaring the needed dependencies, synchronize your packages. In IntelliJ IDEA, click the _Packages get_ link on the top of an opened `pubspec.yaml` file. Alternatively, you can do synchronize packages by running `flutter packages get` from the command line in your projects' root folder. + +Next, define some build actions. + +#### Defining the build actions + +_Build actions_ are a way of running _builders_ in our project's source files. + +Our build action has the following parameters: + +* **builder**, which in our case is a `jsonPartBuilder` +* **package name** which is the specific package this build action will be performed on +* **inputs**, which are files the build action is performed on. + +We'll create our build action file in `/tool/build_actions.dart`. + +**tool/build_actions.dart** + +{% prettify dart %} +import 'package:build_runner/build_runner.dart'; +import 'package:json_serializable/json_serializable.dart'; + +List get buildActions => [ + new BuildAction( + // The builder to run on our source files. + jsonPartBuilder(), + + // The name of the current package, usually the same as the name + // for your Flutter project. For example, if you created a new + // project with "flutter run hello", your package name here would + // be 'hello'. + '[[highlight]][[/highlight]]', + + // All of the files this `BuildAction` should target when run. + inputs: const [ + 'lib/*.dart', + ], + ), + ]; +{% endprettify %} + +We are declaring a top-level variable called `buildActions`, which returns a `List` containing all build actions in our project. In this case, we only have one build action, which generates JSON serialization logic for files in the _lib_ folder. + +It is good practice to define build actions in the same file, as they are used by the _build_ and the _watcher_ files. + +Which brings us to the next topic, the build file. + +#### Creating the build file + +Our build file runs the actions responsible for generating JSON serialization logic for our classes. When run, it triggers the build actions, searches for all the relevant files, generates the necessary code and then complete. + +We'll create the build file in `/tool/build.dart`. + +**tool/build.dart** + +```dart +import 'dart:io'; +import 'package:build_runner/build_runner.dart'; +import 'build_actions.dart'; + +main() async { + var result = await build(buildActions, deleteFilesByDefault: true); + if (result.status == BuildStatus.failure) { + exitCode = 1; + } +} +``` + +Now we can generate all the necessary JSON serialization source files by running `dart tool/build.dart` in our project's root folder. While the build file is all we need for one-time source code generation, it would be nice if we did not have to run it manually every time we make changes. + +#### Creating the watcher file + +A _watcher_ makes our source code generation progress more convenient. It watches changes in our project and automatically builds the necessary files when needed. + +The watcher file also lives in the tool folder, so we create it in `/tool/watch.dart`. + +**tool/watch.dart** + +```dart +import 'package:build_runner/build_runner.dart'; +import 'build_actions.dart'; + +main() { + watch(buildActions, deleteFilesByDefault: true); +} +``` + +We can run the watcher by doing `dart tool/watch.dart` in our project's root folder. It is safe to start the watcher once and leave it running in the background. \ No newline at end of file From cfa4f0f83a34e1452ddeda3b918edcb58499ee3b Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Tue, 9 Jan 2018 01:49:21 +0200 Subject: [PATCH 02/11] First feedback iteration & small fixes that came up. --- _includes/sidebar.html | 2 +- json.md | 56 ++++++++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/_includes/sidebar.html b/_includes/sidebar.html index 41b3a2fdfe0..4a3d71785ea 100644 --- a/_includes/sidebar.html +++ b/_includes/sidebar.html @@ -38,7 +38,7 @@
  • Platform-specific code
  • Read and write files
  • Network and HTTP
  • -
  • Serializing JSON objects
  • +
  • JSON and serialization
  • Firebase for Flutter - Codelab
  • diff --git a/json.md b/json.md index 11814c8bdbd..bc58cbbb992 100644 --- a/json.md +++ b/json.md @@ -1,12 +1,12 @@ --- layout: page -title: TODO - JSON article title +title: JSON and serialization permalink: /json/ --- It is hard to think of a mobile app that doesn't need to communicate with a web server at some point. When making network-connected apps, chances are that we need to consume some good old JSON, sooner or later. -In this tutorial, we look into ways of doing JSON with Flutter. We will go over what JSON solution to use in different scenarios and why. +In this tutorial, we look into ways of using JSON with Flutter. We will go over what JSON solution to use in different scenarios and why. * TOC Placeholder {:toc} @@ -19,35 +19,42 @@ Different projects come with different complexities and use cases. For smaller p ### Use manual serialization for smaller projects -Manual JSON serialization refers to using the built-in JSON decoder in `dart:convert`. It involves passing the raw JSON string to the `JSON.decode()` method, and then looking up the values you need in the `Map` the method returns. It involves no external dependencies or particular setup process, and it is good for quick proof of concepts. +Manual JSON serialization refers to using the built-in JSON decoder in `dart:convert`. It involves passing the raw JSON string to the `JSON.decode()` method, and then looking up the values you need in the `Map` the method returns. It has no external dependencies or particular setup process, and it is good for quick proof of concepts. Where the manual serialization does not perform well is when your project becomes bigger. Writing the serialization logic by hand can become hard to manage and error-prone. If you have a typo when accessing an unexisting JSON field, your code throws an error during runtime. -If you do not have many JSON models in your project and are looking to test a concept quickly, manual serialization might be the way you want to start. +If you do not have many JSON models in your project and are looking to test a concept quickly, manual serialization might be the way you want to start. For an example of manual serialization, [see here](#manual-serialization). ### Use code generation for medium to large projects JSON serialization with code generation means having an external library generate the serialization boilerplate for you. They involve some initial setup and running a file watcher that generates the code from your model classes. For example, [json_serializable](https://pub.dartlang.org/packages/json_serializable) and [built_value](https://pub.dartlang.org/packages/built_value) are these kind of libraries. -This approach scales well for a larger project. There are no hand-written boilerplate needed, and typos when accessing JSON fields are caught at compile-time. The downside with code generation is that it involves some initial setup. Also, the generated source files may produce visual clutter in your project navigator +This approach scales well for a larger project. There is no hand-written boilerplate needed, and typos when accessing JSON fields are caught at compile-time. The downside with code generation is that it involves some initial setup. Also, the generated source files may produce visual clutter in your project navigator -You might want to use generated code for JSON serialization when you have a medium or a larger project. +You might want to use generated code for JSON serialization when you have a medium or a larger project. To see an example of code generation based JSON serialization, [see here](#code-generation). ## Is there a GSON/Jackson/Moshi equivalent in Flutter? -Dart has supported _tree shaking_ for quite a long time. With tree shaking, we can “shake off” unused code from our release builds. Tree shaking allows us to optimize the size of our applications significantly. +The simple answer is no. It would require runtime reflection, which is disabled in Flutter. -While Dart users might have gotten used to [dartson](https://pub.dartlang.org/packages/dartson) to (de)serialize their JSON, it does not work with Flutter. The reason behind this is that `dartson`, like [GSON](https://github.com/google/gson) in Java, uses _runtime reflection_. In Flutter, reflection (also known as `dart:mirrors`) is disabled. +Dart has supported _tree shaking_ for quite a long time. With tree shaking, we can “shake off” unused code from our release builds. Tree shaking allows us to optimize the size of our applications significantly. Since reflection makes all code implicitly used by default, it interferes with tree shaking. The tools cannot know what parts are unused at runtime; the redundant code is impossible to strip away. App sizes cannot be optimized when using reflection. -Although we cannot use dartson, there’s a library that gives us a simple to use API, without using reflection. That library is called [json_serializable](https://pub.dartlang.org/packages/json_serializable). + + +Although we cannot use runtime reflection with Flutter, some libraries give us similarly easy to use APIs but are based on code generation instead. This approach is covered in more detail in the [code generation libraries](#code-generation) section. + ## Serializing JSON manually using dart:convert -Basic JSON serialization in Flutter is very simple. Dart comes with the `dart:convert` library built in, and using the JSON decoder API is straightforward. +Basic JSON serialization in Flutter is very simple. Flutter has a built-in `dart:convert` library, which includes a straightforward JSON encoder and decoder. -Let's say we need to deserialize a JSON representing a user object. For sample purposes, we have a real simple JSON model. +Here is an example JSON for a simple user. ```json { @@ -60,7 +67,7 @@ With `dart:convert`, we can serialize this JSON model in two ways. Let's have a ### Serializing JSON inline -By looking at [the dart:convert JSON documentation](https://api.dartlang.org/stable/1.24.3/dart-convert/JsonCodec-class.html), we see that we can parse the JSON by calling the `JSON.decode` method, with our JSON string as the method argument. +By looking at [the dart:convert JSON documentation](https://api.dartlang.org/stable/1.24.3/dart-convert/JsonCodec-class.html), we see that we can decode the JSON by calling the `JSON.decode` method, with our JSON string as the method argument. ```dart Map user = JSON.decode(json); @@ -71,7 +78,7 @@ print('We sent the verification link to ${user['email']}.'); Unfortunately, `JSON.decode()` merely returns a `Map`, meaning that we do not know the types of the values until runtime. With this approach, we lose most of the statically typed language features: type safety, autocompletion and most importantly, compile-time exceptions. Our code can become instantly more error-prone. -For example, whenever we access the `name` or `email` fields, we could quickly introduce a typo. A typo which our compiler does not know of since our entire JSON merely lives in a map structure. +For example, whenever we access the `name` or `email` fields, we could quickly introduce a typo. A typo which our compiler does not know of, since our entire JSON merely lives in a map structure. ### Serializing JSON inside model classes @@ -80,16 +87,16 @@ We can combat the previously mentioned problems by introducing a plain model cla * a `User.fromJson` constructor, for constructing a new `User` instance from a map structure * a `toJson` method, which converts a `User` instance into a map. -This way, the calling code can now have type safety, autocompletion for the `name` and `email` fields and compile-time exceptions. If we make typos or treat the fields as `int`s instead of `String`s, instead of crashing on runtime, our app will not even compile. +This way, the _calling code_ can now have type safety, autocompletion for the `name` and `email` fields and compile-time exceptions. If we make typos or treat the fields as `int`s instead of `String`s, our app will not even compile, instead of crashing on runtime. **user.dart** ```dart class User { - User(this.name, this.email); + final String name; + final String email; - String name; - String email; + User(this.name, this.email); User.fromJson(Map json) : name = json['name'], @@ -104,11 +111,11 @@ class User { } ``` -Now the calling code does not have to care about the serialization logic. With this new approach, we can deserialize a user quite easily. +Now the responsibility of the serialization logic is moved inside the model itself. With this new approach, we can deserialize a user quite easily. ```dart -Map map = JSON.decode(json); -User user = new User.fromJson(map); +Map userMap = JSON.decode(json); +var user = new User.fromJson(userMap); print('Howdy, ${user.name}!'); print('We sent the verification link to ${user.email}.'); @@ -120,17 +127,18 @@ To serialize a user, we just call the `toJson` method in the `User` class. String json = JSON.encode(user.toJson()); ``` -While with this new approach the calling code does not have to worry about JSON serialization at all, the model class still definitely has. In a production app, we would want to be sure that the serialization works. In practice, this means that the `User.fromJson` and `User.toJson` both need to have unit tests in place to verify correct behavior. +This way, the calling code does not have to worry about JSON serialization at all. However, the model class still definitely has to. In a production app, we would want to be sure that the serialization works properly. In practice, the `User.fromJson` and `User.toJson` methods both need to have unit tests in place to verify correct behavior. Also, real-world scenarios are not usually that simple. It is unlikely that we can get by with such small JSON responses. Nested JSON objects are not that uncommon either. It would be nice if there were something that handled the JSON serialization for us. Luckily, there is! + ## Serializing JSON using json_serializable The `json_serializable` package is an automated source code generator that can generate the JSON serializing boilerplate for us. -Since the serialization code is not handwritten and maintained by us anymore, we minimize the risk of having JSON serialization exceptions at runtime. If the sources are generated, and the app compiles, we should be fine. +Since the serialization code is not handwritten and maintained by us anymore, we minimize the risk of having JSON serialization exceptions at runtime. ### How does a json_serializable class look? @@ -174,8 +182,8 @@ class User extends Object with _$[[highlight]]User[[/highlight]]SerializerMixin To deserialize a JSON string `json_serializable` way, we do not have actually to make any changes to our previous code. ```dart -Map map = JSON.decode(json); -User user = new User.fromJson(map); +Map userMap = JSON.decode(json); +var user = new User.fromJson(userMap); ``` Same goes for serialization. The calling API is the same as before. From c1cb821673920c39823ead1c3da1ad928651d69f Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Tue, 9 Jan 2018 01:56:49 +0200 Subject: [PATCH 03/11] Proper line breaks. --- json.md | 184 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 140 insertions(+), 44 deletions(-) diff --git a/json.md b/json.md index bc58cbbb992..51e1df81625 100644 --- a/json.md +++ b/json.md @@ -4,9 +4,12 @@ title: JSON and serialization permalink: /json/ --- -It is hard to think of a mobile app that doesn't need to communicate with a web server at some point. When making network-connected apps, chances are that we need to consume some good old JSON, sooner or later. +It is hard to think of a mobile app that doesn't need to communicate with a web +server at some point. When making network-connected apps, chances are that we +need to consume some good old JSON, sooner or later. -In this tutorial, we look into ways of using JSON with Flutter. We will go over what JSON solution to use in different scenarios and why. +In this tutorial, we look into ways of using JSON with Flutter. We will go over +what JSON solution to use in different scenarios and why. * TOC Placeholder {:toc} @@ -15,44 +18,81 @@ In this tutorial, we look into ways of using JSON with Flutter. We will go over There isn't a one size fits all JSON solution for every project. -Different projects come with different complexities and use cases. For smaller proof-of-concept projects or quick prototypes, using code generators might be overkill. For apps with several JSON models with more complexity, doing serialization by hand can quickly become tedious, repetitive and lend itself to lots of small errors. +Different projects come with different complexities and use cases. For smaller +proof-of-concept projects or quick prototypes, using code generators might be +overkill. For apps with several JSON models with more complexity, doing +serialization by hand can quickly become tedious, repetitive and lend itself to +lots of small errors. ### Use manual serialization for smaller projects -Manual JSON serialization refers to using the built-in JSON decoder in `dart:convert`. It involves passing the raw JSON string to the `JSON.decode()` method, and then looking up the values you need in the `Map` the method returns. It has no external dependencies or particular setup process, and it is good for quick proof of concepts. +Manual JSON serialization refers to using the built-in JSON decoder in +`dart:convert`. It involves passing the raw JSON string to the `JSON.decode()` +method, and then looking up the values you need in the `Map` +the method returns. It has no external dependencies or particular setup process, +and it is good for quick proof of concepts. -Where the manual serialization does not perform well is when your project becomes bigger. Writing the serialization logic by hand can become hard to manage and error-prone. If you have a typo when accessing an unexisting JSON field, your code throws an error during runtime. +Where the manual serialization does not perform well is when your project +becomes bigger. Writing the serialization logic by hand can become hard to +manage and error-prone. If you have a typo when accessing an unexisting JSON +field, your code throws an error during runtime. -If you do not have many JSON models in your project and are looking to test a concept quickly, manual serialization might be the way you want to start. For an example of manual serialization, [see here](#manual-serialization). +If you do not have many JSON models in your project and are looking to test a +concept quickly, manual serialization might be the way you want to start. For an +example of manual serialization, [see here](#manual-serialization). ### Use code generation for medium to large projects -JSON serialization with code generation means having an external library generate the serialization boilerplate for you. They involve some initial setup and running a file watcher that generates the code from your model classes. For example, [json_serializable](https://pub.dartlang.org/packages/json_serializable) and [built_value](https://pub.dartlang.org/packages/built_value) are these kind of libraries. +JSON serialization with code generation means having an external library +generate the serialization boilerplate for you. They involve some initial setup +and running a file watcher that generates the code from your model classes. For +example, +[json_serializable](https://pub.dartlang.org/packages/json_serializable) and +[built_value](https://pub.dartlang.org/packages/built_value) are these kind of +libraries. -This approach scales well for a larger project. There is no hand-written boilerplate needed, and typos when accessing JSON fields are caught at compile-time. The downside with code generation is that it involves some initial setup. Also, the generated source files may produce visual clutter in your project navigator +This approach scales well for a larger project. There is no hand-written +boilerplate needed, and typos when accessing JSON fields are caught at +compile-time. The downside with code generation is that it involves some initial +setup. Also, the generated source files may produce visual clutter in your +project navigator -You might want to use generated code for JSON serialization when you have a medium or a larger project. To see an example of code generation based JSON serialization, [see here](#code-generation). +You might want to use generated code for JSON serialization when you have a +medium or a larger project. To see an example of code generation based JSON +serialization, [see here](#code-generation). ## Is there a GSON/Jackson/Moshi equivalent in Flutter? -The simple answer is no. It would require runtime reflection, which is disabled in Flutter. +The simple answer is no. It would require runtime reflection, which is disabled +in Flutter. -Dart has supported _tree shaking_ for quite a long time. With tree shaking, we can “shake off” unused code from our release builds. Tree shaking allows us to optimize the size of our applications significantly. +Dart has supported _tree shaking_ for quite a long time. With tree shaking, we +can “shake off” unused code from our release builds. Tree shaking allows us to +optimize the size of our applications significantly. -Since reflection makes all code implicitly used by default, it interferes with tree shaking. The tools cannot know what parts are unused at runtime; the redundant code is impossible to strip away. App sizes cannot be optimized when using reflection. +Since reflection makes all code implicitly used by default, it interferes with +tree shaking. The tools cannot know what parts are unused at runtime; the +redundant code is impossible to strip away. App sizes cannot be optimized when +using reflection. -Although we cannot use runtime reflection with Flutter, some libraries give us similarly easy to use APIs but are based on code generation instead. This approach is covered in more detail in the [code generation libraries](#code-generation) section. +Although we cannot use runtime reflection with Flutter, some libraries give us +similarly easy to use APIs but are based on code generation instead. This +approach is covered in more detail in the [code generation +libraries](#code-generation) section. ## Serializing JSON manually using dart:convert -Basic JSON serialization in Flutter is very simple. Flutter has a built-in `dart:convert` library, which includes a straightforward JSON encoder and decoder. +Basic JSON serialization in Flutter is very simple. Flutter has a built-in +`dart:convert` library, which includes a straightforward JSON encoder and +decoder. Here is an example JSON for a simple user. @@ -63,11 +103,15 @@ Here is an example JSON for a simple user. } ``` -With `dart:convert`, we can serialize this JSON model in two ways. Let's have a look at both. +With `dart:convert`, we can serialize this JSON model in two ways. Let's have a +look at both. ### Serializing JSON inline -By looking at [the dart:convert JSON documentation](https://api.dartlang.org/stable/1.24.3/dart-convert/JsonCodec-class.html), we see that we can decode the JSON by calling the `JSON.decode` method, with our JSON string as the method argument. +By looking at [the dart:convert JSON +documentation](https://api.dartlang.org/stable/1.24.3/dart-convert/JsonCodec-class.html), +we see that we can decode the JSON by calling the `JSON.decode` method, with our +JSON string as the method argument. ```dart Map user = JSON.decode(json); @@ -76,18 +120,29 @@ print('Howdy, ${user['name']}!'); print('We sent the verification link to ${user['email']}.'); ``` -Unfortunately, `JSON.decode()` merely returns a `Map`, meaning that we do not know the types of the values until runtime. With this approach, we lose most of the statically typed language features: type safety, autocompletion and most importantly, compile-time exceptions. Our code can become instantly more error-prone. +Unfortunately, `JSON.decode()` merely returns a `Map`, meaning +that we do not know the types of the values until runtime. With this approach, +we lose most of the statically typed language features: type safety, +autocompletion and most importantly, compile-time exceptions. Our code can +become instantly more error-prone. -For example, whenever we access the `name` or `email` fields, we could quickly introduce a typo. A typo which our compiler does not know of, since our entire JSON merely lives in a map structure. +For example, whenever we access the `name` or `email` fields, we could quickly +introduce a typo. A typo which our compiler does not know of, since our entire +JSON merely lives in a map structure. ### Serializing JSON inside model classes -We can combat the previously mentioned problems by introducing a plain model class, which we call `User`. Inside the `User` class, we have: +We can combat the previously mentioned problems by introducing a plain model +class, which we call `User`. Inside the `User` class, we have: -* a `User.fromJson` constructor, for constructing a new `User` instance from a map structure +* a `User.fromJson` constructor, for constructing a new `User` instance from a + map structure * a `toJson` method, which converts a `User` instance into a map. -This way, the _calling code_ can now have type safety, autocompletion for the `name` and `email` fields and compile-time exceptions. If we make typos or treat the fields as `int`s instead of `String`s, our app will not even compile, instead of crashing on runtime. +This way, the _calling code_ can now have type safety, autocompletion for the +`name` and `email` fields and compile-time exceptions. If we make typos or treat +the fields as `int`s instead of `String`s, our app will not even compile, +instead of crashing on runtime. **user.dart** @@ -111,7 +166,8 @@ class User { } ``` -Now the responsibility of the serialization logic is moved inside the model itself. With this new approach, we can deserialize a user quite easily. +Now the responsibility of the serialization logic is moved inside the model +itself. With this new approach, we can deserialize a user quite easily. ```dart Map userMap = JSON.decode(json); @@ -127,22 +183,32 @@ To serialize a user, we just call the `toJson` method in the `User` class. String json = JSON.encode(user.toJson()); ``` -This way, the calling code does not have to worry about JSON serialization at all. However, the model class still definitely has to. In a production app, we would want to be sure that the serialization works properly. In practice, the `User.fromJson` and `User.toJson` methods both need to have unit tests in place to verify correct behavior. +This way, the calling code does not have to worry about JSON serialization at +all. However, the model class still definitely has to. In a production app, we +would want to be sure that the serialization works properly. In practice, the +`User.fromJson` and `User.toJson` methods both need to have unit tests in place +to verify correct behavior. -Also, real-world scenarios are not usually that simple. It is unlikely that we can get by with such small JSON responses. Nested JSON objects are not that uncommon either. +Also, real-world scenarios are not usually that simple. It is unlikely that we +can get by with such small JSON responses. Nested JSON objects are not that +uncommon either. -It would be nice if there were something that handled the JSON serialization for us. Luckily, there is! +It would be nice if there were something that handled the JSON serialization for +us. Luckily, there is! ## Serializing JSON using json_serializable -The `json_serializable` package is an automated source code generator that can generate the JSON serializing boilerplate for us. +The `json_serializable` package is an automated source code generator that can +generate the JSON serializing boilerplate for us. -Since the serialization code is not handwritten and maintained by us anymore, we minimize the risk of having JSON serialization exceptions at runtime. +Since the serialization code is not handwritten and maintained by us anymore, we +minimize the risk of having JSON serialization exceptions at runtime. ### How does a json_serializable class look? -Let's see how to convert our `User` class to a `json_serializable` one. For the sake of simplicity, we use the dumbed-down JSON model from the previous samples. +Let's see how to convert our `User` class to a `json_serializable` one. For the +sake of simplicity, we use the dumbed-down JSON model from the previous samples. **user.dart** @@ -179,7 +245,8 @@ class User extends Object with _$[[highlight]]User[[/highlight]]SerializerMixin } {% endprettify %} -To deserialize a JSON string `json_serializable` way, we do not have actually to make any changes to our previous code. +To deserialize a JSON string `json_serializable` way, we do not have actually to +make any changes to our previous code. ```dart Map userMap = JSON.decode(json); @@ -192,19 +259,30 @@ Same goes for serialization. The calling API is the same as before. String json = JSON.encode(user.toJson()); ``` -With `json_serializable`, we can forget any manual JSON serialization in the `User` class. The source code generator creates a file called `user.g.dart`, which has all the necessary serialization logic. Now we do not necessarily have to write automated tests to be sure that the serialization works - it is now _the library's responsibility_ to make sure the serialization works appropriately. +With `json_serializable`, we can forget any manual JSON serialization in the +`User` class. The source code generator creates a file called `user.g.dart`, +which has all the necessary serialization logic. Now we do not necessarily have +to write automated tests to be sure that the serialization works - it is now +_the library's responsibility_ to make sure the serialization works +appropriately. -Now that we have learned how the model classes look, let's go through the required steps to introduce `json_serializable` to our project. +Now that we have learned how the model classes look, let's go through the +required steps to introduce `json_serializable` to our project. ### Setting up json_serializable in a project -To have `json_serializable` up and running, we have to go through some steps first. We need to do this only ** once per project**. After the initial setup, creating JSON model classes is trivial. +To have `json_serializable` up and running, we have to go through some steps +first. We need to do this only ** once per project**. After the initial setup, +creating JSON model classes is trivial. #### Setting up the pubspec dependencies -To include `json_serializable` in our project, we need one regular and two _dev\ dependencies_. In short, _dev dependencies_ are dependencies that are not included in our app source code. +To include `json_serializable` in our project, we need one regular and two _dev\ +dependencies_. In short, _dev dependencies_ are dependencies that are not +included in our app source code. -The latest versions of these required dependencies can be seen by following [this link](https://github.com/dart-lang/json_serializable/blob/master/example/pubspec.yaml). +The latest versions of these required dependencies can be seen by following +[this link](https://github.com/dart-lang/json_serializable/blob/master/example/pubspec.yaml). **pubspec.yaml** @@ -219,7 +297,10 @@ dev_dependencies: json_serializable: ^0.3.0 ``` -After declaring the needed dependencies, synchronize your packages. In IntelliJ IDEA, click the _Packages get_ link on the top of an opened `pubspec.yaml` file. Alternatively, you can do synchronize packages by running `flutter packages get` from the command line in your projects' root folder. +After declaring the needed dependencies, synchronize your packages. In IntelliJ +IDEA, click the _Packages get_ link on the top of an opened `pubspec.yaml` file. +Alternatively, you can do synchronize packages by running `flutter packages get` +from the command line in your projects' root folder. Next, define some build actions. @@ -230,7 +311,8 @@ _Build actions_ are a way of running _builders_ in our project's source files. Our build action has the following parameters: * **builder**, which in our case is a `jsonPartBuilder` -* **package name** which is the specific package this build action will be performed on +* **package name** which is the specific package this build action will be + performed on * **inputs**, which are files the build action is performed on. We'll create our build action file in `/tool/build_actions.dart`. @@ -260,15 +342,21 @@ List get buildActions => [ ]; {% endprettify %} -We are declaring a top-level variable called `buildActions`, which returns a `List` containing all build actions in our project. In this case, we only have one build action, which generates JSON serialization logic for files in the _lib_ folder. +We are declaring a top-level variable called `buildActions`, which returns a +`List` containing all build actions in our project. In this case, we only have +one build action, which generates JSON serialization logic for files in the +_lib_ folder. -It is good practice to define build actions in the same file, as they are used by the _build_ and the _watcher_ files. +It is good practice to define build actions in the same file, as they are used +by the _build_ and the _watcher_ files. Which brings us to the next topic, the build file. #### Creating the build file -Our build file runs the actions responsible for generating JSON serialization logic for our classes. When run, it triggers the build actions, searches for all the relevant files, generates the necessary code and then complete. +Our build file runs the actions responsible for generating JSON serialization +logic for our classes. When run, it triggers the build actions, searches for all +the relevant files, generates the necessary code and then complete. We'll create the build file in `/tool/build.dart`. @@ -287,13 +375,19 @@ main() async { } ``` -Now we can generate all the necessary JSON serialization source files by running `dart tool/build.dart` in our project's root folder. While the build file is all we need for one-time source code generation, it would be nice if we did not have to run it manually every time we make changes. +Now we can generate all the necessary JSON serialization source files by running +`dart tool/build.dart` in our project's root folder. While the build file is all +we need for one-time source code generation, it would be nice if we did not have +to run it manually every time we make changes. #### Creating the watcher file -A _watcher_ makes our source code generation progress more convenient. It watches changes in our project and automatically builds the necessary files when needed. +A _watcher_ makes our source code generation progress more convenient. It +watches changes in our project and automatically builds the necessary files when +needed. -The watcher file also lives in the tool folder, so we create it in `/tool/watch.dart`. +The watcher file also lives in the tool folder, so we create it in +`/tool/watch.dart`. **tool/watch.dart** @@ -306,4 +400,6 @@ main() { } ``` -We can run the watcher by doing `dart tool/watch.dart` in our project's root folder. It is safe to start the watcher once and leave it running in the background. \ No newline at end of file +We can run the watcher by doing `dart tool/watch.dart` in our project's root +folder. It is safe to start the watcher once and leave it running in the +background. \ No newline at end of file From e125878c366f557fa338e043a02b0cb292f48ab1 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Tue, 9 Jan 2018 02:15:37 +0200 Subject: [PATCH 04/11] Skipping validations for code samples that need extra dependencies / would degrade in readability if fixed. --- json.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/json.md b/json.md index 51e1df81625..8c612c09882 100644 --- a/json.md +++ b/json.md @@ -113,6 +113,7 @@ documentation](https://api.dartlang.org/stable/1.24.3/dart-convert/JsonCodec-cla we see that we can decode the JSON by calling the `JSON.decode` method, with our JSON string as the method argument. + ```dart Map user = JSON.decode(json); @@ -169,6 +170,7 @@ class User { Now the responsibility of the serialization logic is moved inside the model itself. With this new approach, we can deserialize a user quite easily. + ```dart Map userMap = JSON.decode(json); var user = new User.fromJson(userMap); @@ -179,6 +181,7 @@ print('We sent the verification link to ${user.email}.'); To serialize a user, we just call the `toJson` method in the `User` class. + ```dart String json = JSON.encode(user.toJson()); ``` @@ -212,6 +215,7 @@ sake of simplicity, we use the dumbed-down JSON model from the previous samples. **user.dart** + {% prettify dart %} /// This allows the generated code access our class members. /// The value for this is the same as the source file name, @@ -248,6 +252,7 @@ class User extends Object with _$[[highlight]]User[[/highlight]]SerializerMixin To deserialize a JSON string `json_serializable` way, we do not have actually to make any changes to our previous code. + ```dart Map userMap = JSON.decode(json); var user = new User.fromJson(userMap); @@ -255,6 +260,7 @@ var user = new User.fromJson(userMap); Same goes for serialization. The calling API is the same as before. + ```dart String json = JSON.encode(user.toJson()); ``` @@ -319,6 +325,7 @@ We'll create our build action file in `/tool/build_actions.dart`. **tool/build_actions.dart** + {% prettify dart %} import 'package:build_runner/build_runner.dart'; import 'package:json_serializable/json_serializable.dart'; @@ -362,6 +369,7 @@ We'll create the build file in `/tool/build.dart`. **tool/build.dart** + ```dart import 'dart:io'; import 'package:build_runner/build_runner.dart'; @@ -391,6 +399,7 @@ The watcher file also lives in the tool folder, so we create it in **tool/watch.dart** + ```dart import 'package:build_runner/build_runner.dart'; import 'build_actions.dart'; From 1df3fb3b9b13c4345cca218cc593c66ddbe3a923 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Tue, 9 Jan 2018 08:11:54 +0200 Subject: [PATCH 05/11] Yet another attempt of fixing a failing Travis build. --- json.md | 1 + 1 file changed, 1 insertion(+) diff --git a/json.md b/json.md index 8c612c09882..094b1cbdbe9 100644 --- a/json.md +++ b/json.md @@ -147,6 +147,7 @@ instead of crashing on runtime. **user.dart** + ```dart class User { final String name; From eb0567886598d153fad96897e04c8b1fef735c34 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Thu, 1 Feb 2018 20:55:38 +0200 Subject: [PATCH 06/11] Simplified json_serializable section & minor improvements to other parts of the article. --- images/json/ide_warning.png | Bin 0 -> 41901 bytes json.md | 219 ++++++++++++------------------------ 2 files changed, 72 insertions(+), 147 deletions(-) create mode 100644 images/json/ide_warning.png diff --git a/images/json/ide_warning.png b/images/json/ide_warning.png new file mode 100644 index 0000000000000000000000000000000000000000..b7361ac92de15b9e11b78ba5eac22f5545dc6673 GIT binary patch literal 41901 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 literal 0 HcmV?d00001 diff --git a/json.md b/json.md index 094b1cbdbe9..74f1728175c 100644 --- a/json.md +++ b/json.md @@ -5,10 +5,10 @@ permalink: /json/ --- It is hard to think of a mobile app that doesn't need to communicate with a web -server at some point. When making network-connected apps, chances are that we +server at some point. When making network-connected apps, the chances are that we need to consume some good old JSON, sooner or later. -In this tutorial, we look into ways of using JSON with Flutter. We will go over +In this tutorial, we look into ways of using JSON with Flutter. We go over what JSON solution to use in different scenarios and why. * TOC Placeholder @@ -48,7 +48,7 @@ generate the serialization boilerplate for you. They involve some initial setup and running a file watcher that generates the code from your model classes. For example, [json_serializable](https://pub.dartlang.org/packages/json_serializable) and -[built_value](https://pub.dartlang.org/packages/built_value) are these kind of +[built_value](https://pub.dartlang.org/packages/built_value) are these kinds of libraries. This approach scales well for a larger project. There is no hand-written @@ -63,10 +63,10 @@ serialization, [see here](#code-generation). ## Is there a GSON/Jackson/Moshi equivalent in Flutter? -The simple answer is no. It would require runtime reflection, which is disabled -in Flutter. +The simple answer is no. -Dart has supported _tree shaking_ for quite a long time. With tree shaking, we +Such a library would require using runtime reflection, which is disabled +in Flutter. Dart has supported _tree shaking_ for quite a long time. With tree shaking, we can “shake off” unused code from our release builds. Tree shaking allows us to optimize the size of our applications significantly. @@ -94,7 +94,7 @@ Basic JSON serialization in Flutter is very simple. Flutter has a built-in `dart:convert` library, which includes a straightforward JSON encoder and decoder. -Here is an example JSON for a simple user. +Here is an example JSON for a simple user model. ```json { @@ -128,7 +128,7 @@ autocompletion and most importantly, compile-time exceptions. Our code can become instantly more error-prone. For example, whenever we access the `name` or `email` fields, we could quickly -introduce a typo. A typo which our compiler does not know of, since our entire +introduce a typo. A typo which our compiler does not know of since our entire JSON merely lives in a map structure. ### Serializing JSON inside model classes @@ -203,13 +203,36 @@ us. Luckily, there is! ## Serializing JSON using json_serializable -The `json_serializable` package is an automated source code generator that can -generate the JSON serializing boilerplate for us. +The [json_serializable package](https://github.com/dart-lang/json_serializable) is an automated source code generator that can generate the JSON serializing boilerplate for us. Since the serialization code is not handwritten and maintained by us anymore, we minimize the risk of having JSON serialization exceptions at runtime. -### How does a json_serializable class look? +### Setting up json_serializable in a project + +To include `json_serializable` in our project, we need one regular and two _dev +dependencies_. In short, _dev dependencies_ are dependencies that are not +included in our app source code. + +The latest versions of these required dependencies can be seen by following +[this link](https://github.com/dart-lang/json_serializable/blob/master/example/pubspec.yaml). + +**pubspec.yaml** + +```yaml +dependencies: + # Your other regular dependencies here + json_annotation: ^0.2.2 + +dev_dependencies: + # Your other dev_dependencies here + build_runner: ^0.6.1 + json_serializable: ^0.3.0 +``` + +Run `flutter packages get` inside your project root folder (or click "Packages Get" in your editor) to make these new dependencies available in your project. + +### Creating model classes the json_serializable way Let's see how to convert our `User` class to a `json_serializable` one. For the sake of simplicity, we use the dumbed-down JSON model from the previous samples. @@ -231,7 +254,7 @@ import 'package:json_annotation/json_annotation.dart'; part '[[highlight]]user[[/highlight]].g.dart'; /// An annotation for the code generator to know that this class needs the -/// source generator to generate JSON serialization logic. +/// JSON serialization logic to be generated. [[highlight]]@JsonSerializable()[[/highlight]] /// Every json_serializable class must have the serializer mixin. @@ -250,166 +273,68 @@ class User extends Object with _$[[highlight]]User[[/highlight]]SerializerMixin } {% endprettify %} -To deserialize a JSON string `json_serializable` way, we do not have actually to -make any changes to our previous code. - - -```dart -Map userMap = JSON.decode(json); -var user = new User.fromJson(userMap); -``` +With this setup, the source code generator will generate code for serializing the `name` and `email` fields from JSON and back. -Same goes for serialization. The calling API is the same as before. +If needed, it is also easy to customize the naming strategy. For example, if the API we are working with returns objects with _snake\_case_, and we want to use _lowerCamelCase_ in our models, we can use the `@JsonKey` annotation with a name parameter: ```dart -String json = JSON.encode(user.toJson()); +/// Tell json_serializable that "registration_date_millis" should be +/// mapped to this property. +@JsonKey(name: 'registration_date_millis') +final int registrationDateMillis; ``` -With `json_serializable`, we can forget any manual JSON serialization in the -`User` class. The source code generator creates a file called `user.g.dart`, -which has all the necessary serialization logic. Now we do not necessarily have -to write automated tests to be sure that the serialization works - it is now -_the library's responsibility_ to make sure the serialization works -appropriately. +### Running the code generation utility -Now that we have learned how the model classes look, let's go through the -required steps to introduce `json_serializable` to our project. +When creating `json_serializable` classes the first time, you will get errors similar to the image below. -### Setting up json_serializable in a project +![IDE warning when the generated code for a model class does not exist yet.](/images/json/ide_warning.png) -To have `json_serializable` up and running, we have to go through some steps -first. We need to do this only ** once per project**. After the initial setup, -creating JSON model classes is trivial. +These errors are entirely normal and are simply because the generated code for the model class does not exist yet. To resolve this, we must run the code generator that generates the serialization boilerplate for us. -#### Setting up the pubspec dependencies +There are two ways of running the code generator. -To include `json_serializable` in our project, we need one regular and two _dev\ -dependencies_. In short, _dev dependencies_ are dependencies that are not -included in our app source code. +#### One-time code generation -The latest versions of these required dependencies can be seen by following -[this link](https://github.com/dart-lang/json_serializable/blob/master/example/pubspec.yaml). +By running `flutter packages pub run build_runner build` in our project root, we can generate json serialization code for our models whenever needed. This triggers a one-time build which goes through our source files, picks the relevant ones and generates the necessary serialization code for them. -**pubspec.yaml** +While this is pretty convenient, it would nice if we did not have to run the build manually every time we make changes in our model classes. -```yaml -dependencies: - # Your other regular dependencies here - json_annotation: ^0.2.2 - -dev_dependencies: - # Your other dev_dependencies here - build_runner: ^0.6.1 - json_serializable: ^0.3.0 -``` - -After declaring the needed dependencies, synchronize your packages. In IntelliJ -IDEA, click the _Packages get_ link on the top of an opened `pubspec.yaml` file. -Alternatively, you can do synchronize packages by running `flutter packages get` -from the command line in your projects' root folder. - -Next, define some build actions. - -#### Defining the build actions - -_Build actions_ are a way of running _builders_ in our project's source files. - -Our build action has the following parameters: - -* **builder**, which in our case is a `jsonPartBuilder` -* **package name** which is the specific package this build action will be - performed on -* **inputs**, which are files the build action is performed on. - -We'll create our build action file in `/tool/build_actions.dart`. - -**tool/build_actions.dart** - - -{% prettify dart %} -import 'package:build_runner/build_runner.dart'; -import 'package:json_serializable/json_serializable.dart'; - -List get buildActions => [ - new BuildAction( - // The builder to run on our source files. - jsonPartBuilder(), - - // The name of the current package, usually the same as the name - // for your Flutter project. For example, if you created a new - // project with "flutter run hello", your package name here would - // be 'hello'. - '[[highlight]][[/highlight]]', - - // All of the files this `BuildAction` should target when run. - inputs: const [ - 'lib/*.dart', - ], - ), - ]; -{% endprettify %} - -We are declaring a top-level variable called `buildActions`, which returns a -`List` containing all build actions in our project. In this case, we only have -one build action, which generates JSON serialization logic for files in the -_lib_ folder. - -It is good practice to define build actions in the same file, as they are used -by the _build_ and the _watcher_ files. - -Which brings us to the next topic, the build file. +#### Generating code continuously -#### Creating the build file +A _watcher_ can make our source code generation progress more convenient. It watches changes in our project files and automatically builds the necessary files when needed. We can start the watcher by running `flutter packages pub run build_runner watch` in our project root. -Our build file runs the actions responsible for generating JSON serialization -logic for our classes. When run, it triggers the build actions, searches for all -the relevant files, generates the necessary code and then complete. +It is safe to start the watcher once and leave it running in the background. -We'll create the build file in `/tool/build.dart`. +### Consuming json_serializable models -**tool/build.dart** +To deserialize a JSON string `json_serializable` way, we do not have actually to +make any changes to our previous code. ```dart -import 'dart:io'; -import 'package:build_runner/build_runner.dart'; -import 'build_actions.dart'; - -main() async { - var result = await build(buildActions, deleteFilesByDefault: true); - if (result.status == BuildStatus.failure) { - exitCode = 1; - } -} +Map userMap = JSON.decode(json); +var user = new User.fromJson(userMap); ``` -Now we can generate all the necessary JSON serialization source files by running -`dart tool/build.dart` in our project's root folder. While the build file is all -we need for one-time source code generation, it would be nice if we did not have -to run it manually every time we make changes. - -#### Creating the watcher file - -A _watcher_ makes our source code generation progress more convenient. It -watches changes in our project and automatically builds the necessary files when -needed. - -The watcher file also lives in the tool folder, so we create it in -`/tool/watch.dart`. - -**tool/watch.dart** +Same goes for serialization. The calling API is the same as before. ```dart -import 'package:build_runner/build_runner.dart'; -import 'build_actions.dart'; - -main() { - watch(buildActions, deleteFilesByDefault: true); -} +String json = JSON.encode(user.toJson()); ``` -We can run the watcher by doing `dart tool/watch.dart` in our project's root -folder. It is safe to start the watcher once and leave it running in the -background. \ No newline at end of file +With `json_serializable`, we can forget any manual JSON serialization in the +`User` class. The source code generator creates a file called `user.g.dart`, +which has all the necessary serialization logic. Now we do not necessarily have +to write automated tests to be sure that the serialization works - it is now +_the library's responsibility_ to make sure the serialization works +appropriately. + +## Further references + +* [JsonCodec documentation](https://api.dartlang.org/stable/1.24.3/dart-convert/JsonCodec-class.html) +* [The json_serializable package in Pub](https://pub.dartlang.org/packages/json_serializable) +* [json_serializable examples in GitHub](https://github.com/dart-lang/json_serializable/blob/master/example/lib/example.dart) +* [Discussion about dart:mirrors](https://github.com/flutter/flutter/issues/1150) \ No newline at end of file From 492720b0357cdbb568eeeb3c24110b1fdbfa8c6b Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Thu, 1 Feb 2018 22:10:53 +0200 Subject: [PATCH 07/11] Formatting. --- json.md | 57 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/json.md b/json.md index 74f1728175c..b4b51f2de30 100644 --- a/json.md +++ b/json.md @@ -5,11 +5,11 @@ permalink: /json/ --- It is hard to think of a mobile app that doesn't need to communicate with a web -server at some point. When making network-connected apps, the chances are that we -need to consume some good old JSON, sooner or later. +server at some point. When making network-connected apps, the chances are that +we need to consume some good old JSON, sooner or later. -In this tutorial, we look into ways of using JSON with Flutter. We go over -what JSON solution to use in different scenarios and why. +In this tutorial, we look into ways of using JSON with Flutter. We go over what +JSON solution to use in different scenarios and why. * TOC Placeholder {:toc} @@ -65,10 +65,10 @@ serialization, [see here](#code-generation). The simple answer is no. -Such a library would require using runtime reflection, which is disabled -in Flutter. Dart has supported _tree shaking_ for quite a long time. With tree shaking, we -can “shake off” unused code from our release builds. Tree shaking allows us to -optimize the size of our applications significantly. +Such a library would require using runtime reflection, which is disabled in +Flutter. Dart has supported _tree shaking_ for quite a long time. With tree +shaking, we can “shake off” unused code from our release builds. Tree shaking +allows us to optimize the size of our applications significantly. Since reflection makes all code implicitly used by default, it interferes with tree shaking. The tools cannot know what parts are unused at runtime; the @@ -79,8 +79,7 @@ using reflection. **What about dartson?** The [dartson](https://pub.dartlang.org/packages/dartson) library uses runtime -reflection, which makes it not compatible with Flutter. - +reflection, which makes it not compatible with Flutter. Although we cannot use runtime reflection with Flutter, some libraries give us similarly easy to use APIs but are based on code generation instead. This @@ -203,7 +202,9 @@ us. Luckily, there is! ## Serializing JSON using json_serializable -The [json_serializable package](https://github.com/dart-lang/json_serializable) is an automated source code generator that can generate the JSON serializing boilerplate for us. +The [json_serializable package](https://github.com/dart-lang/json_serializable) +is an automated source code generator that can generate the JSON serializing +boilerplate for us. Since the serialization code is not handwritten and maintained by us anymore, we minimize the risk of having JSON serialization exceptions at runtime. @@ -230,7 +231,8 @@ dev_dependencies: json_serializable: ^0.3.0 ``` -Run `flutter packages get` inside your project root folder (or click "Packages Get" in your editor) to make these new dependencies available in your project. +Run `flutter packages get` inside your project root folder (or click "Packages +Get" in your editor) to make these new dependencies available in your project. ### Creating model classes the json_serializable way @@ -273,9 +275,13 @@ class User extends Object with _$[[highlight]]User[[/highlight]]SerializerMixin } {% endprettify %} -With this setup, the source code generator will generate code for serializing the `name` and `email` fields from JSON and back. +With this setup, the source code generator will generate code for serializing +the `name` and `email` fields from JSON and back. -If needed, it is also easy to customize the naming strategy. For example, if the API we are working with returns objects with _snake\_case_, and we want to use _lowerCamelCase_ in our models, we can use the `@JsonKey` annotation with a name parameter: +If needed, it is also easy to customize the naming strategy. For example, if the +API we are working with returns objects with _snake\_case_, and we want to use +_lowerCamelCase_ in our models, we can use the `@JsonKey` annotation with a name +parameter: ```dart @@ -287,23 +293,34 @@ final int registrationDateMillis; ### Running the code generation utility -When creating `json_serializable` classes the first time, you will get errors similar to the image below. +When creating `json_serializable` classes the first time, you will get errors +similar to the image below. -![IDE warning when the generated code for a model class does not exist yet.](/images/json/ide_warning.png) +![IDE warning when the generated code for a model class does not exist +yet.](/images/json/ide_warning.png) -These errors are entirely normal and are simply because the generated code for the model class does not exist yet. To resolve this, we must run the code generator that generates the serialization boilerplate for us. +These errors are entirely normal and are simply because the generated code for +the model class does not exist yet. To resolve this, we must run the code +generator that generates the serialization boilerplate for us. There are two ways of running the code generator. #### One-time code generation -By running `flutter packages pub run build_runner build` in our project root, we can generate json serialization code for our models whenever needed. This triggers a one-time build which goes through our source files, picks the relevant ones and generates the necessary serialization code for them. +By running `flutter packages pub run build_runner build` in our project root, we +can generate json serialization code for our models whenever needed. This +triggers a one-time build which goes through our source files, picks the +relevant ones and generates the necessary serialization code for them. -While this is pretty convenient, it would nice if we did not have to run the build manually every time we make changes in our model classes. +While this is pretty convenient, it would nice if we did not have to run the +build manually every time we make changes in our model classes. #### Generating code continuously -A _watcher_ can make our source code generation progress more convenient. It watches changes in our project files and automatically builds the necessary files when needed. We can start the watcher by running `flutter packages pub run build_runner watch` in our project root. +A _watcher_ can make our source code generation progress more convenient. It +watches changes in our project files and automatically builds the necessary +files when needed. We can start the watcher by running `flutter packages pub run +build_runner watch` in our project root. It is safe to start the watcher once and leave it running in the background. From 9bb581a4fd20df6e21096e2fb2866364b6f2d1c9 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 7 Feb 2018 16:54:45 +0200 Subject: [PATCH 08/11] Incorporate latest feedback --- json.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/json.md b/json.md index b4b51f2de30..f21d248271d 100644 --- a/json.md +++ b/json.md @@ -5,8 +5,9 @@ permalink: /json/ --- It is hard to think of a mobile app that doesn't need to communicate with a web -server at some point. When making network-connected apps, the chances are that -we need to consume some good old JSON, sooner or later. +server or easily store structured data at some point. When making +network-connected apps, the chances are that we need to consume some good old +JSON, sooner or later. In this tutorial, we look into ways of using JSON with Flutter. We go over what JSON solution to use in different scenarios and why. @@ -16,7 +17,10 @@ JSON solution to use in different scenarios and why. ## Which JSON serialization method is right for me? -There isn't a one size fits all JSON solution for every project. +This article covers two general strategies for working with JSON: + +* Manual serialization and deserialization +* Automated serialization and deserialization via code generation Different projects come with different complexities and use cases. For smaller proof-of-concept projects or quick prototypes, using code generators might be @@ -79,7 +83,8 @@ using reflection. **What about dartson?** The [dartson](https://pub.dartlang.org/packages/dartson) library uses runtime -reflection, which makes it not compatible with Flutter. +reflection, which makes it not compatible with Flutter. + Although we cannot use runtime reflection with Flutter, some libraries give us similarly easy to use APIs but are based on code generation instead. This @@ -200,10 +205,11 @@ It would be nice if there were something that handled the JSON serialization for us. Luckily, there is! -## Serializing JSON using json_serializable +## Serializing JSON using code generation libraries -The [json_serializable package](https://github.com/dart-lang/json_serializable) -is an automated source code generator that can generate the JSON serializing +Although there are other libraries available, in this tutorial, we use the +[json_serializable package](https://github.com/dart-lang/json_serializable). It +is an automated source code generator that can generate the JSON serialization boilerplate for us. Since the serialization code is not handwritten and maintained by us anymore, we From becf677d7d1803247d5f1e2d941c2b4540e23b43 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 7 Feb 2018 21:55:31 +0200 Subject: [PATCH 09/11] Added a mention about the JSON tutorial in the FAQ. --- faq.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/faq.md b/faq.md index b63299edf83..96a4e195c0f 100644 --- a/faq.md +++ b/faq.md @@ -524,6 +524,9 @@ Absolutely. There are libraries in [pub.dartlang.org](https://pub.dartlang.org) for JSON, XML, protobufs, and many other utilities and formats. +For a detailed writeup on using JSON with Flutter, check out [the JSON +tutorial](/json/). + ### Can I build 3D (OpenGL) apps with Flutter? Today we don't support for 3D via OpenGL ES or similar. We have long-term plans From ac3d5602573420c4a355bf80cababd2f979f22e9 Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 7 Feb 2018 23:35:03 +0200 Subject: [PATCH 10/11] Removed redundant toJson() instructions and switched the json_serializable link to be a Pub one. --- json.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/json.md b/json.md index f21d248271d..196d2d5b6f9 100644 --- a/json.md +++ b/json.md @@ -184,11 +184,13 @@ print('Howdy, ${user.name}!'); print('We sent the verification link to ${user.email}.'); ``` -To serialize a user, we just call the `toJson` method in the `User` class. +To serialize a user, we just pass the `User` object to the `JSON.encode` method. +We don't need to call the `toJson` method here, since `JSON.encode` already does +it for us. ```dart -String json = JSON.encode(user.toJson()); +String json = JSON.encode(user); ``` This way, the calling code does not have to worry about JSON serialization at @@ -208,7 +210,7 @@ us. Luckily, there is! ## Serializing JSON using code generation libraries Although there are other libraries available, in this tutorial, we use the -[json_serializable package](https://github.com/dart-lang/json_serializable). It +[json_serializable package](https://pub.dartlang.org/packages/json_serializable). It is an automated source code generator that can generate the JSON serialization boilerplate for us. @@ -345,7 +347,7 @@ Same goes for serialization. The calling API is the same as before. ```dart -String json = JSON.encode(user.toJson()); +String json = JSON.encode(user); ``` With `json_serializable`, we can forget any manual JSON serialization in the From fd48345a2e1039e84a7846ee2419eb4c469649af Mon Sep 17 00:00:00 2001 From: Iiro Krankka Date: Wed, 7 Feb 2018 23:37:57 +0200 Subject: [PATCH 11/11] Switched to arrow notation for more compact toJson() method. --- json.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/json.md b/json.md index 196d2d5b6f9..24ced0ef6a1 100644 --- a/json.md +++ b/json.md @@ -163,12 +163,11 @@ class User { : name = json['name'], email = json['email']; - Map toJson() { - return { + Map toJson() => + { 'name': name, 'email': email, }; - } } ```