가이드

파이썬 JSON: 파일 읽고 쓰기, CSV, pathlib, UTF-8 인코딩

업데이트: · Skillaria.Top

파이썬에서 JSON은 내장 모듈 json으로 다룹니다. json.load는 열린 파일에서, json.loads는 문자열에서 데이터를 읽고, json.dump와 json.dumps는 반대로 씁니다. 파일은 with open(path, encoding="utf-8")으로 열고, 경로는 pathlib.Path로 만들며, 표 형태의 데이터는 csv 모듈로 읽습니다.

파이썬에서 JSON을 다루는 데는 외부 라이브러리가 필요 없습니다. 표준 라이브러리의 json 모듈이 딕셔너리와 리스트를 텍스트로 바꾸고, 텍스트를 다시 객체로 바꿉니다. 함께 쓰이는 것은 파일을 여는 open과 with 구문, 경로를 다루는 pathlib, 표 데이터를 다루는 csv 모듈입니다. 이 글에서는 실제 코드와 자주 걸리는 함정을 순서대로 정리합니다.

파이썬 open 함수: 모드와 인코딩

텍스트 파일은 내장 함수 open(path, mode, encoding=...)으로 엽니다. 파일 객체를 돌려주며, 이 객체에는 read, readline, write 메서드가 있고, 반복문으로 한 줄씩 순회할 수 있습니다.

모드하는 일
"r"읽기 (기본값). 파일이 없으면 FileNotFoundError
"w"쓰기. 파일을 만들거나, 열자마자 내용을 비웁니다
"a"끝에 덧붙이기. 기존 내용은 그대로 둡니다
"x"새 파일 만들기. 이미 있으면 FileExistsError
"b"바이너리 모드 추가 ("rb", "wb")

with를 쓰는 이유

파일은 반드시 닫아야 합니다. 닫지 않으면 데이터가 디스크에 제대로 기록되지 않을 수 있고, 운영체제가 파일 핸들을 계속 붙잡고 있습니다. with 구문은 블록 안에서 오류가 나더라도 파일을 자동으로 닫습니다.

with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("첫 번째 줄\n")
    f.write("두 번째 줄\n")

with open("notes.txt", encoding="utf-8") as f:
    for line in f:
        print(line.rstrip("\n"))

파일을 반복문으로 순회하면 한 번에 한 줄씩만 읽으므로, 기가바이트 단위의 로그도 처리할 수 있습니다. f.read()는 전체 내용을 문자열 하나로, f.readlines()는 줄의 리스트로 돌려줍니다.

기존 내용을 지우지 않고 한 줄 추가하기

초보자가 가장 많이 하는 실수는 파일에 한 줄을 '추가'하려고 "w" 모드로 여는 것입니다. "w"는 write를 호출하기 전, 열 때 파일을 비웁니다. 끝에 덧붙이려면 "a"를, 이미 있는 파일을 실수로 덮어쓰지 않으려면 "x"를 쓰세요.

with open("log.txt", "a", encoding="utf-8") as f:
    f.write("새 기록\n")

write는 줄바꿈을 자동으로 넣지 않으므로 "\n"을 직접 써야 합니다. print(..., file=f)를 쓰면 줄바꿈이 자동으로 붙습니다.

UTF-8 인코딩: 글자가 깨지는 이유

Python 3.15 이전에는 encoding을 주지 않으면 open()이 시스템 기본 인코딩을 씁니다. Linux와 macOS는 보통 UTF-8이지만, 한국어 Windows는 CP949를 씁니다. 서버에서는 잘 돌던 스크립트가 다른 컴퓨터에서 글자가 깨지거나 UnicodeDecodeError를 내기도 했습니다.

Python 3.15는 PEP 686에 따라 UTF-8 모드를 기본으로 켭니다. encoding을 생략해도 모든 운영체제에서 UTF-8로 읽고 씁니다. 이전 동작으로 되돌리려면 환경 변수 PYTHONUTF8=0이나 옵션 -X utf8=0을 쓰고, 시스템 기본 인코딩을 명시적으로 쓰려면 encoding="locale"을 지정합니다.

import sys
print(sys.flags.utf8_mode)   # Python 3.15에서는 1

open("legacy.txt", encoding="locale")     # 시스템 인코딩
open("old_excel.csv", encoding="cp949")   # CP949로 저장된 파일

반대쪽 문제도 있습니다. 예전 Windows 스크립트나 엑셀이 CP949로 저장한 CSV는 이제 UTF-8로 읽히므로 UnicodeDecodeError가 날 수 있습니다. 이때는 encoding="cp949"로 읽은 뒤 UTF-8로 다시 저장하세요. 가장 좋은 습관은 인코딩을 항상 명시하는 것입니다. 그러면 파이썬 버전과 상관없이 같은 결과가 나옵니다.

pathlib: 경로를 객체로 다루기

경로를 문자열로 이어 붙이면("data" + "/" + name) 불편하고 운영체제에 따라 깨집니다. pathlib 모듈의 Path 클래스는 / 연산자로 경로를 잇고, 여러 속성을 제공합니다.

from pathlib import Path

data = Path("data")
csv_path = data / "expenses.csv"

print(csv_path.name)    # expenses.csv
print(csv_path.stem)    # expenses
print(csv_path.suffix)  # .csv
print(csv_path.parent)  # data
print(csv_path.exists())

data.mkdir(parents=True, exist_ok=True)
(data / "hello.txt").write_text("안녕하세요", encoding="utf-8")
print((data / "hello.txt").read_text(encoding="utf-8"))

for p in data.glob("*.csv"):
    print(p)

작은 파일이라면 read_text와 write_text가 open과 with를 대신합니다. Path 객체는 open, json, csv 등 어디든 그대로 넘길 수 있습니다. 현재 작업 폴더가 아니라 스크립트 파일 기준의 경로가 필요하다면 Path(__file__).parent / "data"를 쓰세요.

Python 3.14에서는 Path에 copy()와 move() 메서드가 추가되어, 간단한 복사에는 shutil이 필요 없어졌습니다. 같은 버전에서는 Zstandard 압축을 위한 compression.zstd 모듈도 표준 라이브러리에 들어왔습니다(PEP 784).

파이썬 JSON: load, dump, loads, dumps

JSON은 데이터를 주고받을 때 쓰는 텍스트 형식입니다. API가 JSON을 응답하고, 설정이나 보고서를 JSON으로 저장합니다. json 모듈은 두 가지 작업을 합니다. 직렬화(파이썬 객체 → JSON 텍스트)와 역직렬화(텍스트 → 파이썬 객체)입니다. 함수는 네 개인데, 헷갈리기 쉽습니다.

함수하는 일
json.dumps(obj)객체 → JSON 문자열
json.loads(s)JSON 문자열 → 객체
json.dump(obj, f)객체 → 열린 파일에 쓰기
json.load(f)열린 파일에서 JSON 읽기

기억하기 쉬운 규칙이 있습니다. 이름 끝의 s는 string, 즉 문자열을 다룬다는 뜻입니다. s가 없으면 파일을 다룹니다.

타입 대응표

파이썬JSON
dict객체 {}
list, tuple배열 []
str문자열
int, float숫자
True / Falsetrue / false
Nonenull

튜플은 다시 읽으면 리스트가 됩니다. 집합, 날짜, Decimal은 모듈이 모르므로 TypeError: Object of type ... is not JSON serializable이 납니다.

JSON 파일로 저장하기

import json
from pathlib import Path

report = {
    "month": "2026-10",
    "total": 15230.5,
    "categories": {"식비": 8200, "교통": 2100},
    "tags": ["집", "회사"],
}

path = Path("data/report.json")
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as f:
    json.dump(report, f, ensure_ascii=False, indent=2)

ensure_ascii=False를 주면 한글이 그대로 저장됩니다. 이 옵션이 없으면 '식비'가 "식비" 같은 형태로 저장됩니다. 올바른 JSON이지만 사람이 읽기는 어렵습니다. indent=2는 들여쓰기를 넣어 보기 좋게 만듭니다.

JSON 파일 읽기

with open("data/report.json", encoding="utf-8") as f:
    data = json.load(f)

print(data["total"])               # 15230.5
print(data["categories"]["식비"])   # 8200

결과는 평범한 딕셔너리나 리스트이므로 익숙한 방법으로 다루면 됩니다. 파일이 JSON이 아니거나 잘렸다면 json.JSONDecodeError가 발생하고, 오류 위치의 줄과 열을 알려 줍니다. 데이터가 외부에서 들어오는 경우에는 이 예외를 잡아 두세요.

JSON 문자열 파싱과 예쁘게 출력하기

import json

raw = '{"name": "안나", "skills": ["python", "sql"], "active": true}'
user = json.loads(raw)
print(user["skills"][0])   # python

print(json.dumps(user, ensure_ascii=False, indent=2, sort_keys=True))

sort_keys=True는 출력할 때 키를 정렬합니다. git에서 파일 두 개를 비교할 때 편합니다. 터미널에서 파일을 보기 좋게 정리하려면 내장 도구 python -m json.tool data/report.json을 쓸 수 있습니다.

HTTP API의 응답도 JSON 문자열입니다. requests 라이브러리의 response.json()은 내부적으로 json.loads를 호출합니다.

함정: JSON의 키는 항상 문자열

d = {1: "1월", 2: "2월"}
back = json.loads(json.dumps(d))
print(back == d)   # False
print(back)        # {'1': '1월', '2': '2월'}
# back[1] -> KeyError, back["1"]을 써야 합니다

fixed = {int(k): v for k, v in back.items()}

JSON 객체의 키는 문자열만 가능합니다. 그래서 숫자 키는 저장과 복원을 거치면 문자열이 됩니다. 읽은 뒤에 필요한 타입으로 바꿔 주세요.

직접 만든 타입: 날짜와 Decimal

import json
from datetime import date

def default(obj):
    if isinstance(obj, date):
        return obj.isoformat()
    raise TypeError(f"직렬화할 수 없는 타입: {type(obj).__name__}")

print(json.dumps({"day": date(2026, 10, 1)}, default=default))
# {"day": "2026-10-01"}

default 인자로 넘긴 함수는 모듈이 모르는 객체를 만날 때마다 호출됩니다. 더 복잡한 경우에는 json.JSONEncoder를 상속하고 default 메서드를 재정의하세요.

JSON 배열에 항목 추가하기

JSON은 텍스트 로그처럼 끝에 덧붙일 수 없습니다. 파일을 읽고, 바꾸고, 전체를 다시 써야 합니다.

path = Path("data/items.json")
items = json.loads(path.read_text(encoding="utf-8")) if path.exists() else []
items.append({"id": len(items) + 1, "title": "새 항목"})
path.write_text(json.dumps(items, ensure_ascii=False, indent=2), encoding="utf-8")

기록이 많고 계속 늘어난다면 JSON Lines 형식이 좋습니다. 한 줄에 JSON 객체 하나를 쓰고, 파일은 "a" 모드로 엽니다.

파이썬 CSV: csv 모듈

CSV는 줄이 레코드, 쉼표로 구분된 값이 필드인 텍스트 표입니다. csv 모듈은 따옴표, 값 안의 쉼표, 줄바꿈을 올바르게 처리합니다. 손으로 split(",")을 쓰면 이런 경우에 틀립니다.

import csv

with open("data/expenses.csv", newline="", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    total = 0.0
    for row in reader:
        total += float(row["amount"])
        print(row["date"], row["category"], row["amount"])
print("합계:", total)

csv.DictReader는 헤더 행을 기준으로 각 줄을 딕셔너리로 바꿉니다. 모든 값은 문자열이므로 숫자와 날짜는 직접 변환하세요(float, int, date.fromisoformat). 세미콜론(;)으로 구분된 파일(한국어 엑셀이 저장하는 형식)은 delimiter=";"로 읽습니다.

rows = [
    {"date": "2026-10-01", "category": "식비", "amount": 450},
    {"date": "2026-10-02", "category": "교통", "amount": 60},
]
with open("data/out.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["date", "category", "amount"])
    writer.writeheader()
    writer.writerows(rows)

쓸 때는 newline=""를 반드시 지정하세요. 없으면 Windows에서 줄 사이에 빈 줄이 생깁니다. 파일을 엑셀로 열 예정이라면 encoding="utf-8-sig"를 쓰세요. BOM 표식이 있으면 엑셀이 UTF-8로 인식합니다.

CSV에서 JSON으로: 전체 예제

import csv
import json
from collections import defaultdict
from pathlib import Path

src = Path("data/expenses.csv")
dst = Path("data/report.json")

by_category = defaultdict(float)
with src.open(newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        by_category[row["category"]] += float(row["amount"])

report = {"total": sum(by_category.values()), "categories": dict(by_category)}
dst.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
print(dst.read_text(encoding="utf-8"))

이것이 보고서 스크립트의 기본 구조입니다. CSV에서 읽고, 딕셔너리에서 집계하고, JSON으로 저장합니다.

자주 하는 실수

  • FileNotFoundError — 상대 경로는 스크립트 위치가 아니라 실행한 폴더를 기준으로 합니다. Path(__file__).parent를 쓰세요.
  • 파일 내용이 사라짐 — "w" 대신 "a"를 써야 했습니다.
  • UnicodeDecodeError — 파일의 인코딩이 다릅니다. cp949나 utf-8-sig를 명시하세요.
  • TypeError: Object of type set is not JSON serializable — 리스트로 바꾸거나 default 함수를 넘기세요.
  • JSON을 읽은 뒤 KeyError — 숫자 키가 문자열로 바뀌었습니다.
  • 내 파일 이름이 json.py나 csv.py인 경우 — 표준 모듈보다 먼저 가져와서 import json이 내 파일을 가져옵니다.

정리

파이썬에서 JSON은 표준 라이브러리로 충분합니다. 파일에는 json.load와 json.dump를, 문자열에는 loads와 dumps를 씁니다. 한글을 보존하려면 ensure_ascii=False를, 읽기 좋게 만들려면 indent를 씁니다. 파일은 with로 열고 인코딩을 명시하며, 경로는 pathlib로 다루고, 표는 csv.DictReader로 읽습니다. 이 주제는 과정 강의에서 실습합니다. 지출 관리기가 CSV의 거래 내역을 읽고 JSON 보고서를 저장합니다.

자주 묻는 질문

json.load와 json.loads는 무엇이 다른가요?

json.load는 열린 파일 객체에서 읽고, json.loads는 문자열을 분석합니다. 마찬가지로 json.dump는 파일에 쓰고, json.dumps는 문자열을 돌려줍니다.

JSON 파일에 한글을 \u 이스케이프 없이 저장하려면?

json.dump나 json.dumps에 ensure_ascii=False를 주고, 파일은 encoding="utf-8"로 여세요.

JSON을 다루려고 라이브러리를 따로 설치해야 하나요?

아닙니다. json 모듈은 표준 라이브러리에 포함되어 있어서 import json만 하면 됩니다.

JSON에 날짜나 집합을 저장할 수 있나요?

그대로는 안 됩니다. 날짜는 isoformat으로 문자열로 바꾸고, 집합은 리스트로 바꾸거나, default 함수를 넘기세요.

파일이 메모리에 들어가지 않을 만큼 큰 JSON은 어떻게 읽나요?

json.load는 파일 전체를 읽습니다. 대용량이라면 JSON Lines 형식으로 저장하고 한 줄씩 json.loads로 처리하세요.

영상으로 이 주제 배우기

이 주제는 “Python 3.15: от новичка до профи” 강의에서 실습과 함께 자세히 다룹니다.

강의로 이동