flutterでserializeを使わずに、List形式のjsonとのやり取りをよしなにする

flutterでアプリを再起動しても過去データが消えないようにするにはどうすればよいでしょうか。
色々な方がローカルへのアクセス方法やjsonの書き込み方法といったものをまとめておられますが、複雑なものをやろうとするとやや面倒だったりします。

今回はserializeを使わずに、リスト形式のjsonのやり取りについてこの記事でまとめていければと思います。

## json形式のflutterへのマッピング

例えば、以下のようなjsonを考えます

```
{[
 {
  name: "田中太郎",
  age: 70,
  gender: "男性",
 },
 {
  name: "田中次郎",
  age: 60,
  gender: "男性",
 }
]}
```

複数の同じ構造体のデータを保存したいことがあることは往々にしてありますが、flutterでは少し工夫をする必要があります。
以下、２つの項目に分けてそれぞれこれらの形式のデータを読み書きできるプログラムを書いていきたいと思います。


1. 構造体をjsonとやり取りする
2. リスト形式のデータをjsonとやり取りする


### 構造体をjsonとやり取りする

flutterでは構造体と名前のつくstructみたいなものはなく、基本的にClassを使います。


```dart

class Person {
  Person({this.name, this.score, this.age, this.gender, this.date});

  String name;
  int age;
  String gender;
}
```
このようにすることで、

```
Person person = Person(
        name: "田中太郎",
        age: 70,
        gender: "男性",
      );
```
みたいにすれば呼び出すことができます。

しかし、jsonとやり取りをする場合は実はこれでは不十分で、jsonに対してclassの各要素のマッピングを行う必要があります。以下がそれぞれでマッピングを行った場合のPersonクラスの定義になります

```dart

class Person {
  Person({this.name, this.score, this.age, this.gender, this.date});

  String name;
  int age;
  String gender;

  factory Person.fromJson(Map<String, dynamic> json) {
    return Person(
      name: json['name'] as String,
      age: json['age'] as int,
      gender: json['gender'] as String,
    );
  }

  Map<String, dynamic> toJson() => {
        'name': name,
        'age': age,
        'gender': gender,
      };
}
```

このようにすることで、Class内の変数を各jsonの形式にマッピングすることが可能となります。


### リスト形式のデータをjsonとやり取りする
ここで、クラスからjsonへの書き込み・及びクラスへの読み込みについて考えたいと思います。
ローカル保存先はpath_providerのgetApplicationDocumentsDirectory()を使って取得します。


```dart

class PersonStorage {
  PersonStorage(this.filePath);

  String filePath;

  // read path
  Future<String> get _localPath async {
    final directory = await getApplicationDocumentsDirectory();

    return directory.path;
  }

  // get file
  Future<File> get _localFile async {
    final path = await _localPath;
    return File('$path/$filePath');
  }

  // convert from json to class
  List<History> parseEntries(String responseBody) {
    final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
    return parsed.map<Person>((json) => Person.fromJson(json)).toList();
  }

  // get as json
  Future<List<Person>> readEntries() async {
    try {
      final file = await _localFile;

      // Read the file
      String contents = await file.readAsString();
      List<Person> personList = parseEntries(contents);

      return personList;
    } catch (e) {
      // If we encounter an error, return []
      return [];
    }
  }

  // write
  Future<File> writeEntries(List<Person> personData) async {
    final file = await _localFile;

    // convert from list to json
    List<Map> encoder = [];
    for (var trial in personData) {
      encoder.add(trial.toJson());
    }

    // Write the file
    return file.writeAsString(json.encode(encoder));
  }
}

```

リスト形式でやり取りする場合にポイントとなるのがreadEntries・writeEntriesとなります。
readEntriesではjsonをparseして、それをクラスPersonのList形式にマッピングすることで読み込むことができます。リストとして使うために最後にtoListを使うことが必要になります。

またwriteEntriesについては、それぞれfor文で各Classを取り出し、json形式に変換してからListに詰め替える処理をしています(もしかしたらもう少しうまい方法があるかもしれません)。

これにより、List<Person>形式のデータを一括で読み込み/書き込みが可能になります。

### 利用方法

最後に、これらの関数の使い方について説明します。

これらをjsonとして読み込む場合は下記のように初期化します

```dart

String personPath = 'persons.json';
PersonStorage personStorage = new PersonStorage(personPath);
List<Person> personList = [];
```
このpersons.jsonが保存先のpathとなります。

読み込みの場合は、Widgetの中で下記の関数を呼ぶ必要があります。
下のケースではStatefulWidgetを想定しているのでsetStateを呼んでいますが、必ずしも必要はないはずです。

```dart

personStorage.readEntries().then((List<Person> value) {
      setState(() {
        personList = value;
      });
    });

```

書き込みは新しいpersonListをwriteEntriesに与えてあげるだけです。

```dart

personStorage.writeEntries(personList);
```

## まとめ
flutterでList形式のjsonの取扱いを説明しました。
本来だとserializeとか使うのが王道なのかもしれませんが、この形式だとほぼどこでも使い回せるかつ、個人的には扱いやすかったので選択肢の一つとして悪くはないのかなーと思います。


## 参考文献

[flutterでjson](https://qiita.com/tatsu/items/55188f7e7f7ac90f40c3)

[Parsing complex JSON in Flutter](https://medium.com/flutter-community/parsing-complex-json-in-flutter-747c46655f51)