새로운 강의는 이제 https://memi.dev 에서 진행합니다.
memi가 Vue & Firebase로 직접 만든 새로운 사이트를 소개합니다.
Flutter와 Firebase로 Android iOS 둘 다 만들기 11 JSON 데이터 처리하기
플러터로 JSON 데이터를 처리하는 방법을 다룹니다.
개요
플러터는 다트(dart)라는 언어를 사용합니다.
JSON(JavaScript Object Notation)이라는 데이터 형식은 결국 자스의 오브젝트형 변수인데 다트의 형(type)중에는 없습니다.
그래서 json으로 데이터를 주고 받을 때 항상 형변환을 신경쓰며 코딩해야되는 번거로움이 있습니다.
참고: https://flutter-ko.dev/docs/development/data-and-backend/json
Map
json과 비슷한 형이 있는데 바로 Map이라는 형입니다.
키와 값을 가진 변수를 만들 수 있습니다.
값 만들기
Map<String, String> data = { 'ab': 'xxx' };
Map<int, int> datai = { 111: 333 };
Map<String, dynamic> datad = { 'dd': 1.23 };
키와 값에 모두 형을 써줘야 됩니다..
키나 값이 다양할 경우에는 dynamic이란 형으로 지정하면 됩니다.
값 가져오기
appBar: AppBar(
title: Text(data['ab'] + ' ' + datano[111].toString() + ' ' + datad['dd'].toString()),
),
이런 식으로 가져오는 것이 가능합니다.
만약 없는 키(datano[123].toString()
) 를 사용하면 null이 출력됩니다.
아직
http로 가져와보기
지난번 만든 웹서버에서 get 요청을 할 때 json 형식으로 보내봅니다.
기본형
server
router
.get('/', (ctx, next) => {
ctx.body = { abc: 'test', def: 123 }
})
client
final r = await http.get(url);
print(r.body); // {"abc":"test","def":123}
출력은 문자열로 나오게 됩니다.
값을 참조하기 위해서는 문자열을 맵형으로 변경해야합니다.(String to Map)
String to map
final v = jsonDecode(r.body);
print(v); // {abc: test, def: 123}
print(v['abc']); // test
print(v['xxx']); // null
jsonDecode 함수로 간단하게 맵형 자료가 되었습니다.
복잡한 데이터
사실 json의 장점은 유연함입니다. 배열형도 있을 수 있고 하위로 다른 오브젝트가 올수 있습니다.
만들어 놓고 테스트를 해봅니다.
server
.get('/', (ctx, next) => {
const user = {
email: 'a@a.aaa',
name: 'namesss',
address: {
city: 'seoul',
no: 333
},
friends: ['aaa', 'bbb']
}
ctx.body = user
})
client
final r = await http.get(url);
final v = jsonDecode(r.body);
print(v);
print(v['address']['city']); // seoul
print(v['friends'][1]); // bbb
예상했던 대로 잘 나옵니다.
하지만 상당히 가독성도 떨어지고 예외처리를 많이해야할 코드 입니다.
없는 주소를 사용하면
print(v['friends'][3]);
바로 에러가 납니다.
그래서 보통 이럴 때는 클래스를 만들어 관리하는 것이 좋습니다.
클래스로 테스트하기 만들기
위의 내용으로 클래스를 만들면..
class
class Address {
String city;
int no;
Address(this.city, this.no);
}
class User {
String email;
String name;
Address address;
List<dynamic> friends;
User(this.email, this.name, this.address, this.friends);
}
이렇게 만들 수 있습니다.
friends가 dynamic인 이유는 기본적으로 jsonDecode할 때 <String, dynamic> 으로 값들이 구성되기 때문입니다.
cient
Address address = Address(v['address']['city'], v['address']['no']);
User user = User(v['email'], v['name'], address, v['friends']);
print(user.email);
print(user.address.city);
print(user.friends[1]);
사용은 위와 같이 사용할 수 있습니다.
고작 쩜 하나 찍자고 요란합니다.
class 변환 로직 추가하기
저런 데이터를 다루는 클래스를 보통 모델이라 하는데 간단한 양방향 변환이 가능한 함수를 넣어서 관리하는 것이 편합니다.
class Address {
String city;
int no;
Address(this.city, this.no);
Address.fromJson(Map<String, dynamic> json)
: city = json['city'],
no = json['no'];
Map<String, dynamic> toJson() =>
{
'city': city,
'no': no,
};
}
class User {
String email;
String name;
Address address;
List<dynamic> friends;
User(this.email, this.name, this.address, this.friends);
User.fromJson(Map<String, dynamic> json)
: name = json['name'],
email = json['email'],
address = Address.fromJson(json['address']),
friends = json['friends'];
Map<String, dynamic> toJson() =>
{
'name': name,
'email': email,
'address': address,
'friends': friends
};
}
사실 지저분한 코드를 정돈한 느낌으로만 보이지만 사용할때 편합니다.
client
User user = User.fromJson(v);
print(user.email);
print(user.address.city);
print(user.friends[1]);
사용하니 편리합니다~
JSON 직렬화 사용하기
위의 예제까지는 그럭저럭 쓸만합니다.
하지만 실제 운용해보면 훨씬 더 많은 필드들이 필요합니다.
그 때마다 저 반복적인 바보짓을 계속하는 것은 가슴이 답답해집니다.
class User {
String email;
String name;
Address address;
List<dynamic> friends;
int age;
List<dynamic> parents;
int like;
// more more
User(this.email, this.name, this.address, this.friends, this.age, this.parents, this.like);
User.fromJson(Map<String, dynamic> json)
: name = json['name'],
email = json['email'],
address = Address.fromJson(json['address']),
friends = json['friends'],
age = json['age'],
parents = json['parents'],
like = json['like']; //,more
Map<String, dynamic> toJson() =>
{
'name': name,
'email': email,
'address': address,
'friends': friends,
'age': age,
'parents': parents,
'like': like
// more
};
}
필드 하나 늘어날 때 마다 3줄은 기본이며.. 오타도 유심히 봐야하죠..
그래서 직렬화 유틸리티가 있습니다.
클래스 파일 만들기
직렬화 유틸리티를 사용하기 전에 모델들을 파일로 정리해야합니다.
User, Address 클래스를 각각의 파일로 저장합니다.
- models/user.dart
- models/address.dart
main.dart 상단에
import 'models/address.dart';
import 'models/user.dart';
해주고 잘 되는 지 확인하면 준비 끝입니다.
자스에 비해 불편한 점이 많지만, 이런 면(코드 재활용)은 상당히 편리합니다.
설치
pubspec.yaml
dependencies:
# 다른 의존성들
json_annotation: ^2.0.0
dev_dependencies:
# 다른 개발 의존성들
build_runner: ^1.0.0
json_serializable: ^2.0.0
요렇게 3가지만 추가해줍니다.
직렬화 클래스 만들기
models/address.dart
import 'package:json_annotation/json_annotation.dart';
part 'address.g.dart';
@JsonSerializable()
class Address {
String city;
int no;
Address(this.city, this.no);
factory Address.fromJson(Map<String, dynamic> json) => _$AddressFromJson(json);
Map<String, dynamic> toJson() => _$AddressToJson(this);
}
models/user.dart
import 'package:json_annotation/json_annotation.dart';
import 'address.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
String email;
String name;
Address address;
List<dynamic> friends;
int age;
List<dynamic> parents;
int like;
User(this.email, this.name, this.address, this.friends, this.age, this.parents, this.like);
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
클래스 명 기반으로 이제 자동으로 변환이 가능한 클래스입니다.
직렬화를 하고 나면 이제 필드값만 추가해도 알아서 코드를 작성해줍니다.(*.g.dart)
빌드하기
아직 사용이 안됩니다.
*.g.dart로 만들려면 명령어를 사용해야합니다.
한번만 하기
$ flutter pub run build_runner build
자동으로 추가 즉시 변환하기
$ flutter pub run build_runner watch
빌드를 매번 해야하는 번거로움이 있지만 필드 추가는 훨씬 간단해졌습니다.
요약
- 변환: 문자(String) > 맵(Map<String, dynamic>) > 클래스(class)
- 방법: jsonDecode -> fromJson
댓글남기기