JSON to Excel: 네 가지 접근과 각각의 충실도 손실 지점
핵심 요약: 중첩 구조, 고르지 않은 필드, 배열의 배열이 JSON-to-스프레드시트 변환이 깨지는 지점입니다. 결과까지의 시간 순으로 정렬한 네 가지 접근과 각각의 절충을 다룹니다.

핵심부터 말하면, 평평한 객체 배열이나 배열을 감싼 객체는 온라인 도구로 가장 빠르게 변환하고, 데이터를 매주 갱신한다면 Power Query, 정리할 필드가 많다면 pandas, 변환이 파이프라인에 합류해야 할 때만 Node 스크립트를 쓰세요. 그러나 중첩 데이터나 고르지 않은 키를 다루기 전에 "한 행"의 의미를 먼저 결정하세요.
변환 자체는 어렵지 않습니다. 어려운 것은 나중에 숫자가 텍스트가 되었고 여러 중첩 수준이 하나로 접혔다는 사실을 발견하는 일입니다. 방법을 고르기 전에 데이터 구조를 읽으면 재작업을 피할 수 있습니다.
1. 먼저 어떤 종류의 JSON인지 파악하기
파일을 열고 가장 바깥 계층을 보세요:
| 구조 | 모양 | 변환 난이도 |
|---|---|---|
| 평평한 객체 배열 | [{"a":1,"b":2},{...}] |
가장 쉬움 — 레코드당 한 행 |
| 배열을 감싼 객체 | {"data":[{...}],"total":100} |
쉬움 — data 계층을 취함 |
| 중첩 객체 | {"user":{"name":"x"}} |
중간 — 평탄화할지 문자열로 합칠지 결정 |
| 배열의 배열 | {"tags":["a","b"]} |
고통 — 한 행에 맞지 않아 테이블 분할 필요 |
| 고르지 않은 키 | 레코드마다 다른 필드 | 고통 — 열이 대부분 비게 됨 |
앞의 두 가지는 거의 100퍼센트 성공률로 직접 변환됩니다. 뒤의 세 가지는 "한 행"의 의미를 먼저 결정해야 하며, 아니면 결과 시트가 쓸 수 없게 됩니다.
2. 접근 1: 온라인 도구(1분)
JSON to table을 열고 데이터를 붙여 넣은 뒤 구분자와 헤더를 고르고 CSV로 내보내 Excel에서 엽니다. 수십에서 수천 행의 잘 구조화된 데이터에 적합합니다.
반드시 주의할 세 가지:
- CSV를 받지 xlsx를 받지 않습니다. Excel이 CSV를 열 때 시스템 로케일에서 인코딩을 추측하므로 중국어 텍스트가 깨질 수 있습니다. "Data → From Text/CSV"를 쓰고 UTF-8을 명시적으로 고르는 것이 더 신뢰할 만합니다.
0으로 시작하는 식별자는 0을 잃습니다."007"은 CSV에서 텍스트이지만 Excel이 자동으로 숫자 7로 변환합니다. 가져오기 중에 해당 열을 텍스트로 설정하거나 JSON에서 접두사를 붙이세요.- 긴 숫자는 과학적 표기로 바뀝니다. 주민등록번호나 15자리를 넘는 주문 번호는 정밀도를 잃습니다. 그런 필드는 JSON에서 이미 문자열이어야 하고, 가져온 후에도 텍스트 형식을 유지해야 합니다.
3. 접근 2: Excel 내장 Power Query(재사용 가능)
데이터를 매주 갱신한다면 Excel에 내장된 Power Query를 쓰세요. 한 번 설정하고 "Refresh"만 클릭합니다:
- "Data" → "Get Data" → "From File" → "From JSON"
- 파일을 고르면 Power Query 편집기에 들어가
List/Record구조를 봅니다. - 열 헤더 오른쪽의 확장 아이콘을 클릭해 중첩 필드를 한 단계씩 확장합니다.
- 열 이름과 타입이 맞으면 "Close & Load".
장점은 중첩을 계층별로 여러 열로 확장할 수 있고 타입을 수동으로 지정할 수 있다는 것입니다. 비용은 첫 설정에서 List와 Record의 차이를 이해해야 한다는 것입니다. List는 배열, Record는 객체이며 확장 방향이 다릅니다.
4. 접근 3: Python + pandas(필드가 많고 정리가 필요)
import pandas as pd
# Object wrapping an array: first reach the list level
rows = pd.read_json('data.json')['data']
# Expand nested fields into multiple columns
frame = pd.json_normalize(rows, sep='_')
frame.to_excel('out.xlsx', index=False)
json_normalize의 핵심 파라미터는 sep이며, 중첩 필드를 확장한 뒤의 열 이름 스타일을 결정합니다. user_name이 Excel에서 user.name보다 쓰기 쉽습니다. tags 같은 배열의 배열에는 추가 explode 단계가 필요합니다:
frame = frame.explode('tags')
이 단계는 한 행을 여러 행으로 나누며, 일대다 관계를 보존하는 유일한 방법입니다. 비용은 나머지 필드가 중복된다는 것입니다.
5. 접근 4: Node 스크립트(파이프라인에 합류해야 할 때)
import { readFileSync, writeFileSync } from 'node:fs';
const rows = JSON.parse(readFileSync('data.json', 'utf8'));
const headers = [...new Set(rows.flatMap((row) => Object.keys(row)))];
const escapeCell = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`;
const lines = [
headers.join(','),
...rows.map((row) => headers.map((key) => escapeCell(row[key])).join(',')),
];
writeFileSync('out.csv', '\uFEFF' + lines.join('\n'), 'utf8');
생략하지 말아야 할 두 디테일: Excel이 UTF-8을 인식하도록 시작에 \uFEFF(BOM)를 쓰고, 셀 안의 큰따옴표를 ""로 이스케이프하세요. 그렇지 않으면 열이 밀립니다.
6. 변환이 항상 잃는 세 가지
| 잃는 것 | 증상 | 해결 |
|---|---|---|
| 타입 정보 | 숫자와 불리언이 텍스트나 TRUE/FALSE가 됨 |
가져온 후 열 타입을 수동 설정하거나 Power Query에서 지정 |
| 중첩 수준 | 깊은 필드가 [object Object]로 접힘 |
json_normalize나 단계별 확장을 쓰고 String(obj)는 절대 쓰지 말 것 |
| 일대다 관계 | 배열이 단일 셀에 밀어 넣어짐 | explode로 행을 나누거나 별도 관련 테이블을 만듦 |
또한 null과 "필드 없음"은 시트에서 같아 보이지만(둘 다 빈 셀) 업무 영역에서는 다릅니다. 구분해야 할 때는 JSON에서 null을 문자열 "null"로 명시적으로 변환하거나 _has_field 마커 열을 더하세요.
7. 변환 후 검증: 첫 행만 보지 말 것
변환 후 최소 세 가지를 표본 점검하세요:
- 레코드 수: CSV 줄 수가 배열 길이 + 1(헤더)과 같아야 합니다. 줄이 빠졌다면 대개 마지막 줄 바꿈이 삼켜진 것입니다.
- null 비율이 가장 높은 열: 빈 셀이 많으면 키 이름이 고르지 않다는 신호입니다. 원본 JSON이 두 가지 표기(
userId와user_id)를 쓰는지 돌아가 확인하세요. - 가장 긴 행: 중첩 배열이 대개 여기에 떨어집니다. 잘려 나간 것이 없는지 확인하세요.
역방향 작업(Excel/CSV를 JSON으로)이 필요하면 CSV to JSON을 쓰고, 헤더가 키 이름으로 취급된다는 점을 기억하세요. 헤더를 바꾸는 것은 필드 이름을 바꾸는 것입니다.
FAQ
온라인 도구, Power Query, pandas 중 어떻게 고르나?
한 번 변환하는 수십에서 수천 행의 잘 구조화된 데이터에는 온라인 도구를 쓰세요. 매주 갱신되는 데이터에는 Power Query가 한 번 설정하고 한 번 클릭으로 갱신되므로 적합합니다. 정리할 필드가 많으면 json_normalize를 쓰는 pandas를, 변환이 파이프라인에 합류해야 하면 Node 스크립트를 쓰세요.
CSV에서 중국어 텍스트가 왜 깨지나?
내보내기가 xlsx가 아니라 CSV이고 Excel이 시스템 로케일에서 인코딩을 추측하므로 BOM 없는 UTF-8은 깨져 보입니다. 파일 시작에 \uFEFF BOM을 쓰거나 "Data → From Text/CSV"로 열어 UTF-8을 명시적으로 고르세요.
중첩 객체를 평탄화해야 하나 추가 행으로 분할해야 하나?
문자열로 붙이지 말고 계층별로 평탄화하세요. Power Query는 중첩 필드를 열로 확장하고, pandas의 sep를 쓴 json_normalize도 같은 일을 합니다. 배열의 배열에는 한 행을 여러 행으로 나누는 추가 explode 단계가 필요합니다. 깊은 객체를 String(obj)로 넘기면 [object Object]만 얻습니다.