[Flutter, Dart] MVVM 모델을 기반으로 JSON 데이터에서 없는 키 값을 처리하는 방법

Dart에서 JSON 데이터를 처리할 때 없는 키 값에 대한 처리는 여러 가지 방법으로 할 수 있습니다. 일반적으로는 해당 키가 없을 때 null을 반환하도록 하거나, 기본값을 제공하거나, 예외 처리를 통해 문제를 해결합니다.

다음은 MVVM 모델을 기반으로 JSON 데이터에서 없는 키 값을 처리하는 방법을 설명하고 코드를 제공해 드릴게요.

1. 솔루션 개요

JSON 데이터를 다룰 때 가장 일반적인 시나리오는 백엔드 API에서 데이터를 받아와 Dart 객체로 변환(역직렬화)하는 것입니다. 이때 API 응답이 항상 모든 키를 포함하지 않을 수 있으므로, 없는 키에 대한 안정적인 처리가 중요합니다.

여기서는 다음 단계를 통해 JSON 데이터의 없는 키 값을 처리하는 방법을 보여드릴게요.

  • Model 계층: JSON 데이터를 Dart 객체로 변환하는 로직을 담당합니다. 여기서는 factory 생성자를 사용하여 JSON으로부터 객체를 생성할 때 없는 키를 안전하게 처리합니다.

  • ViewModel 계층: Model에서 변환된 데이터를 UI에 표시하기 위해 준비합니다. 여기서는 Model에서 처리된 데이터를 단순히 전달합니다.

  • View 계층 (간단한 예시): ViewModel에서 제공된 데이터를 사용하여 UI를 구성합니다.

가정 및 제한사항:

  • JSON 데이터는 Dart의 Map<String, dynamic> 형태로 제공된다고 가정합니다.

  • 예시에서는 간단한 사용자 데이터를 다룹니다.

  • 네트워크 요청 부분은 생략하고, 이미 Map<String, dynamic> 형태로 변환된 JSON 데이터를 사용합니다.

2. Dart 코드 및 구현 지침

아래 코드는 MVVM 패턴을 따르며, JSON 데이터에서 없는 키 값을 처리하는 다양한 방법을 보여줍니다.

2.1 Model 계층: user_model.dart

User 모델은 JSON 데이터를 Dart 객체로 변환하는 역할을 합니다. 여기서는 Map에서 값을 가져올 때 없는 키를 처리하는 세 가지 방법을 보여줍니다:

  1. 널 안정성 ? 연산자 및 ?? (null-aware operator): 가장 일반적이고 권장되는 방법입니다. map['key']null이면 ?? 뒤의 기본값을 사용합니다.

  2. containsKey() 메서드: Map이 특정 키를 포함하는지 확인한 후 값을 가져옵니다.

  3. 예외 처리 try-catch: Map에서 존재하지 않는 키에 접근하려 할 때 발생하는 StateError를 잡아서 처리합니다. (일반적으로 JSON 파싱에서는 잘 사용되지 않지만, 다른 에러 처리 시나리오에서 유용할 수 있습니다.)

Dart
// lib/models/user_model.dart
class User {
  final String? name;
  final int? age;
  final String? email;
  final String country; // 필수 필드, 기본값 제공

  User({this.name, this.age, this.email, required this.country});

  // JSON Map으로부터 User 객체를 생성하는 팩토리 생성자
  factory User.fromJson(Map<String, dynamic> json) {
    // 1. 널 안정성 ? 연산자 및 ?? (null-aware operator) 사용: 권장
    // 'name' 키가 없거나 값이 null이면 null을 할당합니다.
    final String? name = json['name'] as String?;
    // 'age' 키가 없거나 값이 null이면 null을 할당합니다.
    final int? age = json['age'] as int?;

    // 2. containsKey() 메서드 사용:
    // 'email' 키가 존재하면 해당 값을 가져오고, 없으면 null을 할당합니다.
    final String? email = json.containsKey('email') ? json['email'] as String? : null;

    // 3. 필수 필드에 대한 처리 및 기본값 제공:
    // 'country' 키가 없거나 값이 null이면 'Unknown'을 기본값으로 사용합니다.
    final String country = json['country'] as String? ?? 'Unknown';

    // 예외 처리 (Optional: JSON 파싱에서는 잘 사용되지 않음)
    // 이 방법은 'some_required_field'가 반드시 존재해야 하고,
    // 없으면 오류를 발생시키고 싶을 때 고려할 수 있습니다.
    /*
    String someRequiredField;
    try {
      someRequiredField = json['some_required_field'] as String;
    } catch (e) {
      print('Error parsing some_required_field: $e');
      someRequiredField = 'Default Value'; // 또는 예외를 다시 던지거나 다른 처리
    }
    */

    return User(
      name: name,
      age: age,
      email: email,
      country: country,
    );
  }

  @override
  String toString() {
    return 'User(name: $name, age: $age, email: $email, country: $country)';
  }
}

설명:

  • nameage 필드는 String?int?로 선언하여 null 값을 가질 수 있도록 합니다. json['name'] as String?와 같이 as 키워드 뒤에 ?를 붙여 명시적으로 null 값을 허용하는 형변환을 수행합니다.

  • email 필드는 containsKey()를 사용하여 키 존재 여부를 확인합니다. 이는 특정 키가 존재할 때만 값을 가져오고 싶을 때 유용합니다.

  • country 필드는 필수 필드이지만, JSON에 키가 없을 경우 ?? 'Unknown'을 사용하여 기본값을 제공합니다. 이는 데이터의 안정성을 높이는 데 도움이 됩니다.

2.2 ViewModel 계층: user_viewmodel.dart

ViewModel은 Model의 데이터를 View에 적합한 형태로 노출합니다. 여기서는 단순히 User 객체를 View에 전달합니다. 실제 애플리케이션에서는 데이터를 가공하거나 비즈니스 로직을 포함할 수 있습니다.

Dart
// lib/viewmodels/user_viewmodel.dart
import '../models/user_model.dart';

class UserViewModel {
  final User _user;

  UserViewModel(this._user);

  String? get userName => _user.name;
  int? get userAge => _user.age;
  String? get userEmail => _user.email;
  String get userCountry => _user.country;

  // 추가적인 비즈니스 로직이나 데이터 가공을 ViewModel에서 수행할 수 있습니다.
  String get displayName {
    return _user.name != null && _user.name!.isNotEmpty
        ? '이름: ${_user.name}'
        : '이름: 미상';
  }

  String get displayAge {
    return _user.age != null ? '나이: ${_user.age}세' : '나이: 알 수 없음';
  }

  String get displayEmail {
    return _user.email != null && _user.email!.isNotEmpty
        ? '이메일: ${_user.email}'
        : '이메일: 제공되지 않음';
  }

  String get displayCountry {
    return '국가: ${_user.country}';
  }
}

설명:

  • ViewModel은 User 모델 객체를 캡슐화하고, View에서 직접 Model에 접근하는 대신 ViewModel을 통해 데이터에 접근하도록 합니다.

  • get 메서드를 통해 Model의 속성에 접근하며, 필요에 따라 데이터를 포맷하거나 변환할 수 있습니다. 예를 들어, displayName은 이름이 없을 경우 "미상"으로 표시합니다.

2.3 View 계층 (간단한 예시): main.dart

여기서는 실제 Flutter UI 대신 main 함수에서 ViewModel을 사용하여 데이터를 출력하는 방식으로 View 계층을 간단하게 시뮬레이션합니다.

Dart
// lib/main.dart
import 'dart:convert'; // JSON 문자열을 Map으로 변환하기 위해 필요
import 'package:flutter/material.dart'; // 실제 Flutter 프로젝트에서 사용될 경우

import 'models/user_model.dart';
import 'viewmodels/user_viewmodel.dart';

void main() {
  // 1. 모든 키가 존재하는 JSON 데이터
  String jsonString1 = '''
    {
      "name": "홍길동",
      "age": 30,
      "email": "hong.gildong@example.com",
      "country": "South Korea"
    }
  '''
  ;

  // 2. 일부 키가 없는 JSON 데이터
  String jsonString2 = '''
    {
      "name": "김철수",
      "age": 25
      // "email"과 "country" 키가 없음
    }
  '''
  ;

  // 3. 'name'도 없는 JSON 데이터
  String jsonString3 = '''
    {
      "age": 40,
      "email": "no.name@example.com",
      "country": "USA"
    }
  '''
  ;

  // JSON 문자열을 Map<String, dynamic>으로 변환
  final Map<String, dynamic> jsonData1 = json.decode(jsonString1);
  final Map<String, dynamic> jsonData2 = json.decode(jsonString2);
  final Map<String, dynamic> jsonData3 = json.decode(jsonString3);


  print('--- Case 1: All keys present ---');
  final User user1 = User.fromJson(jsonData1);
  final UserViewModel viewModel1 = UserViewModel(user1);
  print(viewModel1.displayName);
  print(viewModel1.displayAge);
  print(viewModel1.displayEmail);
  print(viewModel1.displayCountry);

  print('\n--- Case 2: Missing "email" and "country" ---');
  final User user2 = User.fromJson(jsonData2);
  final UserViewModel viewModel2 = UserViewModel(user2);
  print(viewModel2.displayName);
  print(viewModel2.displayAge);
  print(viewModel2.displayEmail); // "이메일: 제공되지 않음" 출력 예상
  print(viewModel2.displayCountry); // "국가: Unknown" 출력 예상

  print('\n--- Case 3: Missing "name" ---');
  final User user3 = User.fromJson(jsonData3);
  final UserViewModel viewModel3 = UserViewModel(user3);
  print(viewModel3.displayName); // "이름: 미상" 출력 예상
  print(viewModel3.displayAge);
  print(viewModel3.displayEmail);
  print(viewModel3.displayCountry);
}

설명:

  • main 함수에서 세 가지 다른 JSON 문자열을 정의하고 있습니다. 이 문자열들은 json.decode()를 통해 Map<String, dynamic> 형태로 변환됩니다.

  • jsonDataUser.fromJson() 팩토리 생성자를 통해 User 모델 객체로 변환됩니다. 이때 User 모델의 로직에 따라 없는 키 값이 처리됩니다.

  • 생성된 User 객체는 UserViewModel의 생성자로 전달되어 UserViewModel 인스턴스를 생성합니다.

  • 마지막으로 UserViewModel의 게터들을 사용하여 데이터가 어떻게 처리되었는지 콘솔에 출력합니다.

구현 지침:

  1. 프로젝트 구조 설정:

    • 새 Flutter 또는 Dart 프로젝트를 생성합니다. (예: flutter create my_json_app)

    • lib 폴더 안에 modelsviewmodels 폴더를 생성합니다.

    • 각 코드 파일을 해당 폴더에 저장합니다:

      • lib/models/user_model.dart

      • lib/viewmodels/user_viewmodel.dart

      • lib/main.dart

  2. 의존성 추가 (선택 사항):

    • 이 예시에서는 별도의 패키지가 필요하지 않습니다. Dart의 내장 dart:convert 라이브러리로 충분합니다.

    • 만약 더 복잡한 JSON 직렬화/역직렬화를 위해 json_serializable과 같은 패키지를 사용하고 싶다면, pubspec.yaml 파일에 추가해야 합니다.

  3. 코드 실행:

    • 터미널에서 프로젝트 루트 디렉토리로 이동합니다.

    • 다음 명령을 실행하여 코드를 실행합니다:

      Bash
      dart lib/main.dart
      
    • 콘솔에서 출력되는 결과를 확인하여 없는 키 값이 어떻게 처리되는지 볼 수 있습니다.

이 예시를 통해 Dart에서 JSON 데이터의 없는 키 값을 안전하게 처리할 수 있습니다.

댓글 쓰기

0 댓글