Search
πŸ₯Œ

Flutter Json Object Mapper

생성일
2021/05/11 16:04
νƒœκ·Έ
Flutter
속성

Dependency

https://pub.dev/packages/dart_json_mapper λ₯Ό μ‚¬μš©ν•œλ‹€
flutter pub add dart_json_mapper
Bash
볡사
pubspec.yaml νŒŒμΌμ— build_runnerλ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
... dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. ... dart_json_mapper: ^2.1.17 dev_dependencies: flutter_test: sdk: flutter build_runner: πŸ‘ˆπŸ‘ˆπŸ‘ˆπŸ‘ˆπŸ‘ˆπŸ‘ˆπŸ‘ˆπŸ‘ˆπŸ‘ˆ 이 뢀뢄이 μΆ”κ°€λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.
YAML
볡사
그리고 `flutter pub get` λͺ…λ Ήμ–΄λ‘œ dependency μ„€μΉ˜λ₯Ό μ§„ν–‰ν•©λ‹ˆλ‹€.

build.yaml 생성

flutter root 디렉토리에 build.yaml νŒŒμΌμ„ μƒμ„±ν•˜κ³  μ•„λž˜ λ‚΄μš©μ„ λΆ™μ—¬λ„£λŠ”λ‹€.
targets: $default: builders: dart_json_mapper: generate_for: # here should be listed entry point files having 'void main()' function - lib/main.dart πŸ‘ˆπŸ‘ˆπŸ‘ˆπŸ‘ˆπŸ‘ˆπŸ‘ˆ main() ν•¨μˆ˜κ°€ μžˆλŠ” 파일 pathλ₯Ό μ •ν™•ν•˜κ²Œ 적어야 ν•©λ‹ˆλ‹€ # This part is needed to tell original reflectable builder to stay away # it overrides default options for reflectable builder to an **empty** set of files reflectable: generate_for: - no/files
YAML
볡사
그리고 μ•„λž˜μ˜ λͺ…λ Ήμ–΄λ₯Ό μž…λ ₯ν•œλ‹€
flutter pub run build_runner watch --delete-conflicting-outputs
Bash
볡사
μœ„ λͺ…λ Ήμ–΄λŠ” flutter ν”„λ‘œμ νŠΈλ₯Ό μ €μž₯ν•˜λ©΄ μ‹€ν–‰λ˜λ©°, *.dart νŒŒμΌμ„ 탐색 ν›„ @jsonSerializable μ–΄λ…Έν…Œμ΄μ…˜μ΄ 뢙은 ν΄λž˜μŠ€λ“€μ„ μ°Ύμ•„μ„œ main.mapper.g.dart νŒŒμΌμ„ 생성해쀀닀. 책상 μ•žμ— μ½”λ”© μ€€λΉ„λ₯Ό 마치고 μœ„ 슀크립트λ₯Ό μ‹€ν–‰ν•΄μ£Όμž.
fyi) reflection이 각 μ–Έμ–΄μ—μ„œ κ°€μž₯ μœ μš©ν•˜κ²Œ μ‚¬μš©λ˜λŠ” 뢀뢄은 json을 object 둜 mapping ν•  λ•Œ μ‚¬μš©λ©λ‹ˆλ‹€. 그리고 dart λŠ” reflection이 μ§€μ›λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μ•ˆλ˜λŠ”λ° μ™œ μ—¬κΈ°μ„œ 이야기 ν•˜λŠλƒ?
μ•ˆλ˜λ©΄ 되게 ν•˜λŠ”κ²Œ μ—”μ§€λ‹ˆμ–΄μ£ .. @jsonSerializaiton μ΄λΌλŠ” μ–΄λ…Έν…Œμ΄μ…˜μ„ λ§Œλ“€κ³  prebuild μ‹œμ μ— 슀크립트λ₯Ό ν†΅ν•΄μ„œ reflection 처럼 μ‚¬μš©ν•  수 μžˆλŠ” ν”„λ‘œνΌν‹°λ“€μ„ κ°•μ œ μ£Όμž…ν•˜λŠ” λ°©μ‹μœΌλ‘œ reflection 을 ν‰λ‚΄λƒˆμŠ΅λ‹ˆλ‹€.
μž‘μ—…μ΄ μ™„λ£Œλœλ‹€. 터미널 ν”„λ‘œμ„ΈμŠ€λŠ” μžλ™ μ’…λ£Œλ˜μ§€ μ•ŠμœΌλ‹ˆ [INFO] Succeeded after 15.6s with 1 outputs (1 actions) 문ꡬ가 λ‚˜μ˜€λ©΄ Ctrl+C 둜 ν”„λ‘œμ„ΈμŠ€λ₯Ό μ’…λ£Œμ‹œν‚¨λ‹€
Android Studio 둜 λŒμ•„μ™€μ„œ Directoryλ₯Ό 보면 main.mapper.g.dart 파일이 μƒμ„±λœ 것을 λ³Ό 수 μžˆλ‹€.
κ·Έ ν›„ main() ν•¨μˆ˜κ°€ μžˆλŠ” main.dart νŒŒμΌμ—μ„œ initializeJsonMapper() ν•¨μˆ˜λ₯Ό μ‹€ν–‰ν•΄μ£ΌλŠ” μ½”λ“œλ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.
void main() async { Constants.setEnvironment(Environment.STAGING); WidgetsFlutterBinding.ensureInitialized(); initializeJsonMapper(); πŸ‘ˆπŸ‘ˆπŸ‘ˆπŸ‘ˆ await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); runApp(App()); }
Dart
볡사

Trouble Shooting

1.
_memberSymbolMap is not assined
~.mapper.g.dart νŒŒμΌμ€ μƒμ„±λ˜μ—ˆμ§€λ§Œ λ‚΄λΆ€ _memberSymbolMap, _data λ³€μˆ˜κ°€ μ œλŒ€λ‘œ μ„ μ–Έλ˜μ§€ μ•Šμ•„ 컴파일 μ—λŸ¬κ°€ λ°œμƒν–ˆκ³  이λ₯Ό ν•΄κ²°ν–ˆμŠ΅λ‹ˆλ‹€.
void main() {} ν•¨μˆ˜κ°€ μžˆλŠ” νŒŒμΌμ—
import 'package:reflectable/reflectable.dart';
μœ„ μ½”λ“œ μΆ”κ°€κ°€ ν•„μš”ν•©λ‹ˆλ‹€. μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” importμ§€λ§Œ
flutter pub run build_runner watch --delete-conflicting-outputs
Bash
볡사
μœ„ λͺ…λ Ήμ–΄κ°€ μ •μƒμ μœΌλ‘œ μˆ˜ν–‰λ˜κΈ° μœ„ν•΄μ„œλŠ” μœ„ import ꡬ문을 λ„£μ–΄μ€˜μ•Ό μ •μƒμ μœΌλ‘œ λ™μž‘ν•¨μ„ ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€.
^ μœ„ μ΄μŠˆλŠ” json_dart_mapper μ΅œμ‹ λ²„μ „μ—μ„œ μˆ˜μ •λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
2.
μ •μ‹ λ‚˜κ°„ Unhandled Exception: Bad state: Reflectable has not been initialized. 이슈
void main() ν•¨μˆ˜κ°€ μžˆλŠ” 곳에 @JsonSerializable()을 μ‚¬μš©ν–ˆλ˜ νŒŒμΌμ„ μˆ˜μž‘μ—…μœΌλ‘œ import ν•΄μ€˜μ•Όν•©λ‹ˆλ‹€.
κ·Έ ν›„ ν„°λ―Έλ„μ—μ„œ
flutter pub run build_runner build --delete-conflicting-outputs
Bash
볡사
λͺ…λ Ήμ–΄λ₯Ό μ‹€ν–‰ν•˜κ³  ~.mapper.g.dart νŒŒμΌμ„ 보면 파일 λ‚΄μš©(?) 이 λ§Žμ•„μ§„ 것을 확인할 수 있고,
이 μƒνƒœμ—μ„œ flutter μ•± μ‹€ν–‰μ‹œ 정상 μˆ˜ν–‰λ¨μ„ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

API Response 싀무 예제

개인 ν”„λ‘œμ νŠΈμ—μ„œ μ‚¬μš©ν•˜λ˜ API Response Format 은 μ†Œκ°œν•œμ  μžˆμŠ΅λ‹ˆλ‹€. Flutter μ—μ„œλ„ λ‹Ήμ—°νžˆ λ™μΌν•œ format을 λ”°λ₯Ό μ˜ˆμ •μž…λ‹ˆλ‹€.

ResponseFormat.dart

import 'package:dart_json_mapper/dart_json_mapper.dart'; class ResponseJSON<T> { final ResponseHeader header; final T data; ResponseJSON({ this.header, this.data }); factory ResponseJSON.fromJson(Map<String, dynamic> json, Function(Map<String, dynamic>) fromJson) { return ResponseJSON( header: ResponseHeader.fromJson(json['header']), data: fromJson(json['data']), ); } } // Single Object νƒ€μž…μ˜ Responseλ₯Ό νŒŒμ‹±ν•˜κΈ° μœ„ν•œ Response Format class ResponseListJSON<T> { final ResponseHeader header; final List<T> data; ResponseListJSON({ this.header, this.data }); factory ResponseListJSON.fromJson(Map<String, dynamic> json, Function(Map<String, dynamic>) fromJson) { List<T> list = List<T>.from(json['data'].map((model) => fromJson(model))); return ResponseListJSON( header: ResponseHeader.fromJson(json['header']), data: list, ); } } // List νƒ€μž…μ˜ Responseλ₯Ό νŒŒμ‹±ν•˜κΈ° μœ„ν•œ Response Format
Dart
볡사

Promotion.dart

import 'package:dart_json_mapper/dart_json_mapper.dart'; class Promotion { final String brandName; final String brandLogoURL; final int boltPrice; final int boltEffectiveness; final int discountRate; final int priceMax; final int discountMax; final String description; Promotion({ this.brandName, this.brandLogoURL, this.boltPrice, this.boltEffectiveness, this.discountRate, this.priceMax, this.discountMax, this.description }); factory Promotion.fromJson(Map<String, dynamic> json) { return Promotion( brandName: json['brandName'], brandLogoURL: json["brandLogoURL"], boltPrice: json["boltPrice"], boltEffectiveness: json['boltEffectiveness'], discountRate: json['discountRate'], priceMax: json['priceMax'], discountMax: json['discountMax'], description: json['description'], ); } }
Dart
볡사
μœ„ μ½”λ“œλ₯Ό λͺ¨λ‘ μž‘μ„± ν•œ ν›„ μ•„λž˜ 2개 쀑 1개λ₯Ό μ‚¬μš©ν•˜λ©΄ λ©λ‹ˆλ‹€.
flutter pub run build_runner watch --delete-conflicting-outputs
Dart
볡사
파일 λ³€κ²½μ‹œ μžλ™μœΌλ‘œ main.mapper.g.dart νŒŒμΌμ„ μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€.
flutter pub run build_runner build --delete-conflicting-outputs
Dart
볡사
μˆ˜λ™μœΌλ‘œ μ—…λ°μ΄νŠΈ ν•©λ‹ˆλ‹€.

PromotionProvider.dart

import 'dart:convert'; import 'package:chai_booster/Entity/Promotion.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; class PromotionProvider with ChangeNotifier { List<Promotion> promotion = []; final client = http.Client(); // Action void fetchPromotions() async { final response = await client.get(Uri.parse('http://localhost:5001/chai-booster/asia-northeast3/api/booster/v1/promotions')); final data = ResponseListJSON<Promotion>.fromJson(json.decode(response.body), (data) => Promotion.fromJson(data)).data; promotion = data; notifyListeners(); } }
Dart
볡사
^ μœ„ μ½”λ“œμ—μ„œ λˆˆμ—¬κ²¨λ΄μ•Όν•  뢀뢄은 14번째 라인
final data = ResponseListJSON<Promotion>.fromJson(json.decode(response.body), (data) => Promotion.fromJson(data)).data;
Dart
볡사
μœ„μ™€ 같이 μ‚¬μš©ν•˜λ©΄ data 에 Promotion 배열이 κΉ”λ”ν•˜κ²Œ 떨어진닀.
끝.