중첩 JSON을 CSV로 평탄화하는 방법: 5가지 전략과 결정 매트릭스
기하학 문제입니다
매번 같은 벽에 부딪힙니다. API가 중첩된 JSON을 반환하는데, Slack 너머의 분석가는 그냥 스프레드시트를 원합니다. mongoexport는 $oid 래퍼와 세 단계의 메타데이터를 함께 내보내고, BigQuery는 평평한 테이블을 기대합니다. 중첩 JSON을 CSV로 평탄화하는 일은 문법 문제가 아니라 기하학 문제입니다. JSON은 나무이고 CSV는 격자이므로, 가지가 어떻게 무너질지를 정하지 않고서는 나무를 격자 안으로 옮길 수 없습니다.
가지를 무너뜨리는 전략은 정확히 다섯 가지입니다. 잘못된 전략을 고르면 Excel에 200 개의 열을 떠넘기거나, Twitter ID의 정밀도를 잃거나, 파이프라인이 의존하는 왕복 변환을 깨뜨립니다. 올바른 전략을 고르면 변환은 한 줄짜리 작업이 됩니다.
| 전략 | 한 줄 요약 | 적합한 용도 |
|---|---|---|
| 점 표기법 | customer.address.city | Excel/Sheets 분석 |
| 밑줄 표기법 | customer_address_city | SQL 친화적 열 |
| 인덱스 배열 | items.0.sku, items.1.sku | 고정 크기 배열 |
| 행 분할 | 자식마다 부모를 반복 | Pandas/BigQuery 분석 |
| Stringify | 한 셀에 "{\"city\":\"Seattle\"}" | 무손실 왕복 변환 |
이 가이드는 각 전략을 차례로 살펴보고, 소비자(Excel, Pandas, BigQuery, Postgres) 기준의 결정 매트릭스를 제공하며, 정답이 직관적이지 않은 네 가지 실제 페이로드를 보여 드립니다. 양방향 변환의 전반적인 개요(파서 라이브러리, 스트리밍, 인코딩 함정)가 필요하시다면 CSV와 JSON 변환 온라인 가이드를 참고하세요.
왜 중첩 JSON은 CSV에 맞지 않는가
JSON은 CSV에 없는 세 가지 구조를 담습니다. 계층은 객체 안의 객체입니다. 순서는 배열입니다. 혼합은 그 조합으로, 객체 배열, 배열을 가진 객체, 배열의 배열입니다. 전형적인 전자상거래 주문은 이 세 가지를 한꺼번에 갖습니다.
CSV에는 정확히 두 개의 차원, 즉 행과 열만 있습니다. “이 열은 자식 셋을 담는다”를 표현할 세 번째 축은 없습니다. 나무에서 격자를 요구하는 순간 무언가는 양보해야 합니다. 자식을 더 많은 열에 펼치거나(items.0.options.0.value 같은 이름을 감수하면서), 더 많은 행에 펼치거나(부모 필드가 반복됨), 한 셀 안에 텍스트로 쑤셔 넣고 더는 구조로 다루지 않거나, 셋 중 하나입니다.
아래의 각 전략은 그 질문에 다르게 답합니다. 어떤 것은 가독성을 지키지만 왕복 변환의 안전성을 잃습니다. 어떤 것은 반대로 합니다. 보편적인 답은 없습니다. 다음에 파일을 읽을 사람에게 답을 맞추세요.
5가지 평탄화 전략 비교
전략 1: 점 표기법 (customer.address.city)
점 표기법은 루트에서 잎까지 따라가며 키를 .로 잇습니다. 중첩된 객체는 잎마다 열 하나가 되고, 경로는 열 이름 안에 인코딩됩니다.
{ "customer": { "address": { "city": "Seattle" } }, "email": "alice@example.com" }
는 다음과 같이 변환됩니다:
customer.address.city,email
Seattle,alice@example.com
Pandas에서는 한 줄이면 됩니다:
import pandas as pd
data = [{"customer": {"address": {"city": "Seattle"}}, "email": "alice@example.com"}]
df = pd.json_normalize(data, sep='.')
df.to_csv("out.csv", index=False)
JavaScript에서는 작은 재귀 함수로 충분합니다:
function flattenDot(obj, prefix = '', acc = {}) {
for (const [k, v] of Object.entries(obj)) {
const key = prefix ? `${prefix}.${k}` : k;
if (v && typeof v === 'object' && !Array.isArray(v)) {
flattenDot(v, key, acc);
} else {
acc[key] = v;
}
}
return acc;
}
장점: 사람이 읽기 쉽고, Pandas의 기본값이며, 원본 경로를 보존합니다. 단점: 열 이름이 길어질 수 있고(Kubernetes 스펙은 spec.template.spec.containers.0.resources.limits.memory 같은 이름을 만들어 냅니다), 실제 키에 .이 들어 있으면 점이 모호해집니다(Google Analytics 4 이벤트 매개변수가 그렇습니다).
전략 2: 밑줄 표기법 (customer_address_city)
같은 발상에 구분자만 다릅니다. .을 _로 바꾸면 결과는 SQL 안전 식별자가 됩니다. SELECT customer_address_city FROM events는 식별자를 따옴표로 감싸지 않고도 동작합니다. BigQuery, Snowflake, Postgres 모두 이쪽을 선호합니다.
import pandas as pd
df = pd.json_normalize(data, sep='_')
점과 밑줄 사이의 결정은 전적으로 다운스트림 도구에 달려 있습니다. Excel 분석가에게는 점이 더 자연스럽게 읽히고, SQL 엔진은 밑줄을 군말 없이 받아들입니다. 인수 하나만 바꾸면 전환됩니다.
장점: SQL 안전 열 이름, BigQuery 식별자 규약 준수, 따옴표가 필요 없습니다. 단점: 키에 _가 포함되어 있으면 여전히 모호함이 남습니다(.보다는 드물지만 가능성은 있습니다).
전략 3: 인덱스 배열 (items.0.sku, items.1.sku)
객체는 키가 고유해서 깔끔하게 평탄화됩니다. 배열은 길이에 상한이 없어 그렇지 못합니다. 인덱스 전략은 배열의 위치를 경로 세그먼트로 다룹니다. items[0]은 items.0이 됩니다.
{ "id": "ord-001", "items": [{"sku": "A"}, {"sku": "B"}] }
는 다음과 같이 변환됩니다:
id,items.0.sku,items.1.sku
ord-001,A,B
이것이 저희 JSON to CSV 변환기의 Flatten 기본 동작입니다. 각 잎이 자기 열을 가지며, 위치는 이름에 기록됩니다.
장점: 모든 값이 자기 셀을 가지고, 위치가 보존되며, 행이 중복되지 않습니다. 단점: 열 개수가 폭발합니다(항목 100 개 = 열 100 개). 배열 길이가 다른 행들은 들쭉날쭉한 테이블을 만들고, 다운스트림 집계가 깨집니다(SUM(items.*.qty)가 불가능합니다).
전략 4: 행 분할 (배열을 여러 행으로)
배열에 맞춰 테이블을 넓히는 대신, 길게 늘립니다. 부모 필드를 배열 원소마다 한 번씩 반복하고, 각 원소가 자기 행이 되게 합니다.
{ "order_id": "ord-001", "customer": "Alice", "items": [{"sku": "A", "qty": 2}, {"sku": "B", "qty": 1}] }
는 다음과 같이 변환됩니다:
order_id,customer,items.sku,items.qty
ord-001,Alice,A,2
ord-001,Alice,B,1
Pandas에서는 한 줄로 분할과 정규화를 동시에 처리합니다:
import pandas as pd
orders = [{"order_id": "ord-001", "customer": "Alice",
"items": [{"sku": "A", "qty": 2}, {"sku": "B", "qty": 1}]}]
df = pd.json_normalize(orders, record_path='items', meta=['order_id', 'customer'])
df.to_csv("orders.csv", index=False)
SQL에서는 UNNEST가 같은 일을 합니다:
SELECT order_id, item.sku, item.qty FROM orders, UNNEST(items) AS item;
장점: Pandas와 BigQuery가 이 모양을 기본으로 처리하고, 집계가 동작하며(GROUP BY order_id), 스키마가 좁게 유지됩니다. 단점: 부모 필드가 모든 자식 행에서 중복되어(저장소 부풀음) 1대 다 경계가 암묵적으로 표현되고(order_id가 필요합니다), 같은 단계에 배열이 둘 있으면 신중히 UNNEST 하지 않을 경우 카티전 곱이 발생합니다.
전략 5: Stringify (셀 안의 JSON)
평탄화 자체를 하지 않는 선택지입니다. 중첩된 값 전체를 JSON 문자열로 직렬화해서 한 셀에 넣습니다. 바깥 테이블은 평평하게 유지되고, 구조는 그 안에 그대로 보존됩니다.
{ "id": "ord-001", "items": [{"sku": "A"}, {"sku": "B"}] }
는 다음과 같이 변환됩니다:
id,items
ord-001,"[{""sku"":""A""},{""sku"":""B""}]"
이것이 저희 JSON to CSV 변환기의 Stringify 모드입니다. 열 개수가 절대 폭발하지 않고, 원본 모양이 바이트 단위로 보존되며, 역방향 변환이 입력을 정확히 복원합니다.
장점: 100 % 무손실, 예측 가능한 열 개수, 역방향에서 Infer types를 켜면 왕복 변환 안전이 보장됩니다. 단점: Excel 사용자는 이스케이프된 따옴표를 봅니다. SQL 엔진은 값 내부를 조회하려면 JSON 함수가 필요합니다(BigQuery의 JSON_EXTRACT_SCALAR, Postgres의 ->>'key'). 스프레드시트 수식은 셀 안까지 닿지 못합니다.
5가지 전략 한눈에 비교
다섯 전략 모두에 동일 입력을 사용합니다: {"id":"ord-001","customer":{"name":"Alice"},"items":[{"sku":"A","qty":2},{"sku":"B","qty":1}]}.
| 전략 | 열 | 왕복 변환 안전 | 적합한 소비자 |
|---|---|---|---|
| 점 표기법 | 배열 크기에 따라 증가 | 아니요 | Excel 분석가 |
| 밑줄 표기법 | 배열 크기에 따라 증가 | 아니요 | SQL 웨어하우스 |
| 인덱스 배열 | 배열 슬롯당 2 개 | 아니요 (역방향 모호) | 고정 크기 배열 |
| 행 분할 | 좁고, 자식당 1 행 | 부분적 (키 필요) | Pandas / BigQuery |
| Stringify | 고정 | 예 | 파이프라인 왕복 |
결정 매트릭스: 어떤 소비자에 어떤 전략?
| 소비자 | 추천 전략 | 이유 |
|---|---|---|
| Excel / Sheets (분석가) | 점 표기법 + 큰 배열은 Stringify | 가독성 있는 열 이름. 큰 배열이 시트를 폭발시키지 않음 |
| Excel-EU (DE/FR/IT/ES) | 점 표기법 + ; 구분자 + UTF-8 BOM | 세미콜론 필수. BOM이 인코딩 깨짐을 방지 |
Pandas (json_normalize + explode) | 밑줄 표기법 + 행 분할 | SQL 친화적 열. 분할이 groupby와 잘 맞음 |
| BigQuery / Snowflake | TSV + Stringify 또는 분할 | 탭이 따옴표 함정을 피함. JSON_EXTRACT로 셀 조회 |
PostgreSQL COPY | RFC 4180 + 밑줄 표기법 + 평평한 구조 | SQL 안전 열. 엄격한 RFC 따옴표 처리 |
| MongoDB → BigQuery ETL | NDJSON으로 직접 로드, CSV 생략 | BigQuery는 NDJSON을 기본 지원. CSV는 우회로 |
Excel / Google Sheets: 로케일 함정
Excel 열 이름에는 실질적인 길이 제한이 없습니다. 진짜 함정은 세 가지입니다.
첫째, 로케일 분기. 유럽 Excel(독일, 프랑스, 이탈리아, 스페인)은 ,가 소수점 구분자이기 때문에 구분자로 ;를 기대합니다. 쉼표 구분 CSV는 모든 행이 A 열로 합쳐진 채 열립니다. 저희 json-to-csv 도구의 Excel 프리셋은 한 번의 클릭으로 ; + CRLF + UTF-8 BOM으로 전환합니다.
둘째, 지수 표기. Excel은 9007199254740993을 9.00719925474E+15로 표시합니다. 원본 JSON에서 큰 정수를 문자열로 저장하고 BOM을 켜서 Excel이 셀을 텍스트로 유지하게 하세요. 저희 변환기는 큰 정수를 자동으로 감지합니다.
셋째, 실용적인 열 한도. Excel은 이론상 16,384 개의 열을 지원하지만, 약 500 개를 넘으면 다루기 어려워집니다. 무거운 하위 트리는 Stringify 하거나, 변환 전에 jq로 미리 투영하세요.
Pandas: json_normalize + explode
중첩 배열에 대한 표준 패턴은 record_path + meta를 한 번에 쓰는 것입니다:
import pandas as pd
orders = [{
"order_id": "ord-001",
"customer": {"name": "Alice", "city": "Seattle"},
"items": [{"sku": "SKU-100", "qty": 2}, {"sku": "SKU-205", "qty": 1}]
}]
df = pd.json_normalize(orders, record_path='items',
meta=['order_id', ['customer', 'name'], ['customer', 'city']], sep='_')
df.to_csv("orders.csv", index=False)
출력은 항목당 한 행이고 order_id, customer_name, customer_city가 반복됩니다. 이 편이 explode를 먼저 돌리고 json_normalize를 나중에 쓰는 것보다 낫습니다. record_path는 중간 객체 열을 건너뛰고, meta는 어느 부모 필드가 전파될지를 제어합니다. 배열 원소가 중첩 객체를 담고 있는 입력에서는 max_level=으로 깊이를 제한하세요.
BigQuery / Snowflake: TSV + 셀 안의 JSON
BigQuery의 LOAD DATA는 CSV 따옴표 처리에 엄격해서 따옴표 안에 쉼표가 있는 파일을 자주 잘못 파싱합니다. 탭은 텍스트 필드 안에 거의 등장하지 않기 때문에 TSV가 더 안전합니다:
bq load --source_format=CSV --field_delimiter='\t' \
dataset.orders gs://bucket/orders.tsv \
order_id:STRING,customer:STRING,items:STRING
중첩 데이터를 단일 열에 Stringify된 JSON으로 적재하더라도, BigQuery는 여전히 JSON_EXTRACT_SCALAR로 그 안을 조회합니다:
SELECT order_id, JSON_EXTRACT_SCALAR(items, '$[0].sku') AS first_sku
FROM dataset.orders;
Snowflake는 VARIANT로 같은 기능을 지원하며, items:0.sku::STRING 같은 경로 조회를 제공합니다. 두 엔진 모두에서 중첩 배열이 크거나 가변 길이일 때는 Stringify + JSON 경로 조회가 완전 평탄화를 이깁니다.
PostgreSQL COPY: 엄격한 RFC 4180
COPY ... FROM ... WITH (FORMAT csv, HEADER true)는 일반적으로 마주치는 RFC 4180 리더 중 가장 엄격합니다. 두 가지 동작이 사람들을 걸려 넘어지게 합니다.
첫째, COPY는 UTF-8 BOM을 받아들이지 않습니다. 바이트 순서 표시가 첫 열 이름에 문자 그대로 접두사로 붙고(id 대신 id), id를 참조하는 모든 쿼리가 조용히 실패합니다. Postgres 대상에서는 BOM을 끄세요.
둘째, COPY는 중첩 데이터를 기본으로 파싱하지 못합니다. 적재 전에 배열을 여러 행으로 분할하거나, 대상을 jsonb로 정의하고 중첩 값을 stringify 하세요:
CREATE TABLE orders (order_id text PRIMARY KEY, customer text, items jsonb);
COPY orders FROM '/tmp/orders.csv' WITH (FORMAT csv, HEADER true);
SELECT order_id, items->0->>'sku' AS first_sku FROM orders;
이미 JSON으로만 종단 간 통신하는 파이프라인이라면 CSV를 건너뛰고 JSON 라인 입력과 함께 COPY ... FROM ... WITH (FORMAT text)를 쓰세요.
실제 페이로드 따라가기
사례 1: 전자상거래 주문 (customer + items 배열)
전형적인 주문은 중첩된 고객 정보와 가변 길이 items 배열을 결합합니다:
[{ "id": "ord-001",
"customer": { "name": "홍길동", "address": {"city": "서울", "country": "KR"} },
"items": [{"sku": "SKU-100", "qty": 2}, {"sku": "SKU-205", "qty": 1}] }]
올바른 전략은 누가 파일을 읽느냐에 달려 있습니다. 재무팀은 SKU별 매출을 계산하기 위해 항목당 한 행을 원하므로, 행 분할 전략이 정답이며 id와 customer.name이 반복된 두 행을 만듭니다. 운영팀은 풀필먼트 대시보드를 위해 주문당 한 행을 원하므로, items를 Stringify 한 점 표기법이 정답이며 배열이 열 개수를 폭발시키지 않게 합니다. 같은 입력, 두 가지 출력, 각자의 소비자에게 모두 맞습니다.
직접 시도해 보세요. 저희 JSON to CSV 변환기에 페이로드를 붙여넣고 Nested 옵션에서 Flatten과 Stringify를 전환해 보세요. “Nested E-commerce Orders” 예제는 동일한 모양을 로드합니다.
사례 2: GitHub Issues API (labels 배열 + user 객체)
/repos/{owner}/{repo}/issues 엔드포인트는 혼합된 중첩 모양을 반환합니다:
[{ "id": 1001, "title": "Bug: login 404", "state": "open",
"labels": ["bug", "priority:high"], "user": {"login": "alice"} }]
user는 유용한 필드 하나만 가진 객체이고, labels는 길이가 정해지지 않은 문자열 배열입니다. 실용적인 평탄화는 혼합형입니다. user에는 점 표기법을 쓰고(어차피 user.login만 신경 씁니다), labels는 ;로 구분된 단일 셀로 인라인 조인합니다:
id,title,state,labels,user.login
1001,Bug: login 404,open,bug;priority:high,alice
“배열을 한 셀로 합치기”와 “객체를 점으로 평탄화하기”를 단일 전략 안에 동시에 담을 수는 없습니다. 저희 변환기는 객체 평탄화를 자동으로 처리합니다. labels는 jq로 전처리하거나(map(.labels = (.labels | join(";")))) 기본 배열 stringify 동작을 받아들이세요.
사례 3: MongoDB mongoexport ($oid + 메타데이터)
mongoexport --jsonArray는 Extended JSON 래퍼를 생성합니다:
[{ "_id": {"$oid": "6634a1b2c3d4e5f600000001"},
"email": "alice@example.com",
"metadata": { "signupDate": "2026-01-15T10:30:00Z",
"preferences": {"newsletter": true, "theme": "dark"} } }]
$oid 래퍼는 문자 그대로 _id.$oid라는 열을 만들어 내고, 대부분의 SQL 엔진은 이를 거부합니다. jq로 미리 벗겨 내세요:
mongoexport --collection=users --jsonArray | jq 'map(._id = ._id."$oid")' > users.json
깊이 중첩된 metadata.preferences 블록은 소비자에 따라 고르세요. 분석가 내보내기에는 전체를 점 평탄화 하세요. metadata.preferences.theme은 잘 읽힙니다. 파이프라인 왕복에는 구조를 그대로 유지하기 위해 metadata를 stringify 하세요. CSV 파이프라인과 짝지을 수 있는 전체 jq 패턴은 jq 치트시트에서 확인하세요.
사례 4: Kubernetes Pod 스펙 (깊이 중첩)
kubectl get pod -o json 응답은 평탄 전략에 최악의 경우입니다. 구조가 일상적으로 여섯 단계 깊이로 들어갑니다(spec.template.spec.containers.0.resources.limits.memory). 단순한 점 평탄화는 70 자가 넘는 열 이름과 200 개 이상의 출력 열을 만듭니다. 두 가지 전략이 통합니다.
kubectl jsonpath로 미리 투영하기. 실제로 필요한 필드만 고르세요:
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].image}{"\t"}{.status.phase}{"\n"}{end}' > pods.tsv
spec은 stringify, metadata는 평탄화. metadata(name, namespace, labels)는 평평하게 두고 spec은 단일 셀로 stringify 하세요:
kubectl get pods -o json | jq 'map({name: .metadata.name, namespace: .metadata.namespace, spec: (.spec | tostring)})'
그런 다음 Flatten 모드로 변환기에 붙여넣으세요. spec 열은 한 개의 JSON 셀이 되고, metadata 열은 가독성을 유지합니다. 사전 투영 없이 kubectl get pod -o json | json-to-csv flatten을 돌리는 안티 패턴은 피하세요. 열 개수가 감당 불가능해집니다.
왕복 안전성: 평탄화는 손실이 있고, Stringify는 무손실입니다
여기 경쟁사 가이드들이 건너뛰는 정리(theorem)가 있습니다. 점 표기법, 밑줄 표기법, 인덱스 배열, 행 분할은 모두 단방향 투영입니다. 이 중 어떤 것으로든 한 번 평탄화하고 나면, CSV만으로는 원본 JSON을 완벽히 복원할 수 없습니다.
반례는 쉽게 만들 수 있습니다. customer.address.city라는 열은 {"customer": {"address": {"city": "..."}}}와 {"customer": {"address.city": "..."}} 사이에서 모호합니다. 인덱스 배열은 가역적으로 보이지만, CSV는 items.0.sku가 배열로 복원되어야 할지 숫자 키를 가진 객체로 복원되어야 할지 알려 줄 수 없습니다. 행 분할은 group-by 키가 필요합니다. order_id가 없으면 어느 행들이 같은 부모에 속했는지 알 수 없습니다.
오직 Stringify만 왕복에서 살아남습니다. 중첩 값이 JSON 문자열로 그대로 보존되므로, 역방향 변환기가 셀을 읽고 파싱해서 원본을 손상 없이 다시 끼워 넣습니다. Stringify로 변환한 뒤 CSV를 저장하고, 저희 CSV to JSON 변환기에 붙여넣고 Infer types를 켜면 입력과 동일한 바이트를 얻습니다.
실용 규칙: 파이프라인 왕복 → Stringify. 일회성 분석이나 리포팅 → 소비자에 따라 점, 밑줄, 또는 분할.
저희 브라우저 도구에서 직접 해 보기
JSON to CSV 변환기는 다섯 전략 중 두 가지를 직접 노출합니다. Flatten(점 표기법과 인덱스 배열의 결합)과 Stringify(셀 안에 구조를 보존). 나머지 세 가지(밑줄 표기법, 행 분할, SQL 대상 프리셋)는 전처리 한 단계만 거치면 됩니다.
전형적인 작업은 다섯 번의 클릭으로 끝납니다:
- JSON 포맷터로 입력을 검증해서 문법 오류가 조용한 변환 실패로 이어지지 않게 하세요.
- JSON을 JSON to CSV 변환기에 붙여넣으세요. 변환은 즉시 실행됩니다.
- 점·인덱스 키를 원하면 Nested를 Flatten으로, 셀 안에 배열과 객체를 유지하려면 Stringify로 설정하세요.
- 프리셋을 고르세요. 파이프라인에는 RFC 4180, 유럽 스프레드시트에는 Excel, 웨어하우스에는 TSV, 쉼표가 많은 텍스트에는 Pipe.
- Swap direction을 클릭하고 Infer types를 켠 CSV to JSON 변환기로 Stringify 왕복을 검증하세요.
모든 작업은 브라우저 안에서 끝나며, PII나 내부 내보내기, 운영 비밀이 페이지 밖으로 나가지 않습니다. 페이지 로드 이후 네트워크 요청은 0 건이라, 제3자 사이트로 업로드할 수 없는 민감 데이터에 적합합니다.
흔한 함정
여섯 가지 실패 모드가 반복해서 나타납니다.
- 열 이름 폭발. Kubernetes 스펙과 GitHub PR 리뷰 스레드는 수백 개의 잎 경로를 만듭니다. 해결:
jq나kubectl jsonpath로 미리 투영하거나, metadata는 평탄화하면서 무거운 하위 트리는 stringify 하세요. - 배열 길이 불일치. 1 행은 항목이 3 개, 2 행은 5 개. 인덱스 배열은 1 행의
items.3.sku와items.4.sku에 빈 셀을 만듭니다. 해결: 행 분할로 전환하세요. - 역방향에서 인덱스 키가 문자열로 취급됨. CSV-to-JSON이
items.0.sku를 볼 때0은 기술적으로 문자열 키입니다. 일부 역방향 변환기는[{"sku": "A"}]대신{"0": {"sku": "A"}}를 복원합니다. 해결: 왕복에는 Stringify를 쓰세요. - 이미 구분자를 포함한 키. GA4 이벤트는 문자 그대로 점을 담은
event_params.key같은 키를 가집니다..으로 평탄화하면 모호한 경로가 생깁니다. 해결: 밑줄을 쓰거나 문제 키를 개명하세요. 확장 키 지원을 가진 JSON 포맷들의 배경은 JSON5에서 JSONC까지 가이드를 참고하세요. - 단계별 타입 혼합. 일부 행은
address가 객체이고, 다른 행은null입니다. 평탄화는 객체가 null이었던 자리에 빈 셀을 만듭니다. 저희 변환기의 스키마 메모 경고가 이를 표시하므로 다운스트림 소비자에서 확인할 수 있습니다. - Excel에 의해 큰 정수가 잘림.
$oidLong, Twitter snowflake ID, K8sresourceVersion은 JavaScript의 안전 범위(2^53 - 1)를 초과하면 조용히 반올림됩니다. Excel은 이를9.00719925474E+15로 표시합니다. 해결: 원본 JSON에서 ID를 문자열로 저장하고, BOM을 활성화하고, Excel 프리셋을 사용하세요.
FAQ
중첩 JSON을 CSV로 평탄화하는 가장 좋은 방법은 무엇인가요?
중첩 JSON을 CSV로 평탄화하는 가장 좋은 방법은 다운스트림 소비자에 따라 다릅니다. Excel이나 Google Sheets에는 점 표기법을 사용하세요. Pandas나 BigQuery가 데이터를 집계할 때는 행 분할을 사용하세요. CSV가 데이터 손실 없이 JSON으로 다시 왕복해야 한다면 Stringify를 사용하세요. 전략을 다음 독자에게 맞추세요.
JSON 배열을 여러 CSV 행으로 어떻게 변환하나요?
행 분할 전략으로 JSON 배열을 여러 CSV 행으로 변환합니다. 부모 필드를 배열 원소마다 한 번씩 복제해서 각 원소가 자기 행이 되게 하세요. Pandas에서는 pd.json_normalize(data, record_path='items', meta=['order_id'])가 한 번의 호출로 해 줍니다. SQL에서는 UNNEST(items)가 같은 모양을 만듭니다. 부모 키는 분할된 행에 걸쳐 반복됩니다.
CSV를 원래의 중첩 JSON으로 다시 왕복할 수 있나요?
아닙니다. CSV를 원래의 중첩 JSON으로 다시 왕복하는 일은 Stringify 모드에서만 동작합니다. 점 표기법, 밑줄, 인덱스 배열, 행 분할은 손실이 있는 단방향 투영입니다. 역방향 변환기는 트리를 완벽히 복원할 수 없습니다. Stringify는 배열과 객체를 단일 셀 안의 JSON으로 보존하므로, Infer types가 켜져 있을 때 전체 왕복이 바이트 단위로 동일합니다.
Excel이 평탄화된 JSON을 한 개의 긴 열로 표시하는 이유는 무엇인가요?
쉼표가 소수점용으로 예약되어 있어 Excel이 세미콜론을 구분자로 기대하는 유럽 로케일(독일, 프랑스, 이탈리아, 스페인)에 있을 때 Excel이 평탄화된 JSON을 한 개의 긴 열로 표시합니다. json-to-csv 도구의 Excel 프리셋은 한 번의 클릭으로 ; + CRLF + UTF-8 BOM으로 전환합니다.
열 이름에 점 표기법과 밑줄 중 어느 것을 써야 하나요?
대상이 Excel, Google Sheets, Pandas일 때는 점 표기법을 사용하세요. 점은 json_normalize의 기본값이며 자연스럽게 읽힙니다. 대상이 SQL일 때는 밑줄을 사용하세요. Postgres, BigQuery, Snowflake는 점을 포함한 식별자에 따옴표를 요구하는 반면, 밑줄은 어디서나 따옴표 없이 받아들여집니다.
pandas json_normalize는 객체 배열을 어떻게 처리하나요?
Pandas json_normalize는 record_path와 meta 인수를 통해 객체 배열을 처리합니다. pd.json_normalize(data, record_path='items', meta=['order_id'])는 items를 원소당 한 행으로 분할하면서 order_id를 반복합니다. 배열이 없는 중첩 객체에는 더 단순한 pd.json_normalize(data, sep='_')가 customer_address_city 같은 밑줄 구분 열 이름을 생성합니다. 깊은 트리에서는 max_level=으로 깊이를 제한하세요.
깊이 중첩된 JSON을 평탄화할 때 열 한도는 얼마인가요?
깊이 중첩된 JSON을 평탄화할 때 열 한도는 Excel에서 16,384 개이고 CSV 자체로는 사실상 무제한이지만, 500 개를 넘어가면 출력이 다루기 어려워집니다. Kubernetes Pod 스펙이나 GraphQL 응답은 손쉽게 이를 초과합니다. JSON to CSV 변환기로 무거운 하위 트리를 stringify 하거나 jq나 kubectl jsonpath로 미리 투영하세요.
CSV 변환 전에 JSON을 평탄화하는 도구로 jq가 좋은가요?
그렇습니다. jq는 CSV 변환 전에 JSON을 평탄화하기에 적합한 도구입니다. 사전 투영(map({id, name})), 사전 분할(.[] | {id, item: .items[]}), 모양 정규화를 한 줄로 처리합니다. jq 파이프라인은 CSV 단계 이전에 실행되며 어떤 필드가 변환기에 닿을지를 정확히 제어합니다. 패턴은 jq 치트시트를 참고하세요.
결론
다섯 가지 핵심:
- JSON-to-CSV는 문법 문제가 아니라 기하학 문제입니다. 가지가 어떻게 무너질지 정하지 않고는 나무가 격자에 들어가지 않습니다.
- 다섯 전략(점, 밑줄, 인덱스 배열, 행 분할, Stringify)이면 실무에서 마주치는 경우를 거의 다 처리합니다. 소비자 기준으로 고르세요.
- Stringify만이 무손실 경로입니다. 파이프라인 왕복에 쓰세요.
- Excel-EU와 BigQuery 프리셋은 우연히 만든 게 아닙니다. 그대로 쓰세요.
- 실제 페이로드(mongoexport, Kubernetes 스펙, GitHub 응답)는 대개
jq나kubectl jsonpath사전 투영 단계가 먼저 필요합니다.
직접 페이로드를 JSON to CSV 변환기에서 시도해 보세요. 로컬에서 실행되며, 다섯 전략을 모두 처리하고, Stringify 모드는 무손실 왕복을 보장합니다. 업로드나 가입은 필요 없고, 데이터는 페이지를 떠나지 않습니다.