이번에는 파일 입출력 (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 데이터 처리에 대해서 알아보았다. 추가 적인 내용이 필요한 경우에는 댓글을 요청드리고, 틀린 부분이 있다면 이것 또한 댓글로 알려주시면 수정하도록 하겠습니다!
'IT > Dart' 카테고리의 다른 글
Dart_9일차 : 예외 처리 (Exception Handling) & Future Error Handling (0) | 2025.03.07 |
---|---|
Dart_8일차 : 클래스 심화 (생성자, Getter/Setter, 연산자 오버로딩) (0) | 2025.03.06 |
Dart_6일차 : 고급 Stream 활용 (listen(), StreamController, Broadcast Stream) (2) | 2025.03.04 |
Dart_5일차 : 비동기 프로그래밍 (Future, async/await, Stream) (0) | 2025.02.21 |
Dart_4일차 : 클래스(Class)와 객체(Object) 기초 (0) | 2025.02.20 |