Dart 튜토리얼 12편: 데이터 변환(dart:convert·JSON·숫자 표현)
요약
핵심 요지
- 문제 정의: 네트워크/파일에서 들어온 데이터는 대부분 문자열/바이트이며, 이를
JSON1이나 숫자로 “정확히” 바꾸지 못하면 버그가 조용히 쌓인다. - 핵심 주장: Dart는
dart:convert2로JSON과UTF-83 변환을 제공하고, 숫자 표현은 “비교/문자열 생성 시점”에서 주의가 필요하다. - 주요 근거:
jsonDecode()4/jsonEncode()5,utf8디코더/인코더, 그리고 숫자 문자열 비교 예시가 함께 제시된다. - 실무 기준: 입력은 먼저
JSON으로 “구조를 고정”하고, 출력은 필요한 형태로만jsonEncode()한다.
숫자 값은 문자열 비교를 하기 전에 “어떤 형식으로 출력되는지”를 확정한다.
문서가 설명하는 범위
dart:convert의JSON디코딩/인코딩과UTF-8처리JSON serialization6/deserialization7 관점에서 유용한 라이브러리/패키지 목록- 숫자 표현(
double8)이 문자열/비교에 영향을 주는 사례
읽는 시간: 15분 | 난이도: 초급
참고 자료
- dart
- JSON/UTF-8 변환 - Using JSON - JSON 직렬화/역직렬화 관련 라이브러리/패키지
- Numbers in Dart - 숫자 표현 주의점
문제 상황
서버에서 내려오는 응답은 문자열이고, 파일에서 읽은 값은 바이트 스트림인 경우가 많습니다.
이 값을 곧바로 “원하는 타입”으로 쓰려 하면, 타입이 맞지 않거나(Map9으로 생각했는데 List10였다든지), 인코딩이 깨지거나, 숫자 출력 형식 때문에 비교가 실패할 수 있습니다.
그래서 변환 단계는 “그때그때 처리”가 아니라, 항상 같은 규칙으로 처리하는 것이 중요합니다.
해결 방법
단계 1: jsonDecode()로 JSON 입력의 “구조”를 먼저 고정하기
Why
NOTE
JSON문자열을 그대로 다루면, “키가 있는지/타입이 맞는지”를 매번 추측해야 합니다.
먼저jsonDecode()로 구조를 만들고, 그 구조를 기준으로 다음 작업을 진행하는 편이 안전합니다.import 'dart:convert';var jsonString = '''[{"score": 40},{"score": 80}]''';var scores = jsonDecode(jsonString);
What
NOTE
jsonDecode()는JSON텍스트를 Dart 객체(리스트/맵 등)로 바꿉니다.
입력의 최상위가 배열이면List, 객체면Map형태로 나옵니다.
How
TIP디코딩 결과를 “무슨 구조인지” 확인하고 다음 로직을 붙입니다.
var scores = jsonDecode(jsonString);assert(scores is List);var firstScore = scores[0];assert(firstScore is Map);assert(firstScore['score'] == 40);
Watch out
WARNING
JSON문자열은 문자열 규칙이 Dart 코드 문자열과 다릅니다.
예시처럼JSON내부 문자열은 큰따옴표를 사용해야 합니다.// JSON 문자열 내부는 큰따옴표(")를 사용한다.var jsonString = '''[{"score": 40}]''';
결론: 입력은 jsonDecode()로 “구조를 먼저 고정”하면, 이후 단계가 안정됩니다.
단계 2: jsonEncode()로 “내보낼 형태”를 한 번에 만들기
Why
NOTE내부에서 쓰는 데이터 구조를 그대로 외부에 내보내면, 출력 형식이 흔들릴 수 있습니다.
그래서 출력은jsonEncode()로 “최종 텍스트”를 한 번에 만드는 편이 안전합니다.var scores = [{'score': 40},{'score': 80},];var jsonText = jsonEncode(scores);
What
NOTE
jsonEncode()는 Dart 객체를JSON문자열로 인코딩합니다.
null11 같은 값도 JSON의null로 표현됩니다.
How
TIP리스트/맵을 만들고
jsonEncode()로 변환합니다.var scores = [{'score': 40},{'score': 80},{'score': 100, 'overtime': true, 'special_guest': null},];var jsonText = jsonEncode(scores);assert(jsonText =='[{"score":40},{"score":80},''{"score":100,"overtime":true,''"special_guest":null}]',);
Watch out
WARNING인코딩 결과는 “텍스트”이기 때문에, 출력 비교를 엄격하게 할 때는 공백/순서까지 포함해 달라질 수 있습니다.
즉, 문자열 비교가 목적이라면 “비교 기준(정규화)”을 먼저 정하는 편이 안전합니다.
결론: 출력은 jsonEncode()로 “한 번에” 만들고, 비교는 기준을 정해둡니다.
단계 3: UTF-8 디코딩/인코딩을 “바이트 스트림”과 함께 다루기
Why
NOTE파일/네트워크는 바이트(
List<int>)로 흘러오는 경우가 많습니다.
이 바이트를 문자열로 바꾸려면UTF-8디코딩이 필요합니다.import 'dart:convert';List<int> utf8Bytes = [0xc3, 0x8e, 0xc3, 0xb1, 0xc5, 0xa3, 0xc3, 0xa9,0x72, 0xc3, 0xb1, 0xc3, 0xa5, 0xc5, 0xa3, 0xc3,0xae, 0xc3, 0xb6, 0xc3, 0xb1, 0xc3, 0xa5, 0xc4,0xbc, 0xc3, 0xae, 0xc5, 0xbe, 0xc3, 0xa5, 0xc5,0xa3, 0xc3, 0xae, 0xe1, 0xbb, 0x9d, 0xc3, 0xb1,];
What
NOTE
utf8는UTF-8인코딩을 처리하는 코덱입니다.
utf8.decode()12로 바이트 → 문자열,utf8.encode()13로 문자열 → 바이트를 처리합니다.
How
TIP바이트 리스트를 문자열로 디코딩합니다.
var funnyWord = utf8.decode(utf8Bytes);assert(funnyWord == 'Îñţérñåţîöñåļîžåţîờñ');문자열을 다시 바이트로 인코딩할 수도 있습니다.
var encoded = utf8.encode('Îñţérñåţîöñåļîžåţîờñ');assert(encoded.length == utf8Bytes.length);for (var i = 0; i < encoded.length; i++) {assert(encoded[i] == utf8Bytes[i]);}바이트 스트림을 줄 단위로 읽고 싶다면
utf8.decoder와LineSplitter14를 조합합니다.var lines = utf8.decoder.bind(inputStream).transform(const LineSplitter());try {await for (final line in lines) {print('Got ${line.length} characters from stream');}print('file is now closed');} catch (e) {print(e);}
Watch out
WARNING
UTF-8디코딩은 “바이트가 어떤 인코딩인지”가 전제입니다.
즉, 입력이UTF-8이 아니라면 결과가 깨질 수 있습니다.
결론: 바이트를 문자열로 바꾸는 순간이 핵심이므로, utf8 흐름을 코드로 고정합니다.
단계 4: 숫자 표현은 “문자열 비교”에서 먼저 문제를 만든다
Why
NOTE숫자 자체는 같아도, 문자열로 만들면 표현이 달라질 수 있습니다.
그래서 “숫자 → 문자열”은 테스트/비교에서 특히 조심해야 합니다.void main() {var count = 10.0 * 2;var message = "$count cows";if (message != "20.0 cows") throw Exception("Unexpected: $message");}
What
NOTE숫자(
num15/int16/double)가 문자열로 들어가는 순간, 표현 규칙이 개입합니다.
따라서 비교 기준을 “값 비교”로 할지, “문자열 비교”로 할지 먼저 정해야 합니다.
How
TIP문자열 비교가 필요한 경우, 문자열 템플릿을 명시하는 방식으로 비교 기준을 고정할 수 있습니다.
if (message != "${20.0} cows") throw ...;
Watch out
WARNING숫자 표현 문제는 JSON에도 전파될 수 있습니다.
즉, 화면/로그/테스트에서 “숫자 문자열이 항상 같은 형태일 것”을 전제로 두지 않는 편이 안전합니다.
결론: 숫자는 값 자체보다 “표현(문자열/JSON)”에서 더 자주 문제를 만든다는 관점이 필요합니다.
Footnotes
-
JSON(JSON): 키-값 객체/배열로 데이터를 표현하는 텍스트 형식이다. ↩
-
dart
(dart ): JSON과 UTF-8 같은 인코딩/디코딩 도구를 제공하는 라이브러리다. ↩ -
UTF-8(UTF-8): JSON이 요구하는 문자 인코딩 방식이며, 바이트↔문자열 변환에 사용된다. ↩
-
jsonDecode()(JSON 디코드): JSON 문자열을 Dart 객체로 변환하는 함수다. ↩
-
jsonEncode()(JSON 인코드): Dart 객체를 JSON 문자열로 변환하는 함수다. ↩
-
serialization(직렬화): 객체를 전송/저장 가능한 형식(JSON 등)으로 바꾸는 과정이다. ↩
-
deserialization(역직렬화): 전송/저장된 형식(JSON 등)을 객체로 되돌리는 과정이다. ↩
-
double(더블): 부동소수점 수를 표현하는 타입이다. ↩
-
Map(맵): 키와 값의 쌍을 저장하는 컬렉션이다. ↩
-
List(리스트): 순서가 있는 컬렉션이다. ↩
-
null(널): 값이 없음을 나타내는 특수한 값이다. ↩
-
utf8.decode(UTF-8 디코드): UTF-8 바이트를 문자열로 디코딩하는 함수다. ↩
-
utf8.encode(UTF-8 인코드): 문자열을 UTF-8 바이트로 인코딩하는 함수다. ↩
-
LineSplitter(라인 스플리터): 입력을 줄 단위로 나누는 변환기다. ↩
-
num(넘): 숫자 타입의 상위 타입이다. ↩
-
int(인트): 정수를 표현하는 타입이다. ↩
공유
이 글이 도움이 되었다면 다른 사람과 공유해주세요!