Flutter에서 api를 사용하거나, 서버를 통해 데이터를 주고 받을 때, 또는 DB에 쿼리를 날려 데이터를 받을 때 데이터를 처리하는데 있어서 반복이 있고, 귀찮은 부분이 있다. 개발자들은 반복되는 것을 못 참지 않나! 그래서 jsonSerializable이 있다.
탄생배경이라고나 할까?ㅎㅎ
flutter는 많은 언어가 그렇듯, JSON 데이터를 직렬화/파싱을 해줘야 한다. 그리고 플러터에서는 일반적으로 데이터를 매핑할 수 있는 클래스를 별도로 만들어서 사용한다.
1) API를 통해 받을 속성들을 미리 지정하고, 생성자 선언과 맵핑을 자동으로 해주는 factory 생성자가 있다. 생성자는 파라미터에 어떤값을 넣어야하는지지정을 하기에 있는게 당연하지만, factory 생성자로 만든 fromJson은 '반복적'으로 속성을 '또' 넣어줘야 한다.
2) flutter에서 json 데이터를 사용하려면 이를 Map 으로 변경해서 사용해야 한다. json을 객체로 변경하기 위해 fromJson, 객체를 json으로 변경하기 위해 toJson함수를 만들어서 사용하는데, 이 때 문제점은 내부 변수가 변경되거나 추가, 삭제되면 해당 함수들을 모조리 수정해줘야 한다.
이러한 수정을 자동으로 해주는 것이 JsonSerializable이다.
위에 말했듯, 반복적인 부분을 참을 수 없는 개발자들은 이를 자동화시키길 원한 것 같다!
이 부분에 바로 JsonSerializable 패키지가 사용된다.
https://flutter-ko.dev/development/data-and-backend/json
※ 위 사이트는 플러터 개발자 사이트인데, 쉽게 말하면, 소규모 프로젝트는 직접 코딩해서 넣고, 규모가 좀 있으면 버그 날 수도 있고 찾기도 힘들고 코딩도 힘드니 자동 생성하라는 내용이다.
import 'package:authentication_study/common/const/data.dart';
enum RestaurantPriceRange {
expensive,
medium,
cheap,
}
class RestaurantModel {
final String id;
final String name;
final String thumbUrl;
final List<String> tags;
final RestaurantPriceRange priceRange;
final double ratings;
final int ratingsCount;
final int deliveryTime;
final int deliveryFee;
RestaurantModel({
required this.id,
required this.name,
required this.thumbUrl,
required this.tags,
required this.priceRange,
required this.ratings,
required this.ratingsCount,
required this.deliveryTime,
required this.deliveryFee,
});
factory RestaurantModel.fromJson({required Map<String, dynamic> json}) {
return RestaurantModel(
id: json['id'],
name: json['name'],
thumbUrl: 'http://$ip${json['thumbUrl']}',
tags: List<String>.from(json['tags']),
// enum 값을 매핑
priceRange: RestaurantPriceRange.values.firstWhere(
(e) => e.name == json['priceRange'],
),
ratings: json['ratings'],
ratingsCount: json['ratingsCount'],
deliveryTime: json['deliveryTime'],
deliveryFee: json['deliveryFee'],
);
}
}
위와 같은 코드가 있다고 생각해보자.
JsonSerializable 적용
어노테이션을 사용해 해당 클래스를 자동으로 JsonSerializable 코드를 생성시킬거라고 정의한다.
import 'package:json_annotation/json_annotation.dart';
part 'restaurant_model.g.dart';
enum RestaurantPriceRange {
expensive,
medium,
cheap,
}
@JsonSerializable()
class RestaurantModel {
final String id;
final String name;
final String thumbUrl;
final List<String> tags;
final RestaurantPriceRange priceRange;
final double ratings;
final int ratingsCount;
final int deliveryTime;
final int deliveryFee;
RestaurantModel({
required this.id,
required this.name,
required this.thumbUrl,
required this.tags,
required this.priceRange,
required this.ratings,
required this.ratingsCount,
required this.deliveryTime,
required this.deliveryFee,
});
}
// g.dart 적용하는 방법
// json -> 인스턴스를 만드는것
factory RestaurantModel.fromJson(Map<String, dynamic> json)
=> _$RestaurantModelFromJson(json);
// 인스턴스 -> json 으로 만드는것
Map<String, dynamic> toJson() => _$RestaurantModelToJson(this);
위와 같이 바꿀 수 있고,
1) 맨 위에 part로 g.dart 파일을 추가한다.
part '해당 모델 파일 path+.g.dart
예를 들면
part 'cursor_pagination_model.g.dart'
2) 터미널에 아래와 같이 입력하면 g.dart 파일이 생긴다.
flutter pub run build_runner build
g.dart 파일?
위와 같이 코드에 JsonSerializable을 적용하고 생긴 g.dart파일을 보면 아래와 같다.
part of 'restaurant_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
// 1) RestaurantModel 을 생성하는 인스턴스, 직접만든 factory 생성자와 거의 유사
RestaurantModel _$RestaurantModelFromJson(Map<String, dynamic> json) =>
RestaurantModel(
id: json['id'] as String,
name: json['name'] as String,
thumbUrl: json['thumbUrl'] as String,
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(),
priceRange:
$enumDecode(_$RestaurantPriceRangeEnumMap, json['priceRange']),
ratings: (json['ratings'] as num).toDouble(),
ratingsCount: json['ratingsCount'] as int,
deliveryTime: json['deliveryTime'] as int,
deliveryFee: json['deliveryFee'] as int,
);
// 2) 현재 인스턴스에서 다시 json 으로 바꾸는 코드가 자동으로 생긴것
Map<String, dynamic> _$RestaurantModelToJson(RestaurantModel instance) =>
<String, dynamic>{
// 현재 instance 의 id 값을 'id' 키값에 넣어준다.
'id': instance.id,
'name': instance.name,
'thumbUrl': instance.thumbUrl,
'tags': instance.tags,
'priceRange': _$RestaurantPriceRangeEnumMap[instance.priceRange]!,
'ratings': instance.ratings,
'ratingsCount': instance.ratingsCount,
'deliveryTime': instance.deliveryTime,
'deliveryFee': instance.deliveryFee,
};
const _$RestaurantPriceRangeEnumMap = {
RestaurantPriceRange.expensive: 'expensive',
RestaurantPriceRange.medium: 'medium',
RestaurantPriceRange.cheap: 'cheap',
};
저절로 생긴 g.dart 파일을 보면 Model을 생성하는 인스턴스가 생긴 걸 볼 수 있다.
직접 만든 factory 생성자와 thumUrl을 빼면 똑같이 자동 코딩된 것을 볼 수 있다.
따라서 직접 코딩할 필요가 없는 것이다.
※ g.dart 파일은 어차피 새로 생성되거나 수정된 .dart 파일 기준으로 g.dart가 새롭게 생성되기 때문에 수정할 필요가 없다.