Flutter初心者の学習記録 第8回:FlutterでのJSON処理マスターガイド

こんにちは、ジミーです!今回は、モバイルアプリ開発において避けて通れない重要なトピック「JSON処理」について深掘りしていきましょう。

APIとの連携が当たり前となった現代のアプリ開発において、JSONデータの取り扱いは基本中の基本スキルです。この記事では、FlutterでのシンプルなJSON処理から、複雑なネストされたJSONの扱い方まで、実践的なコード例と共に解説していきます。

JSONとは?

まずは基本から。JSONは「JavaScript Object Notation」の略で、軽量なデータ交換フォーマットです。人間が読み書きしやすく、マシンにとっても解析・生成が容易という特徴があります。

JSONの基本構造は以下の通りです:

  • オブジェクト: { } で囲まれ、"キー": 値 のペアで構成
  • 配列: [ ] で囲まれ、カンマで区切られた値のリスト
  • 値: 文字列、数値、真偽値、null、オブジェクト、配列

例えば、以下のようなJSONがあります:

{
  "name": "田中太郎",
  "age": 28,
  "isStudent": false,
  "hobbies": ["読書", "旅行", "プログラミング"],
  "address": {
    "city": "東京",
    "zipCode": "123-4567"
  }
}

FlutterでのJSON処理の基本

Flutterでは、主に以下の方法でJSONを処理します:

  1. dart:convertパッケージを使った手動変換
  2. モデルクラスを用いた自動変換

まずは、dart:convertパッケージによる基本的な処理方法を見ていきましょう。

1. JSONのエンコード・デコード

import 'dart:convert';

void main() {
  // JSONからDartのMap型への変換(デコード)
  String jsonString = '{"name": "田中太郎", "age": 28}';
  Map<String, dynamic> userData = jsonDecode(jsonString);
  
  print(userData['name']); // 田中太郎
  print(userData['age']); // 28
  
  // DartのMap型からJSONへの変換(エンコード)
  Map<String, dynamic> newUser = {
    'name': '佐藤花子',
    'age': 25,
    'isStudent': true
  };
  
  String newJsonString = jsonEncode(newUser);
  print(newJsonString); // {"name":"佐藤花子","age":25,"isStudent":true}
}

これが最も基本的なJSON処理です。しかし、実際のアプリ開発では、型の安全性やコードの保守性のために、JSONデータをモデルクラスに変換して扱うことが推奨されています。

モデルクラスを使ったJSON処理

より実践的なアプローチとして、JSONデータをモデルクラスに変換する方法を見てみましょう。

1. 手動でモデルクラスを作成する方法

class User {
  final String name;
  final int age;
  final bool isStudent;
  final List<String> hobbies;
  final Address address;

  User({
    required this.name,
    required this.age,
    required this.isStudent,
    required this.hobbies,
    required this.address,
  });

  // JSONからUserインスタンスを生成するファクトリメソッド
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      name: json['name'],
      age: json['age'],
      isStudent: json['isStudent'],
      hobbies: List<String>.from(json['hobbies']),
      address: Address.fromJson(json['address']),
    );
  }

  // UserインスタンスからJSONに変換するメソッド
  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'age': age,
      'isStudent': isStudent,
      'hobbies': hobbies,
      'address': address.toJson(),
    };
  }
}

class Address {
  final String city;
  final String zipCode;

  Address({required this.city, required this.zipCode});

  factory Address.fromJson(Map<String, dynamic> json) {
    return Address(
      city: json['city'],
      zipCode: json['zipCode'],
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'city': city,
      'zipCode': zipCode,
    };
  }
}

このモデルクラスを使って、JSONとの相互変換を行います:

void main() {
  String jsonString = '''
  {
    "name": "田中太郎",
    "age": 28,
    "isStudent": false,
    "hobbies": ["読書", "旅行", "プログラミング"],
    "address": {
      "city": "東京",
      "zipCode": "123-4567"
    }
  }
  ''';

  // JSONからUserインスタンスへ変換
  Map<String, dynamic> jsonMap = jsonDecode(jsonString);
  User user = User.fromJson(jsonMap);
  
  print(user.name); // 田中太郎
  print(user.address.city); // 東京
  
  // UserインスタンスからJSONへ変換
  Map<String, dynamic> userJson = user.toJson();
  String newJsonString = jsonEncode(userJson);
  print(newJsonString);
}

手動でモデルクラスを作成する方法は、少ないフィールド数であれば問題ありませんが、複雑なJSONになると記述が冗長になります。そこで便利なのが、自動生成ツールの活用です。

2. json_serializableパッケージを使った自動生成

複雑なJSONを扱う場合は、json_serializableパッケージを使用すると便利です。以下のステップで導入します:

  1. 必要なパッケージをpubspec.yamlに追加:
dependencies:
  json_annotation: ^4.8.1

dev_dependencies:
  build_runner: ^2.4.6
  json_serializable: ^6.7.1
  1. モデルクラスを定義:
import 'package:json_annotation/json_annotation.dart';

// この行が必要です
part 'user.g.dart';

@JsonSerializable(explicitToJson: true)
class User {
  final String name;
  final int age;
  final bool isStudent;
  final List<String> hobbies;
  final Address address;

  User({
    required this.name,
    required this.age,
    required this.isStudent,
    required this.hobbies,
    required this.address,
  });

  // これだけで自動生成されます
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

@JsonSerializable()
class Address {
  final String city;
  final String zipCode;

  Address({required this.city, required this.zipCode});

  factory Address.fromJson(Map<String, dynamic> json) => _$AddressFromJson(json);
  Map<String, dynamic> toJson() => _$AddressToJson(this);
}
  1. コード生成を実行:
flutter pub run build_runner build

この一連の流れで、JSONシリアライズ・デシリアライズのためのコードが自動生成されます。

実践例:RESTful APIからのデータ取得

最後に、実際のアプリ開発で頻繁に行われる、APIからのJSONデータ取得と処理の例を見てみましょう:

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

// ユーザーモデル
class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
}

class ApiExample extends StatefulWidget {
  @override
  _ApiExampleState createState() => _ApiExampleState();
}

class _ApiExampleState extends State<ApiExample> {
  late Future<List<User>> futureUsers;

  @override
  void initState() {
    super.initState();
    futureUsers = fetchUsers();
  }

  Future<List<User>> fetchUsers() async {
    final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));

    if (response.statusCode == 200) {
      List jsonResponse = jsonDecode(response.body);
      return jsonResponse.map((data) => User.fromJson(data)).toList();
    } else {
      throw Exception('Failed to load users');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('API サンプル'),
      ),
      body: Center(
        child: FutureBuilder<List<User>>(
          future: futureUsers,
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return ListView.builder(
                itemCount: snapshot.data!.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text(snapshot.data![index].name),
                    subtitle: Text(snapshot.data![index].email),
                  );
                },
              );
            } else if (snapshot.hasError) {
              return Text('${snapshot.error}');
            }
            return CircularProgressIndicator();
          },
        ),
      ),
    );
  }
}

JSONデータ処理のベストプラクティス

最後に、FlutterでのJSON処理におけるベストプラクティスをまとめます:

  1. 型安全性を確保する:可能な限りdynamic型の使用を避け、強い型付けを行いましょう
  2. null安全性に注意する:JSONデータにフィールドが存在しない場合の対応を考慮しましょう
  3. エラーハンドリング:JSONデコード時のエラーを適切に処理しましょう
  4. 大規模プロジェクトでは自動生成を活用json_serializableなどのツールを使用することで、ミスを減らし保守性を高めましょう
  5. テストを書く:JSON変換のテストを書くことで、予期せぬバグを防ぎましょう

まとめ

FlutterでのJSON処理は、アプリ開発の基本スキルの一つです。基本的なdart:convertパッケージの使用から、モデルクラスを活用した型安全な実装、さらにはjson_serializableを使った効率的なコード生成まで、さまざまなアプローチがあります。

プロジェクトの規模や複雑さに応じて、最適な方法を選択してください。特に大規模なプロジェクトでは、自動生成ツールを活用することで、開発効率と保守性を高めることができます。


ご質問やコメントがありましたら、下のコメント欄にお気軽にどうぞ。それでは、楽しいFlutter開発を!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です