Chapters

Hide chapters

Flutter Apprentice

Fourth Edition · Flutter 3.16.9 · Dart 3.2.6 · Android Studio 2023.1.1

Section II: Everything’s a Widget

Section 2: 5 chapters
Show chapters Hide chapters

Section IV: Networking, Persistence & State

Section 4: 6 chapters
Show chapters Hide chapters

11. Serialization With JSON
Written by Kevin D Moore

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

In this chapter, you’ll learn how to serialize JSON data into model classes. A model class represents data structure and defines attributes and operations for a particular object. An example is a recipe model class, which usually has a title, an ingredient list and steps to cook it.

You’ll continue with the previous project, which is the starter project for this chapter. You’ll add a class that models a recipe, and its properties. Then, you’ll integrate that class into the existing project.

By the end of this chapter, you’ll know:

  • How to serialize JSON into model classes.
  • How to use Dart tools to automate the generation of model classes from JSON.

What is JSON?

JSON, which stands for JavaScript Object Notation, is an open-standard format used on the web and in mobile clients. It’s the most widely used format for Representational State Transfer (REST)-based APIs that servers provide (https://en.wikipedia.org/wiki/Representational_state_transfer). If you talk to a server that has a REST API, it will most likely return data in a JSON format. An example of a JSON response looks something like this:

{
  "results": [
    {
      "id": 296687,
      "title": "Chicken",
      "image": "https://spoonacular.com/recipeImages/296687-312x231.jpeg",
      "imageType": "jpeg"
    },
    ...
    ]
  }

That’s an example recipe response containing a list of results with four fields inside an object.

While it’s possible to treat the JSON as just a long string and try to parse out the data, it’s much easier to use a package that already knows how to do that. Flutter has a built-in package for decoding JSON, but in this chapter, you’ll use the json_serializable and json_annotation packages to help make the process easier.

Note: JSON parsing is the process of converting a JSON object in String format to a Dart object that can be used inside a program.

Flutter’s built-in dart:convert package contains methods like json.decode() and json.encode(), which converts a JSON string to a Map<String, dynamic> and back. While this is a step ahead of manually parsing JSON, you’d still have to write extra code that takes that map and puts the values into a new class.

The json_serializable package is useful because it can generate model classes for you according to the annotations you provide via json_annotation. Before taking a look at automated serialization, you need to see how to manually serialize JSON.

Writing the Code Yourself

So, how do you go about writing code to serialize JSON yourself? Typical model classes have toJson() and fromJson() methods. The toJson() method helps to convert objects into JSON strings, and the fromJson() method helps to parse a JSON string into an object so you can use it inside the program.

class Recipe {
  final String uri;
  final String label;

  Recipe({this.uri, this.label});
}
factory Recipe.fromJson(Map<String, dynamic> json) {
  return Recipe(json['uri'] as String, json['label'] as String);
}

Map<String, dynamic> toJson() {
  return <String, dynamic>{ 'uri': uri, 'label': label}
}

Automating JSON Serialization

In this chapter, you’ll use two packages: json_annotation and json_serializable from Google.

Adding Dependencies for JSON Serialization and Deserialization

Continue with your current project, or open the starter project in the projects folder. Add the following package to pubspec.yaml in the Flutter dependencies section underneath and make sure it’s aligned with flutter_riverpod:

json_annotation: ^4.8.1
json_serializable: ^6.7.1

Generating Model Classes From JSON

The JSON that you’re trying to serialize looks something like this:

{
  "results": [
    {
      "id": 296687,
      "title": "Chicken",
      "image": "https://spoonacular.com/recipeImages/296687-312x231.jpeg",
      "imageType": "jpeg"
    },
    {
      "id": 379523,
      "title": "Chicken",
      "image": "https://spoonacular.com/recipeImages/379523-312x231.jpeg",
      "imageType": "jpeg"
    },
    ...
  ],
  "offset": 0,
  "number": 10,
  "totalResults": 51412
}

Creating Model Classes

Start by opening lib/network/spoonacular_model.dart and add the following import at the top:

import 'package:json_annotation/json_annotation.dart';
import '../data/models/models.dart';

part 'spoonacular_model.g.dart';
@JsonSerializable()
class SpoonacularResults {
  // TODO: Add Fields
  // TODO: Add Constructor
  // TODO: Add fromJson
  // TODO: Add toJson
}

// TODO: Add SpoonacularResult
...

/// Creates a new [JsonSerializable] instance.
const JsonSerializable({
  @Deprecated('Has no effect') bool? nullable,
  this.anyMap,
  this.checked,
  this.constructor,
  this.createFieldMap,
  this.createFactory,
  this.createToJson,
  this.disallowUnrecognizedKeys,
  this.explicitToJson,
  this.fieldRename,
  this.ignoreUnannotated,
  this.includeIfNull,
  this.converters,
  this.genericArgumentFactories,
  this.createPerFieldToJson,
});

...

Converting to and From JSON

Now, you need to add JSON conversion methods within the SpoonacularResults class. Return to spoonacular_model.dart and replace // TODO: Add Fields with:

List<SpoonacularResult> results;
int offset;
int number;
int totalResults;
SpoonacularResults({
  required this.results,
  required this.offset,
  required this.number,
  required this.totalResults,
});
factory SpoonacularResults.fromJson(Map<String, dynamic> json) =>
    _$SpoonacularResultsFromJson(json);
Map<String, dynamic> toJson() => _$SpoonacularResultsToJson(this);
// 1
@JsonSerializable()
class SpoonacularResult {
  // 2
  int id;
  String title;
  String image;
  String imageType;

  // 3
  SpoonacularResult({
    required this.id,
    required this.title,
    required this.image,
    required this.imageType,
  });

  // 4
  factory SpoonacularResult.fromJson(Map<String, dynamic> json) =>
      _$SpoonacularResultFromJson(json);


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

Generating the code for JSON Serialization and Deserialization

Open the terminal in Android Studio by clicking the Terminal panel in the lower left, or by selecting View ▸ Tool Windows ▸ Terminal, and type:

dart run build_runner build
[INFO] Generating build script completed, took 155ms
[INFO] Precompiling build script... completed, took 3.3s
[INFO] Building new asset graph completed, took 372ms
[INFO] Checking for unexpected pre-existing outputs. completed, took 15.0s
[INFO] Generating SDK summary completed, took 2.3s
[INFO] Running build completed, took 11.8s
[INFO] Caching finalized dependency graph completed, took 44ms
[INFO] Succeeded after 11.8s with 11 outputs (82 actions)
➜
[INFO] Found 6 declared outputs which already exist on disk. This is likely because the`.dart_tool/build` folder was deleted, or you are submitting generated files to your source repository.
Delete these files?
1 - Delete
2 - Cancel build
3 - List conflicts
1
dart run build_runner watch
// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'spoonacular_model.dart';
// 1
SpoonacularResults _$SpoonacularResultsFromJson(Map<String, dynamic> json) =>
    SpoonacularResults(
      // 2
      results: (json['results'] as List<dynamic>)
          .map((e) => SpoonacularResult.fromJson(e as Map<String, dynamic>))
          .toList(),
      // 3
      offset: json['offset'] as int,
      // 4
      number: json['number'] as int,
      // 5
      totalResults: json['totalResults'] as int,
    );

Testing the Generated JSON Code

Now that you can parse model objects from JSON, you’ll read one of the JSON files included in the starter project and show one card to make sure you can use the generated code.

import 'dart:convert';
import '../../network/spoonacular_model.dart';
import 'package:flutter/services.dart';
// 1
final jsonString = await rootBundle.loadString('assets/recipes1.json');
// 2
final spoonacularResults =
    SpoonacularResults.fromJson(jsonDecode(jsonString));
// 3
final recipes = spoonacularResultsToRecipe(spoonacularResults);
// 4
final apiQueryResults = QueryResult(
    offset: spoonacularResults.offset,
    number: spoonacularResults.number,
    totalResults: spoonacularResults.totalResults,
    recipes: recipes);
// 5
currentResponse = Future.value(Success(apiQueryResults));

Mock Service

Now that you’ve manually loaded a sample JSON file, it’s time to implement the Mock Service. This service class will randomly load one of two recipe files: One for chicken and one for pasta.

final recipeService = ref.watch(serviceProvider);
currentResponse = recipeService.queryRecipes(
    searchTextController.text.trim(), currentStartPosition, pageCount);
import 'dart:convert';
import 'package:flutter/services.dart';
import '../network/spoonacular_model.dart';
import 'mock_service/mock_service.dart';
final service = await MockService.create();
serviceProvider.overrideWithValue(service),

Key Points

  • JSON is an open-standard format used on the web and in mobile clients, especially with REST APIs.
  • In mobile apps, JSON code is usually parsed into the model objects that your app will work with.
  • You can write JSON parsing code yourself, but it’s usually easier to let a JSON package generate the parsing code for you.
  • json_annotation and json_serializable are packages that will let you generate the parsing code.

Where to Go From Here?

In this chapter, you’ve learned how to create models that you can parse from JSON and then use when you fetch JSON data from the network. If you want to learn more about json_serializable, go to https://pub.dev/packages/json_serializable.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now