Table of contents
인턴 중인 회사에서 json_serializable을 통한 데이터 직렬화와, retrofit을 통한 API 통신을 진행하기로 했다.
json_serializable은 Dart 객체와 JSON 데이터 간의 변환 작업을 자동화하여 데이터 직렬화를 담당하며, retrofit은 네트워크 API와의 효율적인 통신을 쉽게 구현하는 데 사용된다.
이 두 개 패키지를 사용함으로서 코드의 가독성과 확장성을 향상시키고, 데이터 처리 및 네트워크 통신의 복잡성을 감소시켜, 개발 시간을 단축하고 유지보수를 용이하게 하고자 목표한다.
난 이번에 둘 다 처음 접해봤기에, 이번 글에서 json_serializable에 대해 기록해보고자 한다.
json_serializable의 이점
json_serializable
은 Dart의 코드 생성 라이브러리 중 하나로, JSON 형식의 데이터를 Dart 객체로 변환하거나 Dart 객체를 JSON으로 변환하는 과정을 자동화한다. 이를 사용함으로서 얻을 수 있는 이점은 다음과 같이 있다:
1. 타입 안정성(Type Safety)
json_serializable
을 사용하면, 엄격하게 선언된 JSON 데이터를 만들어줌으로서, Dart의 타입 시스템의 이점을 활용할 수 있다. 타입에 대해 명확히 선언함으로서, 컴파일 타임에 오류를 잡을 수 있게 하고, 런타임 에러의 가능성을 줄여준다.
개발 생산과 유지 보수성(Productivity, Maintainability)
json_serializable
은 반복적인 JSON 파싱 코드를 사람이 직접 작성하는 대신, 어노테이션을 기반으로 해당 코드를 자동 생성해준다. 이는 개발 시간도 단축시켜주고, 수작업으로 인한 실수를 방지할 수 있게 해준다! 그리고, 사람이 직접 작성하는 것보다 일관되고, 오류가 적으며, 이해하기 쉽다. 서로 다른 사람이 JSON 모델을 작업하더라도 일관된 형태를 유지할 수 있게 된다는 장점도 있다.
또한 json_serializable
을 사용하면 모델 클래스가 변경될 때마다 JSON 파싱 코드를 수동으로 업데이트할 필요가 없어, 유지보수성이 크게 향상된다.
3. 커스터마이징(Customization)
단순히 기본적인 JSON 형태만 제공해주는 게 아니라, json_serializable
은 다양한 어노테이션을 통해 JSON 변환 과정을 세밀하게 제어할 수 있는 옵션을 제공한다. 예를 들어, 필드 이름 변환, 선택적 필드, 커스텀 변환 로직 등의 구현을 지원한다.
JSON 데이터 직렬화?
JSON 데이터 직렬화란, 데이터 구조나 객체 상태를 JSON(JavaScript Object Notation) 포맷의 문자열로 변환하는 과정을 말한다. (이와 반대로 '역직렬화'는 JSON 문자열을 다시 데이터 구조나 객체로 변환하는 과정이다.)
플러터에서 JSON 직렬화를 사용하는 이유는 여러 가지가 있지만, 가장 핵심적인 이유는 플러터 애플리케이션 개발 과정에서 외부 API나 서비스와의 데이터 교환을 용이하게 하기 위함이다. JSON은 통신을 위한 표준 포맷이고, 언어 독립적이기에 다양한 프로그래밍 언어와 플랫폼에서 쉽게 사용할 수 있다 (= 크로스 플랫폼 호환성 有). 또한, 데이터 구조의 명확성을 제공하고, 직렬화를 통해 데이터 크기를 줄여 성능도 최적화 할 수 있다.
플러터에서 json_serializable을 사용해보자!
0. pubspec.yaml 에 패키지를 추가해준다. 초기 설정 예시는 [여기]
1. 파일 작성
// 현재 파일명: test_model.dart
import 'package:json_annotation/json_annotation.dart';
part 'test_model.g.dart'; // 없으면 빌드 제대로 안됨. 클래스명 말고 파일명과 맞춰줘야 함!
@JsonSerializable()
class TestModel{
const TestModel({
required this.id,
required this.name,
this.profileImgUrl,
});
final int id;
final String name;
final String? profileImgUrl;
factory TestModel.fromJson(Map<String, dynamic> json) => _$TestModelFromJson(json);
Map<String, dynamic> toJson() => _$TestModelToJson(this);
}
json_annotation 패키지를 import 해준다
part '[파일명].g.dart';
를 포함해준다. 이 파일명으로 자동 생성(g - generated 인가 싶다)된 파일이 빌드된다.모델 이름과 필드를 정의해주고, 필요에 따라 추가적 옵션을 지정해준다.
- 처음엔 이런식으로 에러가 날 것이다. 아직 빌드를 안 해줘서 그러니 걱정하지 말자.
3. build
빌드를 돌릴 준비가 되었다면, 빌드 명령어를 콘솔에 입력해준다.
dart run build_runner build
혹시 이런 에러가 뜬다면
[INFO] Found 13 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, 2, 혹은 3을 입력해준다.
난 일일이 입력하기 귀찮아서 빌드시 명령어에 빌드 설정도 포함해줬었다.
dart run build_runner build --delete-conflicting-outputs
4. 결과물
돌리기 전엔 이렇게 에러 상태로 혼자 존재하던 파일이
빌드를 돌리면 .g 파일이 자동 생성되며 에러도 없어진다!
위와 같은 코드로 test_model.g.dart
를 생성해주면 다음과 같은 결과물이 나온다. 모든 걸 json_serializable
이 처리해주니 정말 편하다!
test_model.g.dart:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'test_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
TestModel _$TestModelFromJson(Map<String, dynamic> json) => TestModel(
id: json['id'] as int,
name: json['name'] as String,
profileImgUrl: json['profileImgUrl'] as String?,
);
Map<String, dynamic> _$TestModelToJson(TestModel instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'profileImgUrl': instance.profileImgUrl,
};
ref