소소한 일상과 잡다한 정보

IT/Dart

Dart_7일차 : 파일 입출력 & JSON 데이터 처리

pandada 2025. 3. 5. 16:42
반응형

 이번에는 파일 입출력 (File I/O)과 JSON 데이터 처리를 확인해보자. 이제 파일을 읽고 쓰는 법, 그리고 JSON 데이터를 다루는 법을 익혀서 실제 애플리케이션 개발에 활용할 수 있도록 하자. JSON 처리는 웹 API 활용에 중요한 부분이니 내용을 정확히 알고 가는 것이 좋을 것 같다.

 

 


 

1. 파일 입출력 (File I/O)

 Dart에서는 dart:io 라이브러리를 사용해서 파일을 읽고 쓸 수 있다.

 ✔️ 파일에 문자열 쓰기 (writeAsString)

import 'dart:io';

void main() async {
  File file = File('test.txt'); // 파일 객체 생성
  await file.writeAsString('안녕하세요, Dart 파일 입출력!'); // 파일에 문자열 쓰기
  print("✅ 파일 저장 완료!");
}
  • 파일을 생성하고(File('test.txt')), writeAsString()을 사용해 텍스트를 저장
  • 파일이 test.txt라는 이름으로 현재 프로젝트 폴더에 생성됨
  • await를 사용해서 파일이 저장될 때까지 기다림
  • 특정 폴더에 파일 저장 ( 경로 지정 / Windows ) : "C:/폴더명/파일명.txt" 형식으로 경로 지정
  • 특정 폴더에 파일 저장 ( 경로 지정 / Mac & Linux ) : /Users/yourname/Documents/myfile.txt 형식 사용
  • 실행 중인 프로그램의 현재 경로에 저장 : Directory.current.path 사용 ( 경로를 따로 설정하지 않아도 자동으로 현재 폴더에 파일이 생김 )
  • 상대 경로로 저장 ( 현재 폴더 내 트정 서브 폴더 ) : 폴더가 없으면 오류 발생함으로 폴더를 만들어 두거나, 자동 생성필요
  • 경로에 폴더가 없으면 자동 생성 : file.parent.createSync(recursive: true)를 사용하면 폴더가 없을 경우 자동 생성
import 'dart:io';

void main() async {
  // 현재 실행 중인 경로 가져오기
  //String currentPath = Directory.current.path;
  // 현재 폴더에 저장
  //File file = File('$currentPath/myfile.txt'); 
  
  String path = 'data/myfile.txt'; // 현재 폴더의 "data" 폴더 내에 저장
  File file = File(path);

  file.parent.createSync(recursive: true); // 폴더가 없으면 자동 생성
  await file.writeAsString('안녕하세요, Dart 파일 입출력!');
  print("✅ 파일이 $path에 저장되었습니다!");
}

 

📌 결론 : Broadcast Stream을 사용하면 좋은 경우

  • 파일 저장 경로를 지정할 때 File('경로/파일명')을 사용
  • OS에 따라 경로 지정 방식이 다름
  • 현재 실행 중인 폴더에 저장하려면 Directory.current.path 활용 가능
  • 상대 경로(data/myfile.txt), 자동 폴더 생성(createSync(recursive: true))도 활용 가능

 

 ✔️ 파일에서 문자열 읽기 (readAsString)

import 'dart:io';

void main() async {
  File file = File('test.txt'); // 파일 객체 생성

  if (await file.exists()) { // 파일이 존재하는지 확인
    String contents = await file.readAsString(); // 파일에서 데이터 읽기
    print("📄 파일 내용: $contents");
  } else {
    print("⚠️ 파일이 존재하지 않습니다.");
  }
}
  • readAsString()을 사용하면 파일 내용을 문자열로 읽을 수 있음
  • 파일이 없을 경우 오류를 방지하기 위해 exists()로 체크

 

 ✔️ 여러 줄 데이터를 파일에 저장1 (writeAsLines)

import 'dart:io';

void main() async {
  File file = File('lines.txt');
  List<String> lines = ['Dart', 'Flutter', 'File I/O', 'JSON Parsing'];

  await file.writeAsLines(lines); // 여러 줄 저장
  print("✅ 여러 줄 저장 완료!");
}
  • 리스트 형태의 데이터를 writeAsLines()를 사용해 파일에 저장 가능
  • Dart SDK 버전과 프로젝트 환경 설정에 따른 오류가 발생할 가능성이 있음.
  • 에러 내용 : Error: The method 'writeAsLines' isn't defined for the class 'File'. - 'File' is from 'dart:io'. Try correcting the name to the name of an existing method, or defining a method named 'writeAsLines'. await file.writeAsLines(lines);

 

 ✔️ 여러 줄 데이터를 파일에 저장2 ( writeAsString()과 join('\n')을 사용 )

import 'dart:io';

void main() async {
  File file = File('lines.txt'); // 파일 객체 생성
  List<String> lines = ['Dart', 'Flutter', 'File I/O', 'JSON Parsing'];

  // '\n'을 사용하여 여러 줄을 하나의 문자열로 변환 후 저장
  await file.writeAsString(lines.join('\n')); 

  print("✅ 여러 줄 저장 완료!");
}
  • writeAsLines() 대신 writeAsString() 사용
  • lines.join('\n')을 사용하여 리스트를 하나의 문자열로 변환
  • 리스트(List<String>)를 문자열로 변환할 때 \n(줄바꿈) 추가 /  ['Dart', 'Flutter'] → "Dart\nFlutter"

📌 참고 내용

  • writeAsLines()가 지원되지 않으면 writeAsString() + join('\n')을 사용
  • 리스트 데이터를 파일에 저장할 때 줄바꿈(\n)을 활용하면 됨
  • readAsLines()를 사용하면 다시 리스트 형태로 데이터를 불러올 수 있음

 

✔️ 파일에서 여러 줄 데이터 읽기 (readAsLines)

import 'dart:io';

void main() async {
  File file = File('lines.txt');

  if (await file.exists()) {
    List<String> lines = await file.readAsLines();
    print("📄 파일 내용:");
    for (var line in lines) {
      print("- $line");
    }
  } else {
    print("⚠️ 파일이 존재하지 않습니다.");
  }
}
  • readAsLines()를 사용하면 여러 줄 데이터를 리스트로 읽을 수 있음
  • for문을 사용해 한 줄씩 출력 가능

 

2.  JSON 데이터 다루기

 Dart에서는 JSON 데이터를 쉽게 다룰 수 있도록 dart:convert 라이브러리를 제공한다.
 보통 웹 API에서 데이터를 받을 때 JSON 형식으로 제공되기 때문에, JSON을 다루는 방법을 익혀두면 필수적인 기술.

 ✔️ JSON 문자열을 Dart 객체(Map)로 변환 ( jsonDecode )

import 'dart:convert';

void main() {
  String jsonString = '{"name": "Kim", "age": 25, "city": "Seoul"}';
  
  Map<String, dynamic> user = jsonDecode(jsonString); // JSON → Map 변환
  // 값(value)의 타입이 다를 수 있으므로 dynamic을 사용.

  print("👤 이름: ${user['name']}");
  print("🎂 나이: ${user['age']}");
  print("📍 도시: ${user['city']}");
}
  • jsonDecode()를 사용해 JSON 문자열을 Dart의 Map 객체로 변환
  • user['name']처럼 키 값을 통해 데이터에 접근 가능

 

 ✔️ Dart 객체(Map)를 JSON 문자열로 변환 ( jsonEncode )

import 'dart:convert';

void main() {
  String jsonString = '{"name": "Kim", "age": 25, "city": "Seoul"}';
  
  Map<String, dynamic> user = jsonDecode(jsonString); // JSON → Map 변환
  // 값(value)의 타입이 다를 수 있으므로 dynamic을 사용.

  print("👤 이름: ${user['name']}");
  print("🎂 나이: ${user['age']}");
  print("📍 도시: ${user['city']}");
}
  • jsonEncode()를 사용하면 Map을 JSON 문자열로 변환 가능
  • 서버에 데이터를 전송할 때 사용됨

 

 ✔️ JSON 파일 저장 & 불러오기

import 'dart:io';
import 'dart:convert';

void main() async {
  File file = File('user.json');

  // JSON 데이터를 파일에 저장
  Map<String, dynamic> user = {
    "name": "Kim",
    "age": 25,
    "city": "Seoul"
  };
  await file.writeAsString(jsonEncode(user));
  print("✅ JSON 파일 저장 완료!");

  // JSON 파일에서 데이터 불러오기
  if (await file.exists()) {
    String jsonString = await file.readAsString();
    Map<String, dynamic> loadedUser = jsonDecode(jsonString);
    print("📄 불러온 JSON 데이터: $loadedUser");
  } else {
    print("⚠️ JSON 파일이 존재하지 않습니다.");
  }
}
  • JSON 데이터를 파일로 저장 (writeAsString(jsonEncode(user)))
  • JSON 파일을 읽어서 다시 Map 형태로 변환 (jsonDecode())
반응형

✔️ 테스트 예제 코드 1

 위에서 진행했던 내용을 토대로 충분한 실습을 진행한 후, 테스트 예제를 진행해보도록 하자.

 1.사용자가 입력한 문자열을 파일(user_input.txt)에 저장하고, 저장된 내용을 출력하는 프로그램 만들기

 2. 사용자가 "exit"을 입력하면 저장을 종료하고, 파일 내용을 출력

 3. 사용자가 enter만 누를 경우 리스트에 추가 되지 않음.

 4. 공백이나 문자 " " 만 입력하면 다시 입력 받기

import 'dart:io';

void main() async {
  File file = File('user_input.txt'); // 파일 객체 생성
  List<String> inputs = []; // 입력값을 저장할 리스트

  print("📝 입력을 시작하세요. (종료하려면 'exit' 입력)");

  while (true) {
    stdout.write("입력: ");
    String? input = stdin.readLineSync()?.trim(); // 입력값 앞뒤 공백 제거 후 저장

    if (input == null || input.toLowerCase() == "exit") {
      print("📄 입력 저장 중...");
      await file.writeAsString(inputs.join('\n')); // 파일에 저장
      print("✅ 입력이 저장되었습니다: user_input.txt");

      // 저장된 내용 출력
      String content = await file.readAsString();
      print("\n📜 저장된 내용:\n$content");
      break;
    }

    if (input.isEmpty) { // 공백 입력 방지
      print("⚠️ 공백 입력은 허용되지 않습니다.");
      continue; // 입력 받는 루프로 다시 돌아감
    }

    inputs.add(input); // 정상적인 입력값만 리스트에 추가
  }
}

 

 

✔️ 테스트 예제 코드 2

 1. JSON 형식의 유저 데이터를 users.json에 저장하고, 다시 불러와 출력하는 프로그램 ( 이름과 나이 도시 )

 2. 사용자가 입력한 유저 데이터를 JSON 형식으로 저장 (users.json)

 3. 저장된 JSON 파일을 읽어와 출력

 4. 파일이 존재하지 않을 경우 오류 없이 처리

 5. 이름에 공백 입력 방지 및 도시에 공백이 들어갈 경우 기본값("알 수 없음") 처리

 4. 파일이 존재하지 않을 경우 오류 없이 처리

import 'dart:io';
import 'dart:convert';

void main() async {
  File file = File('users.json'); // JSON 파일 객체 생성
  List<Map<String, dynamic>> users = []; // 유저 데이터를 저장할 리스트

  print("📝 유저 정보를 입력하세요. (종료하려면 'exit' 입력)");

  while (true) {
    String? name;
    while (true) { // 이름이 빈 값이면 다시 입력받기
      stdout.write("이름: ");
      name = stdin.readLineSync()?.trim();
      if (name == null || name.isEmpty) {
        print("⚠️ 이름을 입력해야 합니다.");
        continue; // 이름 입력으로 다시 돌아감
      }
      if (name.toLowerCase() == "exit") break; // exit 입력 시 종료
      break; // 정상 입력이면 루프 종료
    }

    if (name.toLowerCase() == "exit") break; // 최종 종료 처리

    int? age;
    while (true) { // 나이는 숫자로 입력해야 함
      stdout.write("나이: ");
      String? ageInput = stdin.readLineSync()?.trim();
      //ageInput이 null이면 ""(빈 문자열)로 대체
      // ?? ""는 null 병합 연산자
      age = int.tryParse(ageInput ?? "");
      
      if (age == null) {
        print("⚠️ 나이는 숫자로 입력해야 합니다.");
        continue;
      }
      break;
    }

    stdout.write("도시: ");
    String? city = stdin.readLineSync()?.trim();
    if (city == null || city.isEmpty) city = "알 수 없음"; // 기본값 설정

    // 유저 정보를 Map으로 저장
    Map<String, dynamic> user = {"name": name, "age": age, "city": city};
    users.add(user); // 리스트에 추가
  }

  if (users.isNotEmpty) {
    await file.writeAsString(jsonEncode(users)); // JSON 파일로 저장
    print("✅ 유저 정보가 users.json 파일에 저장되었습니다!");
  }

  // 저장된 JSON 파일 읽기
  if (await file.exists()) {
    String jsonString = await file.readAsString();
    List<dynamic> loadedUsers = jsonDecode(jsonString); // JSON → List 변환

    print("\n📄 저장된 유저 목록:");
    for (var user in loadedUsers) {
      print("👤 이름: ${user['name']}, 🎂 나이: ${user['age']}, 📍 도시: ${user['city']}");
    }
  } else {
    print("⚠️ 저장된 JSON 파일이 없습니다.");
  }
}
  • ?? "" 에 대해서 언급한 적이 없기에 한번 짚고 넘어가자.
  • int.tryParse(ageInput ?? "");
  • 사용자가 입력한 값(String?)을 받아 공백(trim())을 제거한 후 ageInput 변수에 저장
  • stdin.readLineSync()는 null을 반환할 수도 있음 (사용자가 강제 종료한 경우 등)
  • 만약 ageInput이 null이면 ""(빈 문자열)로 대체 / 즉, null 값이 int.tryParse()로 넘어가지 않도록 방지
  • 숫자가 아닌 값이 입력되면 null을 반환해서 오류를 방지
입력값 (ageInput) ageInput ?? "" int.tryParse() 결과
"25" "25" 25 (정상 변환)
"abc" "abc" null (변환 실패)
"" (빈 값) "" null (변환 실패)
null "" null (변환 실패)

 

✔️ 테스트 예제 코드 3

 1. JSON 데이터에서 특정 키(age > 20)를 기준으로 필터링하여 출력하는 프로그램
 2. 저장된 users.json 파일을 읽어오기
 3. JSON 데이터를 List<Map<String, dynamic>> 형태로 변환
 4. 나이(age > 20) 기준으로 필터링하여 출력
 5. 파일이 존재하지 않으면 오류 없이 처리

 6. JSON 파일(users.json)을 읽어와 문자열로 변환 (readAsString())
 7. 문자열을 JSON 데이터(List<dynamic>)로 변환 (jsonDecode())
 8. where()를 사용해 age > 20 조건에 맞는 사용자만 필터링
 9. 출력 시 cast<Map<String, dynamic>>()을 사용하여 타입 변환
 10. 필터링 결과가 없으면 "⚠️ 나이가 20 초과인 사용자가 없습니다." 메시지 출력

  • users.json 파일
[
  {"name": "Kim", "age": 18, "city": "Seoul"},
  {"name": "Lee", "age": 25, "city": "Busan"},
  {"name": "Park", "age": 30, "city": "Incheon"},
  {"name": "Choi", "age": 19, "city": "Daegu"}
]
  • 코드
import 'dart:io';
import 'dart:convert';

void main() async {
  File file = File('users.json'); // JSON 파일 객체 생성

  // JSON 파일이 존재하는지 확인
  if (await file.exists()) {
    String jsonString = await file.readAsString(); // JSON 파일 읽기
    List<dynamic> users = jsonDecode(jsonString); // JSON → List 변환

    // 나이(age) 기준으로 필터링 (age > 20)
    List<Map<String, dynamic>> filteredUsers = users
        .where((user) => user['age'] is int && user['age'] > 20)
        .cast<Map<String, dynamic>>() // List<dynamic> → List<Map<String, dynamic>>
        .toList();

    // 필터링된 유저 출력
    if (filteredUsers.isEmpty) {
      print("⚠️ 나이가 20 초과인 사용자가 없습니다.");
    } else {
      print("\n📄 나이 20 초과인 유저 목록:");
      for (var user in filteredUsers) {
        print("👤 이름: ${user['name']}, 🎂 나이: ${user['age']}, 📍 도시: ${user['city']}");
      }
    }
  } else {
    print("⚠️ JSON 파일(users.json)이 존재하지 않습니다.");
  }
}
  • 나이 기준 필터링 소스 부분 추가 설명
  • users.where((user) => user['age'] is int && user['age'] > 20)
  • where() 메서드는 리스트(List)에서 특정 조건을 만족하는 요소만 필터링하는 기능을 한다.
  • (user) => user['age'] is int && user['age'] > 20
  • user['age'] is int → age가 **정수(int)**인지 확인
  • user['age'] > 20 → age가 20 초과인지 확인 즉, age 값이 숫자이고 20 초과인 유저만 리스트에 남김
  • cast<Map<String, dynamic>>()
  • where()는 Iterable<dynamic> 타입을 반환하기 때문에 List<Map<String, dynamic>> 형태로 변환해야 함.
  • .cast<Map<String, dynamic>>()을 사용하면, 각 요소를 Map<String, dynamic> 타입으로 변환 가능
  • .toList()
  • .toList()를 사용하면 Iterable을 일반적인 List<Map<String, dynamic>>로 변환 가능
단계 코드 역할
1️⃣ 필터링 users.where((user) => user['age'] is int && user['age'] > 20) 나이(age > 20)인 유저만 남김
2️⃣ 타입 변환 .cast<Map<String, dynamic>>() Iterable<dynamic> → Iterable<Map<String, dynamic>>
3️⃣ 리스트 변환 .toList() Iterable → List<Map<String, dynamic>>
  • 최종적으로 List<Map<String, dynamic>> 형태로 필터링된 JSON 데이터를 저장할 수 있음

 


 이렇게 파일 입출력 & JSON 데이터 처리에 대해서 알아보았다. 추가 적인 내용이 필요한 경우에는 댓글을 요청드리고, 틀린 부분이 있다면 이것 또한 댓글로 알려주시면 수정하도록 하겠습니다!


 

반응형