Dart와 JSON 소개
Dart는 Google이 개발한 최신 클라이언트 최적화 프로그래밍 언어입니다. 특히, 단일 코드베이스로 모바일, 웹, 데스크톱용 고성능 네이티브 애플리케이션을 빌드할 수 있는 Flutter 프레임워크의 핵심 언어로 널리 알려져 있습니다. Dart는 객체 지향적이며 클래스 기반의 언어로, 많은 개발자에게 익숙한 C 스타일의 구문을 가지고 있어 생산적이고 효율적인 코드 작성을 돕습니다.
JSON(JavaScript Object Notation)은 현대 웹에서 데이터를 교환하기 위한 사실상의 표준으로 자리 잡은 경량 데이터 형식입니다. JSON은 사람이 읽고 쓰기 쉬우면서도, 기계가 파싱(해석)하고 생성하기에도 용이하다는 장점이 있습니다. 그 구조는 '이름-값' 쌍의 컬렉션(객체 또는 Map)과 순서가 있는 값의 목록(배열 또는 List)이라는 두 가지 단순한 형태로 이루어집니다.
Dart, 특히 Flutter로 앱을 개발하는 모든 개발자에게 JSON을 다루는 능력은 필수적입니다. 인터넷과 통신하는 거의 모든 앱은 서버 API로부터 데이터를 받거나 서버로 데이터를 보내야 하며, 이때 사용되는 데이터는 대부분 JSON 형식이기 때문입니다.
이러한 데이터 처리 과정은 크게 두 가지 핵심 작업으로 나뉩니다.
- 직렬화(Serialization / 인코딩): Dart 객체(예: 사용자 정의 클래스의 인스턴스)를 서버로 전송하거나 파일에 저장하기 위해 JSON 형식의 문자열로 변환하는 과정입니다.
- 역직렬화(Deserialization / 디코딩): API 등으로부터 수신한 JSON 형식의 문자열을 Dart 앱 내에서 사용할 수 있는 객체(예: `Map` 또는 사용자 정의 클래스의 인스턴스)로 변환하는 과정입니다.
이 두 과정은 완벽한 쌍을 이룹니다. Dart 객체를 JSON으로 변환한 뒤, 다시 그 JSON을 원래의 Dart 객체로 완벽하게 복원할 수 있어야 데이터의 무결성을 보장하고 안정적인 애플리케이션을 만들 수 있습니다.
다음 섹션부터 Dart에 내장된 라이브러리를 사용한 기초적인 방법부터 실제 앱 개발에 적용할 수 있는 실용적인 예제까지, Dart에서 JSON을 다루는 방법을 심도 있게 살펴보겠습니다.
`dart:convert`를 이용한 핵심 JSON 처리
Dart에서 JSON을 다루는 가장 기본적인 방법은 표준 라이브러리인 `dart:convert`를 사용하는 것입니다. 이 라이브러리는 별도의 외부 패키지 설치 없이 바로 사용할 수 있으며, JSON을 포함한 다양한 데이터 형식을 변환하는 기능을 제공합니다.
이 라이브러리에서 가장 중요한 함수는 `jsonEncode()`와 `jsonDecode()`입니다.
직렬화(Encoding)와 `jsonEncode`
`jsonEncode()` 함수는 Dart 객체(주로 `Map`이나 `List`)를 입력받아 JSON 형식의 문자열로 변환(인코딩)합니다.
다음은 Dart의 `Map` 객체를 JSON 문자열로 변환하는 간단한 예제입니다.
import 'dart:convert';
void main() {
var user = {
'name': '홍길동',
'age': 30,
'email': 'gildong.hong@example.com'
};
var jsonString = jsonEncode(user);
print(jsonString);
// 출력: {"name":"홍길동","age":30,"email":"gildong.hong@example.com"}
}
역직렬화(Decoding)와 `jsonDecode`
`jsonDecode()` 함수는 반대로 JSON 형식의 문자열을 입력받아 Dart 객체로 변환(디코딩)합니다. 변환 결과는 JSON의 최상위 구조에 따라 보통 `Map<String, dynamic>` 또는 `List<dynamic>` 타입이 됩니다.
앞서 만든 JSON 문자열을 다시 Dart 객체로 변환하는 예제입니다.
import 'dart:convert';
void main() {
var jsonString = '{"name":"홍길동","age":30,"city":"서울"}';
// jsonDecode의 반환 타입은 dynamic이므로, 보통 Map<String, dynamic>으로 캐스팅하여 사용합니다.
var userMap = jsonDecode(jsonString) as Map<String, dynamic>;
print('이름: ${userMap['name']}'); // 출력: 이름: 홍길동
print('나이: ${userMap['age']}'); // 출력: 나이: 30
}
참고: `jsonDecode` 함수는 유효하지 않은 JSON 문자열을 받으면 `FormatException` 오류를 발생시킵니다. 외부 API와 같이 신뢰할 수 없는 데이터 소스를 다룰 때는 `try-catch` 블록으로 코드를 감싸는 것이 안전합니다.
실전 예제: 모델 클래스를 사용한 타입-세이프(Type-Safe) JSON 파싱
`Map<String, dynamic>`을 직접 사용하는 방식은 간단하지만, 키(key) 문자열을 잘못 입력해도 컴파일 시점에 오류를 발견할 수 없어 런타임 에러의 원인이 되기 쉽습니다. 또한 코드만 봐서는 어떤 데이터가 들어있는지 파악하기 어렵습니다. 따라서 더 안정적이고 유지보수가 쉬운 애플리케이션을 만들기 위해서는, JSON 데이터를 표현하는 모델 클래스를 작성하는 것이 가장 좋은 방법입니다.
웹 API에서 사용자 데이터를 가져와 타입이 보장되는 안전한 Dart 객체로 변환하는 실전 예제를 살펴보겠습니다.
먼저, HTTP 통신을 위해 표준처럼 사용되는 `http` 패키지를 `pubspec.yaml` 파일에 추가합니다.
dependencies:
http: ^1.2.0
다음으로, JSON 구조에 맞는 `User` 모델 클래스를 정의합니다. `Map`으로부터 `User` 인스턴스를 생성하기 위해 `fromJson`이라는 이름의 팩토리 생성자(factory constructor)를 만드는 것이 일반적인 패턴입니다.
class User {
final int id;
final String name;
final String username;
final String email;
User({
required this.id,
required this.name,
required this.username,
required this.email,
});
// Map 객체로부터 User 인스턴스를 생성하는 팩토리 생성자
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
username: json['username'],
email: json['email'],
);
}
}
이제 이 모델 클래스를 사용하여 API에서 데이터를 가져오고 디코딩하는 비동기 함수를 작성합니다. 테스트용 공개 API인 JSONPlaceholder를 사용하겠습니다.
import 'dart:convert';
import 'package:http/http.dart' as http;
// 위에 정의한 User 클래스가 있다고 가정합니다.
// 사용자 목록을 가져와 User 객체의 리스트로 변환하는 비동기 함수
Future<List<User>> fetchUsers() async {
final response =
await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
if (response.statusCode == 200) {
// 서버가 200 OK 응답을 반환하면, JSON을 파싱합니다.
List<dynamic> usersJson = jsonDecode(response.body);
List<User> users = usersJson.map((json) => User.fromJson(json)).toList();
return users;
} else {
// 서버가 200 OK 응답이 아니면, 예외를 발생시킵니다.
throw Exception('Failed to load users');
}
}
void main() async {
try {
List<User> users = await fetchUsers();
print('성공적으로 ${users.length}명의 사용자 정보를 가져왔습니다.');
if (users.isNotEmpty) {
print('첫 번째 사용자 이름: ${users.first.name}');
}
} catch (e) {
print('에러가 발생했습니다: $e');
}
}
이 예제는 네트워크 서비스로부터 받은 JSON을 다루는 안정적이고 타입-세이프한 패턴을 보여줍니다. `User` 클래스는 JSON으로부터 자신을 만드는 방법을 알고 있고, `fetchUsers` 함수는 네트워킹과 데이터 변환 흐름을 관리하는 등 역할이 명확하게 분리되어 코드의 가독성과 유지보수성이 크게 향상됩니다.
마무리 및 추가 자료
이 가이드에서는 `dart:convert`를 사용한 기본적인 JSON 처리 방법부터 모델 클래스를 활용한 실용적이고 타입-세이프한 접근 방식까지, Dart에서 JSON을 다루는 방법을 깊이 있게 알아보았습니다. 웹 API와 연동되는 Dart 및 Flutter 애플리케이션을 개발하는 데 있어 이 기술들은 필수적입니다.
핵심 요약:
- 기본적인 JSON 변환에는 `dart:convert` 라이브러리의 `jsonEncode()`와 `jsonDecode()`를 사용합니다.
- 안정적이고 확장 가능한 코드를 위해, `Map`을 직접 다루기보다는 모델 클래스를 정의하여 타입 안정성을 확보하는 것이 좋습니다.
- 모델 클래스에 `fromJson` 팩토리 생성자를 구현하여 JSON 역직렬화 로직을 캡슐화하세요.
- 복잡한 JSON 구조나 수많은 모델 클래스를 다룰 때는, `fromJson`/`toJson` 메서드를 자동으로 생성해주는 `json_serializable`과 같은 코드 생성 패키지를 사용하면 개발 생산성을 크게 높일 수 있습니다.
더 깊이 있는 학습을 위해 다음의 공식 자료들을 참고하는 것을 적극 권장합니다.
0 개의 댓글:
Post a Comment