Skip to content
블로그로 돌아가기
튜토리얼

CSV와 JSON 변환 온라인 가이드: 방법, 함정, 코드 예제

Python, 자바스크립트, CLI 도구로 CSV를 JSON으로(그리고 반대로) 변환하는 웹 가이드. 인코딩 함정, 타입 강제 변환, 대용량 파일 스트리밍까지 다룹니다.

12분 소요

CSV와 JSON 변환 가이드: 방법, 함정, 모범 사례

운영 팀에서 CSV 내보내기 파일을 보냈는데 API는 JSON을 요구합니다. 파일을 열어 보면 쉼표로 구분된 10,000행이 펼쳐집니다. 데이터 손실 없이 CSV를 JSON으로 변환하는 가장 빠른 방법은 무엇일까요?

네 가지 변환 방식(브라우저 도구, 자바스크립트, Python, CLI), 반대 방향(JSON → CSV), 데이터를 조용히 망가뜨리는 다섯 가지 함정, 메모리에 담기 힘든 큰 파일의 처리 방법을 다룹니다.

CSV와 JSON: 언제 어느 쪽을 써야 할까?

변환에 들어가기 전에 각 포맷이 어떤 용도에 강한지 이해하면 도움이 됩니다.

항목CSVJSON
구조평면 테이블(행과 열)중첩된 계층형 구조(객체, 배열)
데이터 타입모든 값이 문자열문자열, 숫자, 불리언, null
사람이 읽기 좋은 정도스프레드시트 친화적개발자 친화적
주요 용도데이터 내보내기/가져오기, 리포트, ETLAPI, 설정 파일, NoSQL 저장소
파일 크기더 작음(키 이름이 반복되지 않음)더 큼(레코드마다 키 이름이 반복됨)
스키마암묵적(헤더 행)명시적(또는 JSON Schema 사용)

경험적 원칙: 데이터가 테이블 형태이고 사용처가 스프레드시트나 데이터 파이프라인이면 CSV, 계층 구조가 있거나 사용처가 API면 JSON을 쓰세요. 변환 결과는 JSON 포맷터로 검증해 구조 문제를 일찍 잡을 수 있습니다.

설정에 JSON5나 JSONC 같은 관대한 JSON 형식을 쓴다면 문법 차이와 도구 체인은 JSON5·JSONC 포매팅 가이드에서 다룹니다.

CSV를 JSON으로 변환하는 4가지 방법

방법 1 — 브라우저 기반 도구

일회성 변환에는 브라우저 기반 접근이 가장 빠릅니다. CSV를 온라인 변환기에 붙여넣어 JSON을 얻고, JSON 포맷터로 구조를 확인하세요.

데이터가 브라우저를 벗어나지 않습니다. 업로드도 서버 처리도 없습니다. 내부 데이터, 내보내기 파일에 섞인 API 키, 외부로 보내고 싶지 않은 자료를 다룰 때 유용합니다.

적합한 경우: 10 MB 미만의 작은 파일, 빠른 일회성 변환, 기술 배경이 없는 팀 구성원.

방법 2 — 자바스크립트 / Node.js

브라우저(바닐라 JS):

function csvToJson(csv) {
  const lines = csv.trim().split('\n');
  const headers = lines[0].split(',').map(h => h.trim());

  return lines.slice(1).map(line => {
    const values = line.split(',');
    return headers.reduce((obj, header, i) => {
      obj[header] = values[i]?.trim() ?? '';
      return obj;
    }, {});
  });
}

const csv = `name,age,city
Alice,30,New York
Bob,25,London`;

console.log(JSON.stringify(csvToJson(csv), null, 2));

따옴표가 없는 단순한 CSV에는 이 코드로 충분합니다. 값에 쉼표가 들어 있거나, 필드 안에 줄바꿈이 있거나, 따옴표로 감싼 문자열이 섞인 운영 환경이라면 제대로 된 파서를 쓰세요.

Node.js(csv-parser + 스트림):

import { createReadStream } from 'fs';
import { parse } from 'csv-parse';

const records = [];

createReadStream('data.csv')
  .pipe(parse({ columns: true, trim: true, skip_empty_lines: true }))
  .on('data', (row) => records.push(row))
  .on('end', () => {
    console.log(JSON.stringify(records, null, 2));
  });

columns: true 옵션은 첫 번째 행을 키로 사용합니다. trim 옵션은 값 앞뒤 공백을 제거합니다. 따옴표로 감싼 필드, 이스케이프된 쉼표, 여러 줄에 걸친 값도 올바르게 처리합니다.

방법 3 — Python

표준 라이브러리(의존성 없음):

import csv
import json

with open('data.csv', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    rows = list(reader)

with open('data.json', 'w', encoding='utf-8') as f:
    json.dump(rows, f, indent=2, ensure_ascii=False)

csv.DictReader는 헤더 행을 키로 사용해 각 행을 딕셔너리로 매핑합니다. ensure_ascii=False 플래그는 유니코드 문자(한국어, 일본어, 악센트 문자)를 \uXXXX로 이스케이프하지 않고 원본 그대로 보존합니다.

pandas(데이터 과학자를 위한 한 줄 코드):

import pandas as pd

df = pd.read_csv('data.csv')
df.to_json('data.json', orient='records', indent=2, force_ascii=False)

언제 어느 쪽을 써야 할까요?

  • csv + json: 가벼운 스크립트, Lambda 함수, 최소한의 의존성만 원하는 컨테이너 환경에 적합합니다.
  • pandas: 변환 전에 데이터를 정리, 필터링, 변형까지 해야 할 때 적합합니다. 포맷 변환 외에 더 많은 작업이 있다면 pandas를 import하는 오버헤드는 충분히 감수할 만합니다.

방법 4 — CLI 도구

셸 스크립트와 자동화 파이프라인에서는 다음과 같은 도구가 유용합니다.

csvkit:

# 설치: pip install csvkit
csvjson data.csv > data.json

Miller (mlr):

# 설치: brew install miller (macOS) 또는 apt install miller (Ubuntu)
mlr --csv --json cat data.csv > data.json

jq와 파이프로 연결해 필터링:

# 한 파이프라인에서 변환과 필터링을 동시에 수행
csvjson data.csv | jq '[.[] | select(.age | tonumber > 25)]'

Miller는 CSV, JSON, TSV를 비롯한 여러 포맷을 기본적으로 처리하기 때문에 특히 강력합니다. 변환 과정에서 데이터를 변형할 수도 있습니다.

# CSV를 JSON으로 변환하면서 필드 이름을 바꾸고, 계산된 필드를 추가
mlr --csv --json rename name,fullName then put '$age_group = ($age > 30) ? "senior" : "junior"' data.csv

JSON을 CSV로: 반대 방향 처리하기

JSON을 CSV로 변환할 때는 반대 방향에는 없던 과제들이 등장합니다.

중첩된 객체 평탄화

CSV는 본질적으로 평면 구조입니다. JSON에 중첩된 객체가 있다면 평탄화 전략이 필요합니다.

{
  "name": "Alice",
  "address": {
    "city": "New York",
    "zip": "10001"
  }
}

이 구조는 다음과 같이 변환됩니다.

nameaddress.cityaddress.zip
AliceNew York10001

점 표기법 관례(address.city)가 가장 흔한 접근 방식입니다. Python에서는 다음과 같이 처리합니다.

import pandas as pd

data = [
    {"name": "Alice", "address": {"city": "New York", "zip": "10001"}},
    {"name": "Bob", "address": {"city": "London", "zip": "EC1A"}}
]

df = pd.json_normalize(data)
df.to_csv('output.csv', index=False)
# 컬럼: name, address.city, address.zip

배열 처리

배열 필드는 의사결정이 필요합니다.

전략입력 예시CSV 출력적합한 경우
문자열로 합치기["admin","editor"]admin;editor단순한 목록, 재가져오기 용이
컬럼으로 펼치기["admin","editor"]role_0: admin, role_1: editor길이가 고정된 배열
행으로 펼치기["admin","editor"]두 개의 행, 역할마다 하나씩관계형 분석

하위 사용처에 따라 고르세요. CSV가 다시 데이터베이스로 들어간다면 대개 행으로 펼치는 편이 자연스럽습니다.

타입 정보의 손실

CSV에는 타입 시스템이 없습니다. JSON을 CSV로 변환하면 다음과 같은 일이 벌어집니다.

  • true는 문자열 "true"가 됩니다. 이게 불리언일까요, 문자열일까요?
  • null은 빈 셀이 되어, 빈 문자열 ""과 구분되지 않습니다.
  • 42"42"가 됩니다. 숫자일까요, 문자열일까요?

왕복 정확성(CSV → 처리 → JSON)이 중요하다면 헤더 주석이나 별도 스키마 파일에 타입 규약을 문서로 남기세요.

자주 마주치는 5가지 함정과 회피 방법

다음 이슈들은 데이터를 조용히 망가뜨립니다. 대부분의 튜토리얼은 이 부분을 건너뜁니다.

1. 인코딩 지뢰

문제: 동료가 보낸 CSV를 열었더니 é 대신 é가, 또는 중국어 문자 대신 锟斤拷가 보입니다.

발생 원인: 파일이 어떤 인코딩(Windows-1252, GBK, Shift_JIS)으로 저장되었는데 파서는 UTF-8을 가정하기 때문입니다. 윈도우용 Excel은 종종 CSV를 Windows-1252로 저장하거나 UTF-8 BOM(Byte Order Mark — 파일 앞에 붙는 눈에 보이지 않는 \xEF\xBB\xBF)을 추가합니다.

해결 방법:

# 먼저 인코딩 탐지
import chardet

with open('data.csv', 'rb') as f:
    result = chardet.detect(f.read(10000))
    print(result)  # {'encoding': 'Windows-1252', 'confidence': 0.73}

# 그다음 올바른 인코딩으로 읽기
with open('data.csv', encoding=result['encoding']) as f:
    reader = csv.DictReader(f)
    # ...

Node.js에서는 BOM을 명시적으로 제거합니다.

import { readFileSync } from 'fs';

let content = readFileSync('data.csv', 'utf-8');
// UTF-8 BOM이 있으면 제거
if (content.charCodeAt(0) === 0xFEFF) {
  content = content.slice(1);
}

2. 구분자 혼동

문제: 파서가 여러 필드 대신 거대한 단일 컬럼 하나를 만들어 냅니다.

발생 원인: 유럽의 여러 로케일(프랑스, 독일, 스페인)에서는 쉼표가 소수점 구분 기호로 쓰이기 때문에(예: 3.14 대신 3,14), Excel이 CSV 구분자로 세미콜론(;)을 사용합니다. 탭으로 구분된 파일(.tsv)은 또 다른 변종입니다.

해결 방법: 처음 몇 줄을 샘플링해 구분자를 자동 감지합니다.

import csv

with open('data.csv') as f:
    sample = f.read(8192)
    dialect = csv.Sniffer().sniff(sample, delimiters=',;\t|')
    f.seek(0)
    reader = csv.DictReader(f, dialect=dialect)

3. 앞자리 0이 사라지는 문제

문제: 우편번호 00501501로 바뀝니다. 제품 코드 0077이 됩니다.

발생 원인: 파서(또는 Excel)가 해당 필드를 숫자로 해석해 앞자리 0을 잘라내기 때문입니다. 우편번호, 전화번호, ID 코드에서 특히 위험합니다.

해결 방법: 문자열 타입을 강제합니다. pandas에서는 다음과 같습니다.

df = pd.read_csv('data.csv', dtype={'zip': str, 'product_code': str})

자바스크립트에서는 원본 문자열이 숫자 파싱 결과와 다른지 확인합니다.

function preserveLeadingZeros(value) {
  if (/^0\d+$/.test(value)) return value; // 문자열로 유지
  const num = Number(value);
  return isNaN(num) ? value : num;
}

4. 큰 수의 정밀도 손실

문제: ID 9007199254740993이 JSON에서 9007199254740992로 바뀝니다.

발생 원인: 자바스크립트 Number는 64비트 부동소수점(IEEE 754)입니다. Number.MAX_SAFE_INTEGER(2^53 − 1 = 9007199254740991)를 넘는 정수는 정밀도를 잃습니다. 데이터베이스 ID, Snowflake ID, Twitter/X 게시물 ID 등이 영향을 받습니다.

해결 방법: JSON에서 큰 수를 문자열로 유지하거나, 처리 코드에서 BigInt를 사용합니다.

// 큰 수는 문자열로 보존하며 파싱
function safeParseNumber(value) {
  const num = Number(value);
  if (Number.isInteger(num) && !Number.isSafeInteger(num)) {
    return value; // 정밀도 보존을 위해 문자열로 유지
  }
  return isNaN(num) ? value : num;
}

5. 빈 값의 모호성

문제: CSV에 빈 셀이 있습니다. 변환 후에는 원래 값이 빈 문자열 ""이었는지, null이었는지, 아니면 단순히 누락된 것이었는지 구분할 수 없습니다.

발생 원인: CSV에는 이 세 가지 상태를 구분할 방법이 없습니다. 두 쉼표 사이의 빈 필드(Alice,,30)는 셋 중 어느 것이든 의미할 수 있습니다.

해결 방법: 규약을 정하고 일관되게 적용합니다.

def parse_value(value):
    if value == '':
        return None        # 또는 '' — 하나의 규약을 선택
    if value == 'NULL' or value == 'null':
        return None
    return value

데이터에 NULL, N/A, - 같은 센티넬 값을 쓴다면 문서화하고 명시적으로 처리하세요.

대용량 파일 스트리밍

CSV가 100 MB를 넘기면 파일 전체를 메모리에 올릴 수 없습니다. 스트리밍을 쓰세요.

Node.js(스트림 파이프라인):

import { createReadStream, createWriteStream } from 'fs';
import { parse } from 'csv-parse';
import { Transform } from 'stream';
import { pipeline } from 'stream/promises';

let first = true;
const toJsonArray = new Transform({
  objectMode: true,
  transform(record, encoding, callback) {
    const prefix = first ? '[\n' : ',\n';
    first = false;
    callback(null, prefix + JSON.stringify(record));
  },
  flush(callback) {
    callback(null, '\n]');
  }
});

await pipeline(
  createReadStream('large.csv'),
  parse({ columns: true, trim: true }),
  toJsonArray,
  createWriteStream('large.json')
);

Python(제너레이터):

import csv
import json

def csv_rows(path):
    with open(path, encoding='utf-8') as f:
        reader = csv.DictReader(f)
        for row in reader:
            yield row

# JSON Lines 포맷으로 스트리밍(한 줄에 JSON 객체 하나)
with open('large.jsonl', 'w', encoding='utf-8') as out:
    for row in csv_rows('large.csv'):
        out.write(json.dumps(row, ensure_ascii=False) + '\n')

파일이 아주 크다면 단일 JSON 배열 대신 JSON Lines(.jsonl) 포맷을 고려하세요. 각 줄이 독립적인 JSON 객체이므로 한 줄씩 처리할 수 있고, 전체를 한꺼번에 파싱할 필요가 없습니다.

자주 묻는 질문

CSV와 JSON의 차이는 무엇인가요?

CSV(Comma-Separated Values, 쉼표로 구분된 값)는 데이터를 평면 테이블 형태로 저장하며, 모든 값이 문자열입니다. JSON(JavaScript Object Notation)은 중첩된 객체, 배열, 타입이 있는 값(문자열, 숫자, 불리언, null)으로 구조화된 데이터를 저장합니다. CSV는 크기가 더 작고 스프레드시트 친화적이며, JSON은 표현력이 더 풍부하고 API 친화적입니다.

자바스크립트에서 CSV를 JSON으로 어떻게 변환하나요?

Node.js에서는 csv-parse 패키지를 씁니다. createReadStream으로 파일을 읽고 parse({ columns: true })에 파이프로 연결해 결과를 수집하세요. 브라우저에서는 FileReader로 파일을 읽어 줄바꿈으로 분리하고, 헤더 행을 키로 각 행을 객체로 매핑합니다.

Python에서 CSV를 JSON으로 어떻게 변환하나요?

표준 라이브러리의 csv.DictReader로 각 행을 딕셔너리로 읽은 뒤 json.dump()로 JSON 배열로 기록합니다. 변환 전에 데이터를 다뤄야 한다면, pandas.read_csv()에 이어 df.to_json(orient='records')를 쓰는 한 줄짜리 대안도 있습니다.

중첩된 JSON도 CSV로 변환할 수 있나요?

네, 다만 평탄화 전략이 필요합니다. 가장 흔한 접근은 점 표기법입니다. address.city 같은 필드가 컬럼 헤더가 됩니다. Python에서는 pandas.json_normalize()가 자동 처리합니다. 배열은 추가 결정이 필요하며, 문자열로 합치거나, 컬럼으로 펼치거나, 행으로 펼치는 방식 중에서 고르세요.

변환 후에 CSV 문자가 깨져 보이는 이유는 무엇인가요?

인코딩 불일치 때문입니다. 파일이 Windows-1252나 GBK로 저장됐는데 파서가 UTF-8을 가정했을 가능성이 높습니다. Python의 chardet 같은 라이브러리로 인코딩을 식별한 뒤 읽을 때 명시적으로 지정하세요. 일부 도구가 자동으로 붙이는 UTF-8 BOM도 함께 확인하세요.

100 MB가 넘는 CSV 파일은 어떻게 다루나요?

파일 전체를 메모리에 올리지 말고 스트리밍을 쓰세요. Node.js에서는 csv-parse를 스트림으로 파이프 연결하고, Python에서는 제너레이터와 csv.DictReader로 반복합니다. 하위 처리를 쉽게 하려면 단일 JSON 배열 대신 JSON Lines(.jsonl) 포맷을 고려하세요.

변환된 JSON이 유효한지 어떻게 확인할 수 있나요?

출력을 JSON 포맷터에 붙여 넣어 문법, 구조, 중첩을 확인하세요. 자동화된 유효성 검사에는 자바스크립트 JSON.parse()나 Python json.loads()를 쓰면 됩니다. 두 함수 모두 잘못된 입력에는 명확한 오류를 던집니다. 스키마 수준 검증이 필요하면 JSON Schema를 정의해 프로그램으로 검증하세요.

핵심 요약

  1. 상황에 맞는 방법을 고르세요. 일회성 작업은 브라우저 도구, 자동화는 코드, 파이프라인은 CLI.
  2. 인코딩을 항상 명시하세요. 기본값에 의존하지 마세요.
  3. 타입을 의도적으로 보존하세요. 앞자리 0, 큰 정수, null 값은 모두 명시적으로 처리해야 합니다.
  4. 큰 파일은 스트리밍하세요. 메모리에 올리지 말고, 아주 큰 데이터셋에는 JSON Lines를 고려하세요.
  5. 출력을 검증하세요. 변환된 JSON을 JSON 포맷터에 넣어 구조 문제를 일찍 잡으세요.