Python JSON to CSV — pandas 예제

·Backend Developer·검토자Priya Sharma·게시일

무료 JSON to CSV 변환기을 브라우저에서 직접 사용하세요 — 설치 불필요.

JSON to CSV 변환기 온라인으로 사용하기 →

거의 모든 데이터 파이프라인은 결국 같은 단계에 부딪힙니다: API가 JSON을 반환하지만 다음 소비자 — 스프레드시트, 임포트 스크립트, Redshift COPY 명령 — 는 CSV를 필요로 합니다.Python으로 JSON을 CSV로 변환하는 것은 간단해 보이지만 중첩 객체, 일관되지 않은 키, 특별한 처리가 필요한 날짜값에 부딪히면 이야기가 달라집니다. Python은 두 가지 견고한 방법을 제공합니다: 의존성 없는 스크립트를 위한 내장 json + csv 모듈과, 중첩 평탄화 및 대용량 데이터셋을 위한 pandas — 또는 코드 없이 빠르게 처리하려면 온라인 JSON to CSV 변환기를 사용하세요. 이 가이드는 실행 가능한 Python 3.8+ 예제를 통해 두 가지 방법을 처음부터 끝까지 다룹니다.

  • csv.DictWriter는 의존성 없이 딕셔너리 목록을 CSV로 변환합니다 — json.load()로 파싱 후 writeheader() + writerows()를 호출하세요.
  • Windows에서 데이터 행 사이의 빈 행을 방지하려면 항상 newline=""으로 CSV 파일을 여세요.
  • pd.json_normalize()는 to_csv() 호출 전에 중첩 JSON을 평탄한 DataFrame으로 변환합니다 — 다중 레벨 중첩을 자동으로 처리합니다.
  • DataFrame.to_csv()에 index=False를 전달하세요 — 없으면 pandas가 원하지 않는 행 번호 열을 기록합니다.
  • 500 MB 이상 파일은 일정한 메모리 사용을 위해 ijson 스트리밍 JSON 파싱과 csv.DictWriter를 함께 사용하세요.

JSON to CSV 변환이란?

JSON to CSV 변환은 JSON 객체 배열을 각 객체가 행이 되고 각 키가 열 헤더가 되는 표 형식으로 변환합니다. JSON은 계층적 구조로 — 객체가 임의로 깊게 중첩될 수 있습니다. CSV는 평탄한 구조로 — 모든 값이 행-열 격자에 위치합니다. 모든 객체가 동일한 최상위 키 집합을 공유할 때 변환이 깔끔하게 이루어집니다. 중첩 객체, 배열, 일관되지 않은 키에서 흥미로운 문제가 생깁니다. 원시 데이터는 동일하게 유지되고 구조만 변경됩니다.

Before · json
After · json
[{"order_id":"ord_91a3","total":149.99,"status":"shipped"},
 {"order_id":"ord_b7f2","total":34.50,"status":"pending"}]
order_id,total,status
ord_91a3,149.99,shipped
ord_b7f2,34.50,pending

csv.DictWriter — pandas 없이 JSON을 CSV로 변환

csv 모듈은 모든 Python 설치에 포함되어 있습니다. pip install도, 가상 환경 설정도 필요 없습니다. csv.DictWriter 는 딕셔너리 목록을 받아 각각을 CSV 행으로 기록하며, 딕셔너리 키를 열 헤더에 매핑합니다. fieldnames 파라미터는 열 순서와 포함할 키를 모두 제어합니다.

Python 3.8+ — 최소한의 json to csv 예제
import json
import csv

# 샘플 JSON 데이터 — 주문 객체 배열
json_string = """
[
  {"order_id": "ord_91a3", "product": "Wireless Keyboard", "quantity": 2, "unit_price": 74.99},
  {"order_id": "ord_b7f2", "product": "USB-C Hub", "quantity": 1, "unit_price": 34.50},
  {"order_id": "ord_c4e8", "product": "Monitor Stand", "quantity": 3, "unit_price": 29.95}
]
"""

records = json.loads(json_string)

with open("orders.csv", "w", newline="", encoding="utf-8") as csvfile:
    writer = csv.DictWriter(csvfile, fieldnames=records[0].keys())
    writer.writeheader()
    writer.writerows(records)

# orders.csv:
# order_id,product,quantity,unit_price
# ord_91a3,Wireless Keyboard,2,74.99
# ord_b7f2,USB-C Hub,1,34.50
# ord_c4e8,Monitor Stand,3,29.95

open() newline="" 인수는 Windows에서 선택 사항이 아닙니다. 없으면 이중 캐리지 리턴이 발생하여 Excel에서 모든 데이터 행 사이에 빈 행으로 표시됩니다. macOS와 Linux에서는 무해하므로 항상 포함하세요.

위 코드는 문자열에 json.loads()를 사용합니다. 파일 핸들에서 읽을 때는 json.load() (끝에 s 없음)를 사용하세요. 이 부분에서 자주 실수합니다 — 하나는 문자열을 읽고, 다른 하나는 파일 객체를 읽습니다.

Python 3.8+ — JSON 파일 읽기, CSV 파일 쓰기
import json
import csv

with open("server_metrics.json", encoding="utf-8") as jf:
    metrics = json.load(jf)  # 파일 객체에는 json.load() 사용

# 명시적인 fieldnames로 열 순서 제어
columns = ["timestamp", "hostname", "cpu_percent", "memory_mb", "disk_io_ops"]

with open("server_metrics.csv", "w", newline="", encoding="utf-8") as cf:
    writer = csv.DictWriter(cf, fieldnames=columns, extrasaction="ignore")
    writer.writeheader()
    writer.writerows(metrics)

# 지정된 다섯 개 열만 정확히 그 순서로 출력됨

extrasaction="ignore"를 설정하면 fieldnames 목록에 없는 딕셔너리의 키를 자동으로 무시합니다. 기본값은 "raise"로, 딕셔너리에 예상치 못한 키가 있으면 ValueError를 발생시킵니다. 예상치 못한 상황에 대한 허용 수준에 따라 선택하세요.

참고:csv.DictWriter vs csv.writer: DictWriter는 딕셔너리 키를 열 위치에 자동으로 매핑합니다. csv.writer는 원시 목록을 행으로 기록하므로 열 순서를 직접 관리해야 합니다. JSON 레코드는 이미 딕셔너리이므로 JSON-to-CSV에는 DictWriter가 거의 항상 올바른 선택입니다.

Python의 csv 모듈에는 세 가지 명명된 방언이 있습니다: excel (쉼표 구분자, CRLF 줄 끝 — 기본값), excel-tab (탭 구분자, CRLF 줄 끝), unix (LF 줄 끝, 비숫자 필드 모두 인용). csv.DictWriter dialect 인수에 방언 이름을 전달하세요. 대상 시스템에 특이한 인용 또는 구분자 규칙이 있을 때는 csv.register_dialect()로 커스텀 방언을 정의할 수도 있습니다. 대부분의 JSON-to-CSV 워크플로에는 excel 방언이 적합하지만, awk sort 같은 POSIX 도구로 처리할 파일을 작성할 때는 unix로 전환하세요.

비표준 타입 처리: datetime, UUID, Decimal

API의 JSON에는 날짜가 ISO 문자열로, UUID가 하이픈으로 구분된 문자열로, 금액 값이 부동소수점으로 자주 포함됩니다. CSV를 쓰기 전에 처리를 위해 Python 객체로 파싱할 때는 다시 문자열로 변환해야 합니다. csv 모듈은 모든 값에 str()을 호출하므로 대부분의 타입은 그냥 작동합니다. 하지만 datetime 객체는 지저분한 기본 문자열 표현을 생성하고, Decimal 값은 지수 표기를 피하기 위해 명시적 형식이 필요합니다.

Python 3.8+ — CSV 쓰기 전에 datetime과 Decimal 전처리
import json
import csv
from datetime import datetime, timezone
from decimal import Decimal
from uuid import UUID

# Python 타입을 가진 파싱된 API 응답 시뮬레이션
transactions = [
    {
        "txn_id": UUID("a1b2c3d4-e5f6-7890-abcd-ef1234567890"),
        "created_at": datetime(2026, 3, 15, 9, 30, 0, tzinfo=timezone.utc),
        "amount": Decimal("1249.99"),
        "currency": "USD",
        "merchant": "CloudHost Inc.",
    },
    {
        "txn_id": UUID("b2c3d4e5-f6a7-8901-bcde-f12345678901"),
        "created_at": datetime(2026, 3, 15, 14, 12, 0, tzinfo=timezone.utc),
        "amount": Decimal("87.50"),
        "currency": "EUR",
        "merchant": "DataSync GmbH",
    },
]

def prepare_row(record: dict) -> dict:
    """비문자열 타입을 CSV 친화적인 문자열로 변환합니다."""
    return {
        "txn_id": str(record["txn_id"]),
        "created_at": record["created_at"].isoformat(),
        "amount": f"{record['amount']:.2f}",
        "currency": record["currency"],
        "merchant": record["merchant"],
    }

with open("transactions.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["txn_id", "created_at", "amount", "currency", "merchant"])
    writer.writeheader()
    for txn in transactions:
        writer.writerow(prepare_row(txn))

# transactions.csv:
# txn_id,created_at,amount,currency,merchant
# a1b2c3d4-e5f6-7890-abcd-ef1234567890,2026-03-15T09:30:00+00:00,1249.99,USD,CloudHost Inc.
# b2c3d4e5-f6a7-8901-bcde-f12345678901,2026-03-15T14:12:00+00:00,87.50,EUR,DataSync GmbH

prepare_row() 함수가 여기서 올바른 접근 방식입니다. csv.DictWriter에 커스텀 타입을 가르치려 하는 대신, 쓰기 전에 각 레코드를 문자열로 정규화합니다.str()에 의존하는 것보다 datetime 객체에 명시적으로 .isoformat()을 호출하는 것을 선호합니다 — 출력 형식이 더 예측 가능하고 다운스트림 파서가 ISO 8601을 안정적으로 처리합니다.

주의:Decimal 값을 형식화 없이 통과시키면, 매우 작거나 큰 숫자가 지수 표기법으로 렌더링될 수 있습니다(예: 1.5E+7). CSV에 금융 데이터를 기록할 때는 항상 f"{value:.2f}"와 같은 명시적 f-string으로 Decimal을 형식화하세요.

커스텀 타입이 많은 파이프라인의 대안 패턴은 json.JSONEncoder를 확장하는 것입니다. 서브클래스를 만들고 default() 메서드를 재정의하여 각 커스텀 타입에 대해 JSON 직렬화 가능한 값을 반환한 다음, 서브클래스를 json.dumps() cls 인수로 전달합니다. CSV에 쓰기 전에 커스텀 인코더를 통해 다시 인코딩하면 행별 prepare_row() 호출 없이 한 단계에서 모든 타입이 정규화됩니다. 위에서 보여준 prepare_row() 패턴은 일회성 스크립트에 더 간단하고, JSONEncoder 서브클래스 방식은 동일한 도메인 모델이 여러 파이프라인 단계나 마이크로서비스 간에 공유될 때 더 잘 확장됩니다.

csv.DictWriter 파라미터 참조

전체 생성자 시그니처는 csv.DictWriter(f, fieldnames, restval="", extrasaction="raise", dialect="excel", **fmtparams)입니다. 대부분은 합리적인 기본값을 가집니다. 실제로 변경할 것은 fieldnames, delimiter,extrasaction입니다.

파라미터
타입
기본값
설명
f
file object
(필수)
write() 메서드가 있는 모든 객체 — 일반적으로 open()에서 반환됨
fieldnames
sequence
(필수)
CSV 출력의 열 순서를 정의하는 키 목록
restval
str
""
dict에 fieldnames의 키가 없을 때 기록되는 값
extrasaction
str
"raise"
"raise"는 추가 키에 대해 ValueError를 발생시키고, "ignore"는 자동으로 무시함
dialect
str / Dialect
"excel"
미리 정의된 형식 규칙 — "excel", "excel-tab", "unix"
delimiter
str
","
필드를 구분하는 단일 문자 — TSV 출력에는 "\t" 사용
quotechar
str
"
구분자가 포함된 필드를 인용하는 데 사용되는 문자
quoting
int
csv.QUOTE_MINIMAL
인용 적용 시점 제어 — MINIMAL, ALL, NONNUMERIC, NONE
lineterminator
str
"\r\n"
각 행 뒤에 추가되는 문자열 — Unix 스타일 출력에는 "\n"으로 재정의

pandas — DataFrame으로 JSON을 CSV로 변환

이미 pandas 중심 코드베이스에서 작업 중이거나, 평탄화가 필요한 중첩 JSON이 있다면 pandas 방식이 stdlib 버전보다 훨씬 적은 코드로 가능합니다. 트레이드오프: pandas는 ~30 MB 의존성입니다. 일회성 스크립트라면 괜찮습니다. 프로덕션에 배포하는 Docker 이미지라면 stdlib 방식이 더 가볍습니다.

Python 3.8+ — pandas read_json 후 to_csv
import pandas as pd

# JSON 배열을 DataFrame으로 직접 읽기
df = pd.read_json("warehouse_inventory.json")

# CSV로 쓰기 — index=False로 자동 생성된 행 번호 방지
df.to_csv("warehouse_inventory.csv", index=False)

# 끝입니다. 두 줄. pandas가 열 타입을 자동으로 추론합니다.

index=False 플래그는 매번 찾아봐야 하는 것들 중 하나입니다. 없으면 pandas가 0, 1, 2, ... 열을 CSV의 첫 번째 열로 기록합니다. 아무도 원하지 않는 것입니다.

json_normalize로 중첩 JSON 평탄화

실제 API 응답은 거의 평탄하지 않습니다. 주문에는 배송 주소가 포함되고, 사용자에는 중첩된 설정이 포함되며, 원격 측정 이벤트에는 중첩된 메타데이터가 포함됩니다. pd.json_normalize() 는 중첩된 딕셔너리를 순회하고 점으로 구분된 이름의 열로 평탄화합니다.

Python 3.8+ — json_normalize를 사용하여 중첩 JSON 평탄화
import json
import pandas as pd

api_response = """
[
  {
    "order_id": "ord_91a3",
    "placed_at": "2026-03-15T09:30:00Z",
    "customer": {
      "name": "Sarah Chen",
      "email": "s.chen@example.com",
      "tier": "premium"
    },
    "shipping": {
      "method": "express",
      "address": {
        "city": "Portland",
        "state": "OR",
        "zip": "97201"
      }
    },
    "total": 299.95
  },
  {
    "order_id": "ord_b7f2",
    "placed_at": "2026-03-15T14:12:00Z",
    "customer": {
      "name": "James Park",
      "email": "j.park@example.com",
      "tier": "standard"
    },
    "shipping": {
      "method": "standard",
      "address": {
        "city": "Austin",
        "state": "TX",
        "zip": "73301"
      }
    },
    "total": 87.50
  }
]
"""

orders = json.loads(api_response)

# json_normalize는 중첩 딕셔너리를 평탄화 — sep는 구분자 제어
df = pd.json_normalize(orders, sep="_")
df.to_csv("flat_orders.csv", index=False)

# 결과 열:
# order_id, placed_at, customer_name, customer_email, customer_tier,
# shipping_method, shipping_address_city, shipping_address_state,
# shipping_address_zip, total

sep="_" 파라미터는 중첩 키 이름이 어떻게 연결되는지 제어합니다. 기본값은 "."로, customer.name과 같은 열을 만듭니다. 열 이름의 점은 SQL 임포트와 일부 스프레드시트 수식에서 문제를 일으키므로 밑줄을 선호합니다.

중첩 키 아래에 레코드 배열이 감싸진 API 응답의 경우 record_path 파라미터를 사용하세요. 응답이 {"data": {"orders": [...]}}처럼 생겼다면 record_path=["data", "orders"]를 전달하여 올바른 목록으로 이동합니다. 선택적 meta 파라미터를 사용하면 중첩 레코드와 함께 상위 레벨 필드를 가져올 수 있습니다 — 응답에 모든 행의 열로 원하는 최상위 페이지네이션 정보(페이지 번호, 전체 개수)가 포함된 경우 유용합니다. record_path meta를 함께 사용하면 커스텀 전처리 없이 대부분의 실제 중첩 API 응답 형태를 처리할 수 있습니다.

DataFrame.to_csv() 파라미터 참조

DataFrame.to_csv() 에는 20개 이상의 파라미터가 있습니다. JSON-to-CSV 워크플로에서 중요한 것들입니다.

파라미터
타입
기본값
설명
path_or_buf
str / Path / None
None
파일 경로 또는 버퍼 — None이면 CSV를 문자열로 반환
sep
str
","
필드 구분자 — TSV에는 "\t" 사용
index
bool
True
행 인덱스를 첫 번째 열로 기록 — 거의 항상 False로 설정
columns
list
None
출력에서 열의 부분 집합 선택 및 순서 변경
header
bool / list
True
열 이름 기록 — 기존 파일에 추가할 때는 False로 설정
encoding
str
"utf-8"
출력 인코딩 — Windows에서 Excel 호환성을 위해 "utf-8-sig" 사용
na_rep
str
""
누락된 값(NaN, None)의 문자열 표현
quoting
int
csv.QUOTE_MINIMAL
필드 인용 시점 제어
Python 3.8+ — 일반적인 파라미터 재정의로 to_csv 사용
import pandas as pd

df = pd.read_json("telemetry_events.json")

# 명시적 인코딩 및 누락값 처리와 함께 TSV 출력
df.to_csv(
    "telemetry_events.tsv",
    sep="\t",
    index=False,
    encoding="utf-8",
    na_rep="NULL",
    columns=["event_id", "timestamp", "source", "severity", "message"],
)

# 셸 스크립트에서 파이핑을 위해 stdout으로 쓰기
print(df.to_csv(index=False))

# 문자열로 반환 (파일 기록 없음)
csv_string = df.to_csv(index=False)
print(len(csv_string), "characters")

파일 및 API 응답에서 JSON을 CSV로 변환

두 가지 가장 일반적인 실제 시나리오: 디스크의 파일에서 JSON을 읽어 변환하거나, HTTP API에서 JSON을 가져와 CSV로 저장하는 것입니다. 개발 중에는 오류 처리 없이도 넘어갈 수 있습니다. 프로덕션에서는 그 선택이 새벽 2시의 알람이 됩니다. 파일이 존재하지 않을 수 있고, API가 JSON 대신 4xx나 5xx 상태 코드를 반환할 수 있으며, 응답 본문이 배열이 아닌 오류 객체일 수 있고, 네트워크 타임아웃으로 JSON이 잘릴 수도 있습니다. 아래 패턴은 이러한 모든 경우를 명시적으로 처리하고, 오류를 stderr에 로그하며, 호출자가 빈 행 출력을 감지하고 적절히 알림을 보낼 수 있도록 행 수를 반환합니다.

디스크의 파일 — 읽기, 변환, 저장

Python 3.8+ — 오류 처리와 함께 JSON 파일을 CSV로 변환
import json
import csv
import sys

def json_file_to_csv(input_path: str, output_path: str) -> int:
    """객체 배열을 포함하는 JSON 파일을 CSV로 변환합니다.
    기록된 행 수를 반환합니다.
    """
    try:
        with open(input_path, encoding="utf-8") as jf:
            data = json.load(jf)
    except FileNotFoundError:
        print(f"Error: {input_path} not found", file=sys.stderr)
        return 0
    except json.JSONDecodeError as exc:
        print(f"Error: invalid JSON in {input_path}: {exc.msg} at line {exc.lineno}", file=sys.stderr)
        return 0

    if not isinstance(data, list) or not data:
        print(f"Error: expected a non-empty JSON array in {input_path}", file=sys.stderr)
        return 0

    # 모든 레코드에서 고유 키 수집 — 일관되지 않은 스키마 처리
    all_keys: list[str] = []
    seen: set[str] = set()
    for record in data:
        for key in record:
            if key not in seen:
                all_keys.append(key)
                seen.add(key)

    with open(output_path, "w", newline="", encoding="utf-8") as cf:
        writer = csv.DictWriter(cf, fieldnames=all_keys, restval="", extrasaction="ignore")
        writer.writeheader()
        writer.writerows(data)

    return len(data)

rows = json_file_to_csv("deploy_logs.json", "deploy_logs.csv")
print(f"Wrote {rows} rows to deploy_logs.csv")

HTTP API 응답 — 가져오기 및 변환

Python 3.8+ — API에서 JSON 가져와 CSV로 저장
import json
import csv
import urllib.request
import urllib.error

def api_response_to_csv(url: str, output_path: str) -> int:
    """REST API 엔드포인트에서 JSON을 가져와 CSV로 기록합니다."""
    try:
        req = urllib.request.Request(url, headers={"Accept": "application/json"})
        with urllib.request.urlopen(req, timeout=30) as resp:
            if resp.status != 200:
                print(f"Error: API returned status {resp.status}")
                return 0
            body = resp.read().decode("utf-8")
    except urllib.error.URLError as exc:
        print(f"Error: could not reach {url}: {exc.reason}")
        return 0

    try:
        records = json.loads(body)
    except json.JSONDecodeError as exc:
        print(f"Error: API returned invalid JSON: {exc.msg}")
        return 0

    if not isinstance(records, list) or not records:
        print("Error: expected a non-empty JSON array from the API")
        return 0

    with open(output_path, "w", newline="", encoding="utf-8") as cf:
        writer = csv.DictWriter(cf, fieldnames=records[0].keys())
        writer.writeheader()
        writer.writerows(records)

    return len(records)

rows = api_response_to_csv(
    "https://api.internal.example.com/v2/deployments?status=completed",
    "completed_deployments.csv",
)
print(f"Exported {rows} deployments to CSV")
참고:위 예제는 스크립트 의존성을 없애기 위해 표준 라이브러리의 urllib을 사용합니다.requests가 설치되어 있다면,urllib 섹션을 resp = requests.get(url, timeout=30); records = resp.json()으로 교체하세요 — 나머지 CSV 쓰기 코드는 동일하게 유지됩니다.

커맨드라인 JSON to CSV 변환

때로는 터미널에서 한 줄짜리 명령만 필요할 때가 있습니다. Python의 -c 플래그를 사용하면 스크립트 파일을 만들지 않고도 빠르게 변환할 수 있습니다. 더 복잡한 변환은 먼저 jq를 통해 데이터를 재구성한 다음 변환하세요.

bash — 한 줄 json to csv 변환
# Python 한 줄짜리: stdin에서 JSON 읽기, stdout에 CSV 쓰기
cat orders.json | python3 -c "
import json, csv, sys
data = json.load(sys.stdin)
w = csv.DictWriter(sys.stdout, fieldnames=data[0].keys())
w.writeheader()
w.writerows(data)
"

# 파일로 출력 저장
cat orders.json | python3 -c "
import json, csv, sys
data = json.load(sys.stdin)
w = csv.DictWriter(sys.stdout, fieldnames=data[0].keys())
w.writeheader()
w.writerows(data)
" > orders.csv
bash — argparse를 사용한 독립형 CLI 스크립트
# json2csv.py로 저장하고 실행: python3 json2csv.py input.json -o output.csv
python3 -c "
import json, csv, argparse, sys

parser = argparse.ArgumentParser(description='Convert JSON array to CSV')
parser.add_argument('input', help='Path to JSON file')
parser.add_argument('-o', '--output', default=None, help='Output CSV path (default: stdout)')
parser.add_argument('-d', '--delimiter', default=',', help='CSV delimiter')
args = parser.parse_args()

with open(args.input) as f:
    data = json.load(f)

out = open(args.output, 'w', newline='') if args.output else sys.stdout
writer = csv.DictWriter(out, fieldnames=data[0].keys(), delimiter=args.delimiter)
writer.writeheader()
writer.writerows(data)
if args.output:
    out.close()
    print(f'Wrote {len(data)} rows to {args.output}', file=sys.stderr)
" "$@"
bash — 복잡한 변환에 jq + csvkit 사용
# csvkit 설치: pip install csvkit

# jq는 필드를 선택하고 평탄화, in2csv는 CSV 형식 처리
cat api_response.json | jq '[.[] | {id: .order_id, customer: .customer.name, total}]' | in2csv -f json > orders.csv

# Miller (mlr)도 JSON-to-CSV의 또 다른 옵션
mlr --json2csv cat orders.json > orders.csv

Miller (mlr)는 Python 런타임 없이도 JSON, CSV, TSV를 일급 형식으로 처리하는 독립 실행 파일입니다. --json2csv 플래그는 JSON 입력을 한 번의 패스로 CSV로 변환하며, 같은 명령에서 Miller 동사를 연결하여 출력 전에 열을 필터, 정렬, 이름 변경할 수 있습니다. macOS에서는 Homebrew(brew install miller) 또는 Linux 패키지 관리자로 설치합니다. Python 환경을 구동하지 않고 빠른 JSON-to-CSV 변환이 필요한 CI 파이프라인에서 특히 유용합니다.

고성능 대안 — pyarrow를 사용한 pandas

수천만 행 규모의 데이터셋에는 pyarrow 백엔드를 사용한 pandas가 기본값보다 훨씬 빠르게 읽고 씁니다. C 기반 Arrow 엔진은 Python의 행 단위 csv 모듈보다 열 형식 데이터를 더 효율적으로 처리합니다. API는 동일하게 유지됩니다 — engine 파라미터만 설정하면 됩니다.

bash — pyarrow 설치
pip install pyarrow
Python 3.8+ — 빠른 CSV 쓰기를 위해 pyarrow를 사용한 pandas
import pandas as pd

# pyarrow 엔진으로 JSON 읽기 (대용량 파일에서 더 빠른 파싱)
df = pd.read_json("sensor_readings.json", engine="pyarrow")

# to_csv에는 engine 파라미터가 없지만, 읽기와 쓰기 사이의
# DataFrame 작업은 pyarrow의 열 레이아웃의 이점을 받습니다
df.to_csv("sensor_readings.csv", index=False)

# 대용량 내보내기의 경우 CSV 대신 Parquet 쓰기 고려
# — 이진 형식, 5-10배 더 작고, 타입 보존
df.to_parquet("sensor_readings.parquet", engine="pyarrow")

수백 MB 이상의 JSON을 처리하고 최종 소비자가 Parquet를 허용한다면 CSV를 완전히 건너뛰세요. Parquet는 더 작고, 열 타입을 보존하며, Redshift와 BigQuery 모두 기본으로 로드합니다. CSV는 손실이 있는 형식입니다 — 모든 값이 문자열이 됩니다.

구문 강조와 함께 터미널 출력

rich 라이브러리는 터미널에서 테두리, 정렬, 색상으로 테이블을 렌더링합니다 — 출력 파일을 열지 않고도 개발 중에 변환을 미리 보는 데 유용합니다.

bash — rich 설치
pip install rich
Python 3.8+ — rich로 터미널에서 CSV 출력 미리보기
import json
from rich.console import Console
from rich.table import Table

json_string = """
[
  {"hostname": "web-prod-1", "cpu_percent": 72.3, "memory_mb": 3840, "uptime_hours": 720},
  {"hostname": "web-prod-2", "cpu_percent": 45.1, "memory_mb": 2560, "uptime_hours": 168},
  {"hostname": "db-replica-1", "cpu_percent": 91.7, "memory_mb": 7680, "uptime_hours": 2160}
]
"""

records = json.loads(json_string)
console = Console()

table = Table(title="Server Metrics Preview", show_lines=True)
for key in records[0]:
    table.add_column(key, style="cyan" if key == "hostname" else "white")

for row in records:
    table.add_row(*[str(v) for v in row.values()])

console.print(table)
# 터미널에서 테두리가 있는 색상 강조 테이블을 렌더링합니다
주의:Rich는 터미널 표시용입니다. CSV 파일 생성에 사용하지 마세요 — ANSI 이스케이프 코드가 추가되어 출력이 손상됩니다. csv.DictWriter DataFrame.to_csv()로 파일에 쓰고, rich는 미리 보기용으로만 사용하세요.

대용량 JSON 파일 처리

json.load() 는 전체 파일을 메모리에 읽습니다. 200 MB JSON 파일의 경우 ~200 MB의 원시 텍스트와 Python 객체 오버헤드 — 쉽게 500 MB+ 이상의 힙 사용량이 됩니다. 100 MB 이상 파일에는 ijson으로 입력을 스트리밍하고 CSV 행을 진행하면서 기록하세요.

bash — ijson 설치
pip install ijson

ijson으로 JSON 배열을 CSV로 스트리밍

Python 3.8+ — 일정한 메모리로 대용량 JSON 배열을 CSV로 스트리밍
import ijson
import csv

def stream_json_to_csv(json_path: str, csv_path: str) -> int:
    """모두 메모리에 로드하지 않고 대용량 JSON 배열을 CSV로 변환합니다."""
    with open(json_path, "rb") as jf, open(csv_path, "w", newline="", encoding="utf-8") as cf:
        # ijson.items는 최상위 배열의 각 요소를 한 번에 하나씩 yield합니다
        records = ijson.items(jf, "item")

        first_record = next(records)
        fieldnames = list(first_record.keys())

        writer = csv.DictWriter(cf, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerow(first_record)

        count = 1
        for record in records:
            writer.writerow(record)
            count += 1

    return count

rows = stream_json_to_csv("clickstream_2026_03.json", "clickstream_2026_03.csv")
print(f"Streamed {rows} records to CSV")

NDJSON / JSON Lines — 줄당 하나의 객체

NDJSON(줄 구분 JSON), JSON Lines 또는 .jsonl이라고도 불리는 형식은 감싸는 배열 없이 줄당 하나의 유효한 JSON 객체를 저장합니다. 이 형식은 로그 파이프라인, 이벤트 스트림(Kafka, Kinesis), Elasticsearch 및 BigQuery와 같은 서비스의 대량 내보내기에서 일반적입니다. 각 줄이 독립적인 JSON 객체이므로 파일 핸들에서 일반 Python for 루프로 NDJSON 파일을 처리할 수 있습니다 — ijson 라이브러리가 필요 없습니다. 파일 크기에 관계없이 메모리가 일정하게 유지되어, 소스 데이터가 이미 JSON Lines 형식일 때 가장 간단한 스트리밍 방법입니다.

Python 3.8+ — NDJSON을 줄 단위로 CSV로 변환
import json
import csv

def ndjson_to_csv(ndjson_path: str, csv_path: str) -> int:
    """줄 구분 JSON 파일을 한 번에 한 줄씩 CSV로 변환합니다."""
    with open(ndjson_path, encoding="utf-8") as nf:
        first_line = nf.readline()
        first_record = json.loads(first_line)
        fieldnames = list(first_record.keys())

        with open(csv_path, "w", newline="", encoding="utf-8") as cf:
            writer = csv.DictWriter(cf, fieldnames=fieldnames)
            writer.writeheader()
            writer.writerow(first_record)

            count = 1
            for line in nf:
                line = line.strip()
                if not line:
                    continue
                try:
                    record = json.loads(line)
                    writer.writerow(record)
                    count += 1
                except json.JSONDecodeError:
                    continue  # 잘못된 줄 건너뛰기

    return count

rows = ndjson_to_csv("access_log.ndjson", "access_log.csv")
print(f"Converted {rows} log entries to CSV")
참고:JSON 파일이 100 MB를 초과하면 스트리밍으로 전환하세요. json.load()로 로드된 1 GB JSON 배열은 Python 객체 오버헤드로 인해 3–5 GB의 RAM을 소비할 수 있습니다. ijson을 사용하면 파일 크기에 관계없이 메모리가 일정하게 유지됩니다. 소규모 파일의 빠른 변환이 필요하다면 JSON to CSV 변환기에 붙여넣으세요.

흔한 실수

open()에서 newline='' 누락 — Windows에서 빈 행 발생

문제: csv 모듈은 \r\n 줄 끝을 씁니다. newline='' 없이는 Python의 텍스트 모드가 Windows에서 \r을 하나 더 추가하여 이중 간격 출력이 됩니다.

해결책: CSV 쓰기용 파일을 열 때 항상 newline=''을 전달하세요. macOS/Linux에서는 무해합니다.

Before · Python
After · Python
with open("output.csv", "w") as f:
    writer = csv.DictWriter(f, fieldnames=columns)
    writer.writeheader()
    writer.writerows(data)
# Windows에서 모든 데이터 행 사이에 빈 행 발생
with open("output.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=columns)
    writer.writeheader()
    writer.writerows(data)
# 모든 플랫폼에서 깔끔한 출력
pandas to_csv()에서 index=False 잊기

문제: index=False 없이는 pandas가 원래 JSON에 없었던 데이터로 CSV를 오염시키는 자동 증가 행 번호 열(0, 1, 2, ...)을 앞에 추가합니다.

해결책: to_csv()에 index=False를 전달하세요. 인덱스 열이 실제로 필요하다면 df.index.name = 'row_num'으로 명시적으로 이름을 지정하세요.

Before · Python
After · Python
df = pd.read_json("events.json")
df.to_csv("events.csv")
# CSV에 이름 없는 추가 열 생성: ,event_id,timestamp,...
# 앞의 쉼표가 많은 CSV 파서를 깨뜨림
df = pd.read_json("events.json")
df.to_csv("events.csv", index=False)
# 깔끔한 CSV: event_id,timestamp,...
키가 일관되지 않은 레코드에서 records[0].keys() 사용

문제: JSON 객체의 키가 다를 경우(일부 레코드에 선택적 필드 존재), 첫 번째 레코드의 키를 fieldnames로 사용하면 나중에 나타나는 레코드에만 있는 열이 자동으로 누락됩니다.

해결책: DictWriter를 만들기 전에 모든 레코드에서 고유 키를 수집하세요.

Before · Python
After · Python
records = json.load(f)
writer = csv.DictWriter(out, fieldnames=records[0].keys())
# records[2]에만 나타나는 "discount" 필드 누락
records = json.load(f)
all_keys = list(dict.fromkeys(k for r in records for k in r))
writer = csv.DictWriter(out, fieldnames=all_keys, restval="")
# 모든 레코드의 모든 키가 열로 포함됨
평탄화 없이 중첩 딕셔너리를 CSV에 직접 쓰기

문제: csv.DictWriter는 중첩 딕셔너리에 str()을 호출하여 실제 데이터가 아닌 "{'city': 'Portland'}"와 같은 Python repr이 포함된 열을 만들어냅니다.

해결책: 먼저 pd.json_normalize() 또는 커스텀 평탄화 함수를 사용하여 중첩 객체를 평탄화하세요.

Before · Python
After · Python
records = [{"id": "evt_1", "meta": {"source": "web", "region": "us-west"}}]
writer = csv.DictWriter(f, fieldnames=["id", "meta"])
writer.writerows(records)
# meta 열에 포함됨: {'source': 'web', 'region': 'us-west'}
import pandas as pd
records = [{"id": "evt_1", "meta": {"source": "web", "region": "us-west"}}]
df = pd.json_normalize(records, sep="_")
df.to_csv("events.csv", index=False)
# 열: id, meta_source, meta_region

csv.DictWriter vs pandas — 빠른 비교

방법
중첩 JSON
커스텀 타입
스트리밍
의존성
설치 필요 여부
csv.DictWriter
✗ (수동 평탄화)
✓ (행 단위)
없음
아니오 (stdlib)
csv.writer
✓ (행 단위)
없음
아니오 (stdlib)
pd.DataFrame.to_csv()
✗ (평탄 구조만)
✓ (dtype 경유)
pandas + numpy
pip install
pd.json_normalize() + to_csv()
✓ (dtype 경유)
pandas + numpy
pip install
csv.writer + json_flatten
flatten_json
pip install
jq + csvkit (CLI)
✓ (jq 경유)
N/A
jq, csvkit
시스템 설치

의존성이 없어야 하고, JSON이 평탄하며, 제한된 환경(CI 컨테이너, Lambda 함수, 내장 Python)에서 실행되는 경우 csv.DictWriter를 사용하세요. JSON이 중첩되어 있거나, 내보내기 전에 데이터를 변환하거나 필터링해야 하거나, 이미 pandas 워크플로에 있다면 pd.json_normalize() + to_csv()를 사용하세요. 메모리에 올라가지 않는 파일에는 일정한 메모리 스트리밍을 위해 ijson csv.DictWriter를 함께 사용하세요.

코드 없이 빠르게 변환하려면 ToolDeck의 JSON to CSV 변환기를 사용하면 Python 설정 없이 처리할 수 있습니다.

자주 묻는 질문

pandas 없이 Python에서 JSON을 CSV로 변환하려면 어떻게 하나요?

내장 json 및 csv 모듈을 사용하세요. json.load()로 JSON 파일을 딕셔너리 목록으로 파싱하고, 첫 번째 딕셔너리의 키에서 fieldnames를 추출한 뒤, csv.DictWriter를 만들고 writeheader()와 writerows()를 호출합니다. 이 방법은 외부 의존성이 전혀 없으며 모든 Python 3.x 환경에서 작동합니다. DataFrame 할당 오버헤드가 없어 소규모 파일에서는 pandas보다 빠릅니다. JSON 객체의 키가 레코드마다 다를 경우, fieldnames로 전달하기 전에 dict.fromkeys(k for r in records for k in r)로 모든 고유 키를 수집하여 누락 열이 생기지 않도록 하세요.

Python
import json
import csv

with open("orders.json") as f:
    records = json.load(f)

with open("orders.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=records[0].keys())
    writer.writeheader()
    writer.writerows(records)

CSV로 변환할 때 중첩 JSON을 어떻게 처리하나요?

평탄한 JSON 배열은 CSV 행으로 직접 매핑되지만, 중첩 객체는 먼저 평탄화가 필요합니다. pandas에서는 pd.json_normalize()가 이를 자동으로 처리합니다 — 중첩 키를 점 구분자로 연결합니다(예: "address.city"). pandas 없이는 딕셔너리를 순회하며 구분자로 키를 연결하는 재귀 함수를 작성합니다. 여러 단계로 깊게 중첩된 구조는 json_normalize가 한 번에 처리합니다. sep 파라미터는 키 세그먼트 간 연결 문자를 제어하는데, SQL 임포트 및 스프레드시트 수식 호환성을 위해 기본 점 대신 밑줄이 더 안전합니다.

Python
import pandas as pd

nested_data = [
    {"id": "ord_91a3", "customer": {"name": "김지은", "email": "kim.jieun@example.com"}},
]
df = pd.json_normalize(nested_data, sep="_")
# 열: id, customer_name, customer_email
df.to_csv("flat_orders.csv", index=False)

Windows에서 CSV의 데이터 행 사이에 빈 행이 생기는 이유는 무엇인가요?

csv 모듈은 기본적으로 \r\n 줄 끝을 사용합니다. Windows에서 텍스트 모드로 파일을 열면 \r이 하나 더 추가되어 \r\r\n이 되고, 이는 빈 행으로 표시됩니다. 해결책은 open()에 항상 newline=""을 전달하는 것입니다. 이렇게 하면 Python이 줄 끝을 변환하지 않고 csv 모듈이 처리하게 됩니다. 이 패턴은 운영 체제에 관계없이 필요합니다 — macOS와 Linux에서는 무해하고 Windows에서는 필수입니다. Python 문서에서도 CSV 쓰기를 위한 파일 열기의 올바른 방법으로 csv 모듈 섹션에 명시적으로 설명하고 있습니다.

Python
# 잘못된 방법 — Windows에서 빈 행 발생
with open("output.csv", "w") as f:
    writer = csv.writer(f)

# 올바른 방법 — newline=""으로 이중 \r 방지
with open("output.csv", "w", newline="") as f:
    writer = csv.writer(f)

기존 CSV 파일에 JSON 레코드를 추가하려면 어떻게 하나요?

파일을 추가 모드("a")로 열고 동일한 fieldnames로 DictWriter를 만드세요. 헤더 행이 이미 존재하므로 writeheader()는 건너뜁니다. pandas에서는 to_csv(mode="a", header=False)를 사용합니다. 데이터가 잘못된 열에 들어가지 않도록 열 순서가 기존 파일과 일치하는지 확인하세요. 기존 파일의 열 순서가 확실하지 않다면, 먼저 csv.DictReader로 파일을 열고 fieldnames 속성에서 필드명을 읽은 후 쓰기용 writer를 만드세요.

Python
import csv

new_records = [
    {"order_id": "ord_f4c1", "total": 89.50, "status": "shipped"},
]

with open("orders.csv", "a", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["order_id", "total", "status"])
    writer.writerows(new_records)

Python에서 대용량 JSON 파일을 CSV로 변환하는 가장 빠른 방법은 무엇인가요?

500 MB 미만 파일에는 pd.read_json() 후 to_csv()가 가장 빠른 단일 호출 방법입니다 — pandas는 내부적으로 최적화된 C 코드를 사용합니다. 500 MB 이상 파일에는 ijson을 사용하여 JSON 레코드를 스트리밍하고 csv.DictWriter로 행 단위로 CSV에 기록합니다. 이렇게 하면 파일 크기에 관계없이 메모리 사용량이 일정하게 유지됩니다. NDJSON 파일(줄당 하나의 JSON 객체)의 경우 ijson이 전혀 필요 없습니다 — 파일 핸들에서 일반 Python for 루프로 각 줄을 독립적으로 처리하여 서드파티 라이브러리 없이 일정한 메모리를 유지합니다.

Python
# 메모리에 올라가는 파일에 적합한 빠른 방법
import pandas as pd
df = pd.read_json("large_dataset.json")
df.to_csv("large_dataset.csv", index=False)

# 메모리에 올라가지 않는 파일을 위한 스트리밍
import ijson, csv
with open("huge.json", "rb") as jf, open("huge.csv", "w", newline="") as cf:
    records = ijson.items(jf, "item")
    first = next(records)
    writer = csv.DictWriter(cf, fieldnames=first.keys())
    writer.writeheader()
    writer.writerow(first)
    for record in records:
        writer.writerow(record)

Python에서 파일 대신 stdout에 CSV를 출력할 수 있나요?

네. csv.writer() 또는 csv.DictWriter()의 파일 객체로 sys.stdout을 전달하세요. 이는 셸 스크립트에서 출력을 파이프하거나 빠른 디버깅에 유용합니다. pandas에서는 to_csv(sys.stdout, index=False)를 호출하거나 to_csv(None)으로 출력할 문자열을 얻을 수 있습니다. 임시 파일이 필요 없습니다. Windows의 stdout에 쓸 때는 stdout이 기본적으로 텍스트 모드로 열리기 때문에 이중 캐리지 리턴 문제를 피하기 위해 먼저 sys.stdout.reconfigure(newline="")을 호출하세요.

Python
import csv
import sys
import json

data = json.loads('[{"host":"web-1","cpu":72.3},{"host":"web-2","cpu":45.1}]')
writer = csv.DictWriter(sys.stdout, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)
# host,cpu
# web-1,72.3
# web-2,45.1

관련 도구

MS
Maria SantosBackend Developer

Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.

PS
Priya Sharma기술 검토자

Priya is a data scientist and machine learning engineer who has worked across the full Python data stack — from raw data ingestion and cleaning to model deployment and monitoring. She is passionate about reproducible research, Jupyter-based workflows, and the practical engineering side of ML. She writes about NumPy, Pandas, data serialisation, and the Python patterns that make data pipelines reliable at scale.