본문 바로가기
Study/Flutter

[Flutter] JSON 직렬화, json_annotation을 통한 제너레이트

https://flutter-ko.dev/docs/development/data-and-backend/json

 

JSON과 직렬화

어느 시점부터 웹 서버와 통신하지 않거나 구조화된 데이터를 적절하게 보관하지 않는 모바일 앱을생각하기 어려워졌습니다. 네트워크와 연결된 앱을 제작할 때, 결국에는 제법 괜찮은 JSON을사

flutter-ko.dev

 

JSON 데이터

웹 서버와 통신할 때, 구조화된 데이터를 적절하게 다루기 위해서 JSON 형식에 데이터는 필수적이다.

내가 원하는 데이터 객체를 다루기 위해 JSON의 필수적인 직렬화 개념을 이해하고 다뤄보자.

 

 

 

 

일반적인 객체 코드형태

https://www.dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=1041 

위와 같은 복권 api를 호출하면 아래와 같은 JSON 형식의 문자열을 반환받는다

 

String json = 'LotteryNumber{drwNo: 1041, drwNoDate: 2022-11-12, drwtNo1: 6, drwtNo2: 7, drwtNo3: 9, drwtNo4: 11, drwtNo5: 17, drwtNo6: 18, bnusNo: 45, firstWinamnt: 935091165, firstPrzwnerCo: 25}';

/*
// 정형화한 모습이다
LotteryNumber {
    drwNo: 1041,
    drwNoDate: 2022 - 11 - 12,
    drwtNo1: 6,
    drwtNo2: 7,
    drwtNo3: 9,
    drwtNo4: 11,
    drwtNo5: 17,
    drwtNo6: 18,
    bnusNo: 45,
    firstWinamnt: 935091165,
    firstPrzwnerCo: 25
}
*/

이러한 json 형식에 문자열을 적절히 저장하거나 가공하기 위해서는  

디코딩(역직렬화) 과정으로 자료구조 (일반적으로 key:value 구조인 Map) 형식으로 먼저 변환해주고나서 

map 자료구조를 이용핸 객체에 데이터를 담아 객체를 만든다. 

이 그림이 핵심이다 [출저 : https://bebesoft.tistory.com/11 ]

 

 

# 로또번호 객체 코드: LotteryNumber - 생성자만 있는 상태

class LotteryNumber {
  final int drwNo;
  final String drwNoDate;
  final int drwtNo1;
  final int drwtNo2;
  final int drwtNo3;
  final int drwtNo4;
  final int drwtNo5;
  final int drwtNo6;
  final int bnusNo;
  final int firstWinamnt; // 당첨금
  final int firstPrzwnerCo;

  // 생성자
  LotteryNumber(
      this.drwNo,
      this.drwNoDate,
      this.firstWinamnt,
      this.drwtNo1,
      this.drwtNo2,
      this.drwtNo3,
      this.drwtNo4,
      this.drwtNo5,
      this.drwtNo6,
      this.bnusNo,
      this.firstPrzwnerCo
      );

}

 

getter & setter가 없이 만들어둔 기본적 형태의 클래스이다.

그렇다면 json형식으로 제공받은 string으로 LotteryNumber 객체를 하나 만들기 위해서는 어떻게 해야할까?

 

Map이라는 중간지 자료구조를 거쳐치기 위해서 json.encode / decode 를 이용한다.

 

JSON -> MAP -> OBJECT 변환과정

import 'dart:convert';
.
.
.

// json string -> Map<String, dynamic> 으로 변환
Map<String, dynamic> map = json.decode("LotteryNumber{drwNo: 1041, drwNoDate: 2022-11-12, drwtNo1: 6, drwtNo2: 7, drwtNo3: 9, drwtNo4: 11, drwtNo5: 17, drwtNo6: 18, bnusNo: 45, firstWinamnt: 935091165, firstPrzwnerCo: 25}");
// 해당 자료구조를 이용해 객체 생성
LotteryNumber ln = new LotteryNumber(map["drwNo"], map["drwNoDate"], ... );

위와같은 메소드를 통해 Map 자료구조에 구조적으로 담아주는 작업을 하는게

convert 패키지의 json.encode / decode 메소드이다.

 

그래야 직렬화 되어있는 문자열 사이에 Key값과 Value값을 구분하여 접근할 수 있기 때문이다.

 

map에 담긴 구조적인 데이터를 보다 쉽게 객체에 담아줄수 있는데,

이 과정을 객체 코드안에 

fromJson, toJson이라는 메소드라는 이름으로 일반적으로 구현한다.

 

# 로또번호 객체 코드: LotteryNumber   - fromJson, toJson 메소드의 구현

class LotteryNumber {
  final int drwNo;
  final String drwNoDate;
  final int drwtNo1;
  final int drwtNo2;
  final int drwtNo3;
  final int drwtNo4;
  final int drwtNo5;
  final int drwtNo6;
  final int bnusNo;
  final int firstWinamnt; // 당첨금
  final int firstPrzwnerCo;

  // 생성자
  LotteryNumber(
      this.drwNo,
      this.drwNoDate,
      this.firstWinamnt,
      this.drwtNo1,
      this.drwtNo2,
      this.drwtNo3,
      this.drwtNo4,
      this.drwtNo5,
      this.drwtNo6,
      this.bnusNo,
      this.firstPrzwnerCo
      );
  factory LotteryNumber.fromJson(Map<String, dynamic> json){
      return LotteryNumber(
          json['drwNo'] as int,
          json['drwNoDate'] as String,
          json['firstWinamnt'] as int,
          json['drwtNo1'] as int,
          json['drwtNo2'] as int,
          json['drwtNo3'] as int,
          json['drwtNo4'] as int,
          json['drwtNo5'] as int,
          json['drwtNo6'] as int,
          json['bnusNo'] as int,
          json['firstPrzwnerCo'] as int,
    );
  }
  ;
  Map<String, dynamic> toJson(LotteryNumber lotteryNumber){
  return <String, dynamic>{
      'drwNo': lotteryNumber.drwNo,
      'drwNoDate': lotteryNumber.drwNoDate,
      'drwtNo1': lotteryNumber.drwtNo1,
      'drwtNo2': lotteryNumber.drwtNo2,
      'drwtNo3': lotteryNumber.drwtNo3,
      'drwtNo4': lotteryNumber.drwtNo4,
      'drwtNo5': lotteryNumber.drwtNo5,
      'drwtNo6': lotteryNumber.drwtNo6,
      'bnusNo': lotteryNumber.bnusNo,
      'firstWinamnt': lotteryNumber.firstWinamnt,
      'firstPrzwnerCo': lotteryNumber.firstPrzwnerCo,
    };
  };
  
  // 출력을 위헤 재정의
  @override
  String toString() {
    return 'LotteryNumber{drwNo: $drwNo, drwNoDate: $drwNoDate, drwtNo1: $drwtNo1, drwtNo2: $drwtNo2, drwtNo3: $drwtNo3, drwtNo4: $drwtNo4, drwtNo5: $drwtNo5, drwtNo6: $drwtNo6, bnusNo: $bnusNo, firstWinamnt: $firstWinamnt, firstPrzwnerCo: $firstPrzwnerCo}';
  }
}

두가지 메소드를 추가한 상태는  json 형식에 데이터를 객체로 더욱 쉽게 담을 수 있다. 

이런 방식은 현재 정형화 되어있고, 위와같은 메소드를 한번에 자동으로 제너레이트 할 수가 있다.

 

// json string -> map -> object 순서로 변환됨
LotteryNumber lotteryNumber = LotteryNumber.fromJson(json.decode(제이쓴데이터));

// object -> map -> string 순서로 직렬화
String json = json.encode(LotteryNumber.toJson(lotteryNumber));

 

이러한 규칙은 어디에서나 적용되는데,

toJson과 fromJson을 모든 객체마다 작성하는건 너무 번거롭다

그래서 모두 한번에 자동으로 만들어주는 패키지 json_annotation가  존재한다.

 

 

JSON을 다루는 객체 메소드 자동생성

json_annotation 을 이용해 대규모 프로젝트에서 사용하는 객체 제너레이트를 알아보자

# pubspec.yaml

dependencies:
  # 다른 의존성들
  json_annotation: ^2.0.0

dev_dependencies:
  # 다른 개발 의존성들
  build_runner: ^1.0.0
  json_serializable: ^2.0.0

# 로또번호 객체 코드: LotteryNumber   -  json_annotation형태로 작성

import 'package:json_annotation/json_annotation.dart';
// 아래와 같은 형식으로 part 추가
part 'lottery_number.g.dart';

// 제일 중요한 필수 어노테이션
@JsonSerializable()
class LotteryNumber {
  final int drwNo;
  final String drwNoDate;
  final int drwtNo1;
  final int drwtNo2;
  final int drwtNo3;
  final int drwtNo4;
  final int drwtNo5;
  final int drwtNo6;
  final int bnusNo;
  final int firstWinamnt; // 당첨금
  final int firstPrzwnerCo;

  // 생성자
  LotteryNumber(
      this.drwNo,
      this.drwNoDate,
      this.firstWinamnt,
      this.drwtNo1,
      this.drwtNo2,
      this.drwtNo3,
      this.drwtNo4,
      this.drwtNo5,
      this.drwtNo6,
      this.bnusNo,
      this.firstPrzwnerCo
      );
  factory LotteryNumber.fromJson(Map<String, dynamic> json) => _$LotteryNumberFromJson(json);
  Map<String, dynamic> toJson() => _$LotteryNumberToJson(this);
  // 출력을 위헤 재정의
  @override
  String toString() {
    return 'LotteryNumber{drwNo: $drwNo, drwNoDate: $drwNoDate, drwtNo1: $drwtNo1, drwtNo2: $drwtNo2, drwtNo3: $drwtNo3, drwtNo4: $drwtNo4, drwtNo5: $drwtNo5, drwtNo6: $drwtNo6, bnusNo: $bnusNo, firstWinamnt: $firstWinamnt, firstPrzwnerCo: $firstPrzwnerCo}';
  }
}

 

 

위와 같은 규칙으로 작성해주고나서,

아래 명령어를 콘솔에 입력하면

flutter pub run build_runner build # 일회성 생성

or 

flutter pub run build_runner watch # 지속적 변경

 

, _$객체명FromJson , _$객체명ToJson 형태의 메소드를 가지는 

lottery_number.g.dart 를 자동으로 생성해줍니다.

 

 

https://flutter-ko.dev/docs/development/data-and-backend/json 더 많은 내용은 여기를 참조

 

JSON과 직렬화

어느 시점부터 웹 서버와 통신하지 않거나 구조화된 데이터를 적절하게 보관하지 않는 모바일 앱을생각하기 어려워졌습니다. 네트워크와 연결된 앱을 제작할 때, 결국에는 제법 괜찮은 JSON을사

flutter-ko.dev