Flutter Json (Json_serializable)

Updated:

Json

  • 모든 형태의 웹 / 모바일 App 은 API 와 통신하기 위해 Json 을 필수 적으로 사용되게 됩니다. 그러나, Json 의 데이터의 양이 많아 질 수록 구조가 점점 복잡하게 되어져서 사용하기 불편할때가 많습니다. (nesting 형태로 key : value 에서 value 가 다른 object 를 가져서 하위로 계속 key: value 형태로 가지치기가 될때 데이터의 양이 많아질 수 록 관리하기기 복잡하게 됩니다)

Json 에 대해 간단 정리

  • Most widely used dat format for data interchange on the web interface

  • Primary data structure of Json

    • A collection of name / value pairs

    • An ordered list of values

  • Json seperator / token

    • ”:”(between name and value), “,”(between name / value pairs), “{“,”}” (for object), “[”,”]”(for arrays)
  • Json values

    • String, numbers, objects, arrays, booleans, null(or empty)
  • 특히, flutter 에서 Json 을 사용할때 map 을 사용해서 string type 으로 사용하는데 Json 의 value 의 type 이 dynamic 이 되기 때문에 나중에 type checking 할 때, 에러가 많이 발생 됩니다. 그래서 그것을 방지하고자 주로, class 를 만들어서 converting 하는데 두가지 function 을 만드는데, fromJson(), toJson() 이라는 method 를 만듭니다. 근데 그 만드는 과정이 데이터가 많을 경우 코드의 양이 상당히 많아지고 시간도 많이 소요 됩니다

  • 이러한 과정을 자동화 할 수 있고 error 를 미리 방지하기 위해서 flutter 에서는 주로 Json_serializable package 를 사용합니다

image

image

image

serialization, deserialization ?

  • Encoding == serialization == data structure 를 string 타입으로 변경

  • Decoding == deserialization == string 타입을 data structure 로 변경

Json 을 serialization 하는 2가지 방법

  • Manual Serialization (수동 직렬화) - dart:convert 사용해서 직접 코드를 작성해서 직렬화 하기

  • Automated serialization using code generation (코드 생성을 통한 자동 직렬화) - json_serializable, built_value 등 라이브러리를 사용해서 Json 직렬화 하기

Manual Serialization

  • 데이터 양이 얼마 없을때 주로 사용합니다

예시

var jsonString = '{
  "name" : "Jacob ko",
  "email" : "jacobko@info.com",
  "age" : 30
}'

위의 Json 형식의 데이터를 serialization 하기위해서 다음과 같이 진행 합니다.

  • User 클래스 생성

  • User.formJson() 생성자를 만들어서 map 으로 부터 User 를 생성합니다

  • User.toJson() 반대로 User 를 map 형태로 변환 시킵니다


// Manual Serialization 예시
// user.dart

class User {
  final String name;
  final String email;
  final int age;

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

// fromJson
  User.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        email = json['email'],
        age = json['age'];

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

Map userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);

print('How are you ${user.name}');
print('We sent the verification link to ${user.email}.');


String json = jsonEncode(user);

json_serializable package 설치

dependencies:
  # 실제 deploy 할때 json 형태 사용되는 package
  json_annotation:

dev_dependencies:
  # build_runner: json_serializable 의 spec 에 맞게 data 구조를 만들고, 변환 시킬때 사용되는 package
  build_runner:
  json_serializable:

json_serializable 을 사용해 json 데이터를 처리하기

import 'package:json_annotation/json_annotation.dart';

// This allows the `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 'user.g.dart'; // 이부분에서 에러표시가 발생함.

// JSON 직렬화 로직이 만들어져야 한다고 알림 어노테이션
@JsonSerializable()

class User {
  User(this.name, this.email. this.registDateMill);

  String name;
  String email;

// @JsonKey를 사용하여 네이밍전략을 알려줄수 있다.
// name: 네이밍 전략, required(t/f): 무조건 포함해야 하는 키 인지 여부,
//defaultValue(t/f): Json에 해당 키가 없어도 되거나 값이 'null'인 경우.

@JsonKey(name: 'registration_date_millis')
final int registrationDateMillis;

/// map에서 User를 생성하기 위한 팩토리 생성자.
/// Pass the map to the generated `_$UserFromJson()` constructor.
/// 생성자의 이름은 클래스 명을 따른다. ( _$+클래스명+FromJson )
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

  /// `toJson` JSON  인코딩을 지원을 선언하는 규칙.
  /// The implementation simply calls the private, generated
  /// helper method `_$UserToJson`. ( _$+클래스명+ToJson)
  Map<String, dynamic> toJson() => _$UserToJson(this);
}
  • 위의 코드를 작성하고 터미널에서 flutter pub run build_runner build 실행하면 동일 경로에 user.g.dart 에 code generation 이 생성된다. 일회성이 아닌 지속적인 변경 감지를 위해 watch 모드를 실행하면 된다 flutter pub run build_runner watch 실행

  • 사용법은 manual 부분과 마찬가지로 실행하면 됩니다

Map userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);

String json = jsonEncode(user);

json_serializable package 실전 사용 예시

OpenWeather API

Json 형태의 OpenWeather API 를 예시로 fetch data 를 해서 json_serializable 패키지를 통해 flutter 에서 Json 사용하는 예시 코드 입니다.

// https://api.openweathermap.org/data/2.5/weather?q=seoul&appid=$KEY

{
  "coord": {
    "lon": 126.9778,
    "lat": 37.5683
  },
  "weather": [
    {
      "id": 800,
      "main": "Clear",
      "description": "clear sky",
      "icon": "01n"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 285.09,
    "feels_like": 284.06,
    "temp_min": 281.38,
    "temp_max": 286.88,
    "pressure": 1024,
    "humidity": 66
  },
  "visibility": 10000,
  "wind": {
    "speed": 0.51,
    "deg": 150
  },
  "clouds": {
    "all": 0
  },
  "dt": 1635508436,
  "sys": {
    "type": 1,
    "id": 8105,
    "country": "KR",
    "sunrise": 1635458015,
    "sunset": 1635496672
  },
  "timezone": 32400,
  "id": 1835848,
  "name": "Seoul",
  "cod": 200
}
// open_weather_screen.dart (UI widget)

import 'dart:convert';

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

// json model
import 'package:json/models/open_weather/open_weather.dart';


class OpenWeatherScreen extends StatelessWidget {

  // Future type getWeather 함수
  Future<OpenWeather> getWeather() async {
    try {
      const String url =
          'http://api.openweathermap.org/data/2.5/weather?q=seoul&appid=$KEY';

      final http.Response response = await http.get(url);
      final responseData = json.decode(response.body);
      final OpenWeather ow = OpenWeather.fromJson(responseData);

      // Json 형태로 확인
      print(ow.toJson());

      return ow;
    } catch (err) {
      print(err);
      throw err;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flat Weather'),
      ),
      // FutureBuilder 생성
      body: FutureBuilder(
        future: getWeather(),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            final ow = snapshot.data;

            return ......

            } else if (snapshot.hasError) {
            return Center(
              child: Text(
                'Error: ${snapshot.error}',
                style: TextStyle(
                  fontSize: 24,
                ),
              ),
            );
          } else {
            return Center(
              child: CircularProgressIndicator(),
            );
          }
        },
      ),
    );
  }
}
// in coord.dart

*/ nesting 부분된 일부분 처리를 위한 model class 생성
  "coord": {
    "lon": 126.9778,
    "lat": 37.5683
  },
*/



import 'package:json_annotation/json_annotation.dart';

part 'coord.g.dart';

@JsonSerializable()
class Coord {
  final double lon;
  final double lat;

  Coord({required this.lon, required this.lat});

  factory Coord.fromJson(Map<String, dynamic> json) => _$CoordFromJson(json);
  Map<String, dynamic> toJson() => _$CoordToJson(this);
}

import 'package:json_annotation/json_annotation.dart';

// 아래의 자동화된 code source 를 coord.g.dart 파일에 넣으라는 것
part 'main.g.dart';

@JsonSerializable()
class Main {
  final double temp;
  // 실제로 API 에서 불러올때 의 원래의 값을 @JsonKey 로 지정해줘서 camelCase 로 변경해서 사용할 수 있도록 key 값을 매칭 해주는 것임
  @JsonKey(name: 'feels_like')
  final double feelsLike;
  @JsonKey(name: 'temp_min')
  final double tempMin;
  @JsonKey(name: 'temp_max')
  final double tempMax;
  final int pressure;
  final int humidity;

  Main(
      {required this.temp,
      required this.feelsLike,
      required this.tempMin,
      required this.tempMax,
      required this.pressure,
      required this.humidity});

  factory Main.fromJson(Map<String, dynamic> json) => _$MainFromJson(json);
  Map<String, dynamic> toJson() => _$MainToJson(this);
}
// in weather.dart

import 'package:json_annotation/json_annotation.dart';

// 아래의 자동화된 code source 를 coord.g.dart 파일에 넣으라는 것
part 'weather.g.dart';

@JsonSerializable()
class Weather {
  final int id;
  final String main;
  final String description;
  final String icon;

  Weather({
    required this.id,
    required this.main,
    required this.description,
    required this.icon,
  });

  factory Weather.fromJson(Map<String, dynamic> json) =>
      _$WeatherFromJson(json);
  Map<String, dynamic> toJson() => _$WeatherToJson(this);
}

// coord.dart , main.dart, weather.dart 에서 생성된것을 종합하기

import 'package:json_annotation/json_annotation.dart';
import 'package:json_serializable_practice/models/open_weather/coord.dart';
import 'package:json_serializable_practice/models/open_weather/main.dart';
import 'package:json_serializable_practice/models/open_weather/weather.dart';

// 아래의 자동화된 code source 를 coord.g.dart 파일에 넣으라는 것
part 'open_weather.g.dart';

@JsonSerializable(explicitToJson: true)
class OpenWeather {
  final Coord coord;
  final List<Weather> weather;
  final Main main;
  final int visibility;

  OpenWeather(
      {required this.coord,
      required this.weather,
      required this.main,
      required this.visibility});

  factory OpenWeather.fromJson(Map<String, dynamic> json) =>
      _$OpenWeatherFromJson(json);
  Map<String, dynamic> toJson() => _$OpenWeatherToJson(this);
}


🔶 🔷 📌 🔑

Reference

json_serializable pub.dev - https://pub.dev/packages/json_serializable

Heavy Fran - https://youtu.be/DgLLC4hiUgE

자몽이랑꼬부기 For Programming Code!! - https://hellowk1.blogspot.com/2020/10/flutterjson-and-serialization-1.html?view=sidebar

Categories:

Updated:

Leave a comment