πŸ“š Today I Learned 21회차 - 데이터 톡신 κΈ°μ΄ˆμ™€ JSON πŸ’‘

4 λΆ„ μ†Œμš”

μ•ˆλ…•ν•˜μ„Έμš”! μ˜€λŠ˜μ€ 데이터 ν†΅μ‹ μ˜ κΈ°μ΄ˆμ™€ JSON ν˜•μ‹μ— λŒ€ν•΄ ν•™μŠ΅ν–ˆμŠ΅λ‹ˆλ‹€. μ„œλ‘œ λ‹€λ₯Έ μ–Έμ–΄λ‚˜ μ‹œμŠ€ν…œ 간에 데이터λ₯Ό μ£Όκ³ λ°›κΈ° μœ„ν•œ ν‘œμ€€ ν˜•μ‹μΈ JSON의 κ°œλ…κ³Ό Dartμ—μ„œμ˜ ν™œμš© 방법을 λ°°μ› μ–΄μš”!

🎯 ν•™μŠ΅ λͺ©ν‘œ

  • JSON ν˜•μ‹μ˜ κ°œλ…κ³Ό ꡬ쑰 μ΄ν•΄ν•˜κΈ°
  • 직렬화와 μ—­μ§λ ¬ν™”μ˜ κ°œλ… 읡히기
  • Dartμ—μ„œ jsonEncode와 jsonDecode ν™œμš©ν•˜κΈ°
  • 객체와 JSON λ¬Έμžμ—΄ κ°„ λ³€ν™˜ κ΅¬ν˜„ν•˜κΈ°
  • λ³΅μž‘ν•œ 쀑첩 ꡬ쑰의 JSON 닀루기

πŸ“š μ£Όμš” λ‚΄μš©

⭐ JSONμ΄λž€?

JSON (JavaScript Object Notation) 은 μ„œλ‘œ λ‹€λ₯Έ μ–Έμ–΄λ‚˜ μ‹œμŠ€ν…œ κ°„ 데이터λ₯Ό μ£Όκ³ λ°›κΈ° μœ„ν•œ κ²½λŸ‰ 데이터 κ΅ν™˜ ν˜•μ‹μž…λ‹ˆλ‹€.

πŸ’‘ μ™œ JSON을 μ‚¬μš©ν•˜λ‚˜μš”?: Dart 객체λ₯Ό λ‹€λ₯Έ μ–Έμ–΄ μ„œλ²„(예: Java, Python)둜 μ „λ‹¬ν•˜λ €λ©΄ ν‘œμ€€ν™”λœ ν˜•μ‹μ΄ ν•„μš”ν•©λ‹ˆλ‹€. JSON은 언어에 독립적이고 μ‚¬λžŒμ΄ 읽기 μ‰¬μš΄ ν…μŠ€νŠΈ ν˜•μ‹μ΄κΈ° λ•Œλ¬Έμ— 널리 μ‚¬μš©λ©λ‹ˆλ‹€!

πŸ“‹ JSON κΈ°λ³Έ κ·œμΉ™

ꡬ성 μš”μ†Œ κ·œμΉ™ μ˜ˆμ‹œ
Key λ°˜λ“œμ‹œ λ¬Έμžμ—΄ (ν°λ”°μ˜΄ν‘œ) "name", "age"
Value 6κ°€μ§€ νƒ€μž… κ°€λŠ₯ μ•„λž˜ μ°Έμ‘°
ꡬ쑰 μ€‘κ΄„ν˜Έ {} 둜 감싸기 {"key": "value"}

🎨 JSON Value νƒ€μž…

  • String (λ¬Έμžμ—΄): "μ˜€μƒκ΅¬"
  • Number (숫자): 7, 3.14
  • Boolean (뢈린): true, false
  • Array (λ°°μ—΄): ["μ‚Όκ²Ήμ‚΄", "μ—°μ–΄", "고ꡬ마"]
  • Object (객체): {"mobile": "010-0000-0000"}
  • null: null

πŸ’» JSON μ˜ˆμ‹œ

{
  "name": "μ˜€μƒκ΅¬",
  "age": 7,
  "isMale": true,
  "favorite_foods": ["μ‚Όκ²Ήμ‚΄", "μ—°μ–΄", "고ꡬ마"],
  "dislike_foods": [],
  "contact": {
    "mobile": "010-0000-0000",
    "email": null
  }
}

⚑ Dartμ—μ„œμ˜ JSON ν™œμš©

πŸ”„ 직렬화와 역직렬화

데이터λ₯Ό 주고받을 λ•ŒλŠ” JSON ν˜•μ‹μ˜ String으둜 λ³€ν™˜ν•˜μ—¬ μ „μ†‘ν•©λ‹ˆλ‹€.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         직렬화 (Serialization)        β”‚
β”‚   Dart 객체 β†’ Map β†’ JSON String      β”‚
β”‚         jsonEncode() μ‚¬μš©             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                ⬇️  λ„€νŠΈμ›Œν¬ 전솑
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚       역직렬화 (Deserialization)      β”‚
β”‚   JSON String β†’ Map β†’ Dart 객체      β”‚
β”‚         jsonDecode() μ‚¬μš©             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ’‘ μ™œ Map을 κ±°μΉ˜λ‚˜μš”?: 객체λ₯Ό λ°”λ‘œ λ¬Έμžμ—΄λ‘œ λ³€ν™˜ν•˜λ©΄ λ³΅μž‘ν•˜κ³  였λ₯˜κ°€ λ°œμƒν•˜κΈ° μ‰½μŠ΅λ‹ˆλ‹€. Map은 JSON의 ꡬ쑰와 μΌμΉ˜ν•˜κΈ° λ•Œλ¬Έμ— 쀑간 λ‹¨κ³„λ‘œ μ‚¬μš©ν•˜λ©΄ μ•ˆμ „ν•˜κ²Œ λ³€ν™˜ν•  수 μžˆμ–΄μš”!

πŸ“Œ ν•„μˆ˜ λ©”μ„œλ“œ

λ©”μ„œλ“œ μš©λ„ λ³€ν™˜
toJson() 객체 β†’ Map 직렬화 μ‹œ μ‚¬μš©
fromJson() Map β†’ 객체 역직렬화 μ‹œ μ‚¬μš©
jsonEncode() Map β†’ String dart:convert 제곡
jsonDecode() String β†’ Map dart:convert 제곡

πŸ’» μ‹€μŠ΅ 및 예제

πŸ”° κΈ°λ³Έ 예제: κ°„λ‹¨ν•œ Human 클래슀

import 'dart:convert';

// 🎯 직렬화: 객체λ₯Ό JSON ν˜•νƒœμ˜ λ¬Έμžμ—΄λ‘œ λ³€ν™˜
//    - κ³Όμ •: 객체 β†’ Map (toJson) β†’ String (jsonEncode)
// 🎯 역직렬화: JSON ν˜•νƒœμ˜ λ¬Έμžμ—΄μ„ 객체둜 λ³€ν™˜
//    - κ³Όμ •: String β†’ Map (jsonDecode) β†’ 객체 (fromJson)

void main() {
  // βœ… Step 1: JSON λ¬Έμžμ—΄ μ€€λΉ„
  String easyJson = """
{
  "name": "μ˜€μƒκ΅¬",
  "age": 7,
  "isMale": true
}
""";

  // βœ… Step 2: 역직렬화 (String β†’ Map)
  var decodedData = jsonDecode(easyJson);

  // βœ… Step 3: Map을 객체둜 λ³€ν™˜
  Human human = Human.fromJson(decodedData);

  // βœ… Step 4: 객체λ₯Ό λ‹€μ‹œ Map으둜 λ³€ν™˜ν•˜μ—¬ 좜λ ₯
  print(human.toJson());
  // 좜λ ₯: {name: μ˜€μƒκ΅¬, age: 7, isMale: true}
}

// πŸ“¦ Human 클래슀 μ •μ˜
class Human {
  String name;
  int age;
  bool isMale;

  // 일반 μƒμ„±μž
  Human({
    required this.name,
    required this.age,
    required this.isMale,
  });

  // πŸ”§ fromJson: Map β†’ 객체 λ³€ν™˜μš© named μƒμ„±μž
  Human.fromJson(Map<String, dynamic> map)
      : this(
          name: map['name'],
          age: map['age'],
          isMale: map['isMale'],
        );

  // πŸ”§ toJson: 객체 β†’ Map λ³€ν™˜μš© λ©”μ„œλ“œ
  Map<String, dynamic> toJson() {
    return {
      "name": name,
      "age": age,
      "isMale": isMale,
    };
  }
}

πŸš€ κ³ κΈ‰ 예제: 쀑첩 ꡬ쑰 닀루기

λ³΅μž‘ν•œ JSON ꡬ쑰(λ°°μ—΄, 쀑첩 객체)λ₯Ό λ‹€λ£¨λŠ” λ°©λ²•μž…λ‹ˆλ‹€.

void main() {
  // πŸ”₯ λ³΅μž‘ν•œ JSON: λ°°μ—΄κ³Ό 쀑첩 객체 포함
  String hardJson = """
{
  "name": "μ˜€μƒκ΅¬",
  "age": 7,
  "isMale": true,
  "favorite_foods": ["μ‚Όκ²Ήμ‚΄", "μ—°μ–΄", "고ꡬ마"],
  "contact": {
    "mobile": "010-0000-0000",
    "email": null
  }
}
""";

  // βœ… 역직렬화: String β†’ Map β†’ 객체
  var decodedData2 = jsonDecode(hardJson);
  HumanAdvanced humanAdvanced = HumanAdvanced.fromJson(decodedData2);

  // βœ… 좜λ ₯ 확인
  print(humanAdvanced.toJson());
  print(humanAdvanced.contact.toJson());
}

// πŸ“¦ HumanAdvanced 클래슀: λ³΅μž‘ν•œ ꡬ쑰
class HumanAdvanced {
  String name;
  int age;
  bool isMale;
  List<String> favoriteFoods;  // πŸ“‹ λ°°μ—΄ νƒ€μž…
  Contact contact;              // πŸ“¦ μ€‘μ²©λœ 객체 νƒ€μž…

  HumanAdvanced({
    required this.name,
    required this.age,
    required this.isMale,
    required this.favoriteFoods,
    required this.contact,
  });

  // πŸ”§ 객체 β†’ Map λ³€ν™˜
  Map<String, dynamic> toJson() {
    return {
      "name": name,
      "age": age,
      "isMale": isMale,
      "favorite_foods": favoriteFoods,
      "contact": contact.toJson(),  // ⚠️ 쀑첩 κ°μ²΄λŠ” toJson() 호좜!
    };
  }

  // πŸ”§ Map β†’ 객체 λ³€ν™˜
  HumanAdvanced.fromJson(Map<String, dynamic> map)
      : this(
          name: map['name'],
          age: map['age'],
          isMale: map['isMale'],
          // ⚑ λ°°μ—΄ λ³€ν™˜: List<dynamic>을 List<String>으둜
          favoriteFoods: List<String>.from(map['favorite_foods']),
          // ⚑ 쀑첩 객체 λ³€ν™˜: Contact.fromJson() μ‚¬μš©
          contact: Contact.fromJson(map['contact']),
        );
}

// πŸ“¦ Contact 클래슀: μ—°λ½μ²˜ 정보
class Contact {
  String mobile;
  String? email;  // nullable νƒ€μž…

  Contact({
    required this.mobile,
    this.email,
  });

  // πŸ”§ 객체 β†’ Map λ³€ν™˜
  Map<String, dynamic> toJson() {
    return {
      "mobile": mobile,
      "email": email,  // βœ… μˆ˜μ •λ¨: "age" β†’ "email"
    };
  }

  // πŸ”§ Map β†’ 객체 λ³€ν™˜
  Contact.fromJson(Map<String, dynamic> map)
      : this(
          mobile: map["mobile"],
          email: map["email"],  // βœ… μˆ˜μ •λ¨: "age" β†’ "email"
        );
}

🚨 주의: 원본 μ½”λ“œμ—μ„œ Contact 클래슀의 toJson()κ³Ό fromJson()μ—μ„œ "age"둜 잘λͺ» μž‘μ„±λœ 뢀뢄을 "email"둜 μˆ˜μ •ν–ˆμŠ΅λ‹ˆλ‹€!

πŸ” 핡심 포인트

λ°°μ—΄ λ³€ν™˜ μ‹œ μ£Όμ˜μ‚¬ν•­

// ❌ 잘λͺ»λœ 방법: νƒ€μž… 뢈일치 였λ₯˜ λ°œμƒ κ°€λŠ₯
favoriteFoods: map['favorite_foods']

// βœ… μ˜¬λ°”λ₯Έ 방법: λͺ…μ‹œμ  νƒ€μž… λ³€ν™˜
favoriteFoods: List<String>.from(map['favorite_foods'])

쀑첩 객체 λ³€ν™˜ μ‹œ μ£Όμ˜μ‚¬ν•­

// ❌ 잘λͺ»λœ 방법: Map을 객체둜 λ³€ν™˜ν•˜μ§€ μ•ŠμŒ
contact: map['contact']

// βœ… μ˜¬λ°”λ₯Έ 방법: fromJson() μƒμ„±μž μ‚¬μš©
contact: Contact.fromJson(map['contact'])

πŸ” 심화 ν•™μŠ΅

πŸ€” μΆ”κ°€ 탐ꡬ 주제

  • JSON νŒ¨ν‚€μ§€: json_serializable을 μ‚¬μš©ν•œ μžλ™ μ½”λ“œ 생성
  • Null Safety: nullable ν•„λ“œ 처리 방법
  • μ—λŸ¬ 핸듀링: JSON νŒŒμ‹± μ‹œ μ˜ˆμ™Έ 처리
  • λ„€νŠΈμ›Œν¬ 톡신: HTTP μš”μ²­κ³Ό JSON 연동
  • λ³΅μž‘ν•œ ꡬ쑰: 닀쀑 쀑첩 객체와 λ°°μ—΄ 닀루기

πŸ“ 마무리

βœ… 였늘 배운 것

  • JSON κ°œλ…: μ–Έμ–΄ 독립적인 데이터 κ΅ν™˜ ν˜•μ‹μœΌλ‘œ, key-value ꡬ쑰λ₯Ό 가짐
  • 직렬화: Dart 객체λ₯Ό toJson() β†’ jsonEncode()둜 JSON λ¬Έμžμ—΄λ‘œ λ³€ν™˜
  • 역직렬화: JSON λ¬Έμžμ—΄μ„ jsonDecode() β†’ fromJson()으둜 Dart 객체둜 λ³€ν™˜
  • λ°°μ—΄ λ³€ν™˜: List<T>.from() λ©”μ„œλ“œλ‘œ λͺ…μ‹œμ  νƒ€μž… λ³€ν™˜
  • 쀑첩 객체: 각 ν΄λž˜μŠ€μ— toJson()/fromJson() κ΅¬ν˜„ ν•„μš”
  • μ‹€μŠ΅ κ²°κ³Ό: κΈ°λ³Έ ꡬ쑰와 λ³΅μž‘ν•œ 쀑첩 ꡬ쑰 λͺ¨λ‘ μ„±κ³΅μ μœΌλ‘œ λ³€ν™˜

πŸš€ λ‹€μŒ κ³„νš

  1. JSON μžλ™ 생성: json_serializable νŒ¨ν‚€μ§€λ₯Ό ν™œμš©ν•œ λ³΄μΌλŸ¬ν”Œλ ˆμ΄νŠΈ μ½”λ“œ μžλ™ν™”
  2. HTTP 톡신: http νŒ¨ν‚€μ§€λ₯Ό μ‚¬μš©ν•œ μ‹€μ œ API 톡신 κ΅¬ν˜„
  3. μ—λŸ¬ 처리: try-catchλ₯Ό ν™œμš©ν•œ μ•ˆμ „ν•œ JSON νŒŒμ‹±
  4. μƒνƒœ 관리: Provider와 JSON 데이터 연동

λŒ“κΈ€λ‚¨κΈ°κΈ°