Json To Csv LogoJson To Csv

중첩된 JSON을 CSV로 변환하는 방법: 평탄화 토글이 실제로 하는 일 (2026 가이드)

평탄화는 중첩된 JSON을 스프레드시트 모양의 행으로 바꿔요: 객체는 점 표기법 컬럼이 되고, 목록은 합쳐지거나 문자열화되며, 빈 값은 정확한 규칙을 따라요. 토글이 실제로 셀 단위로 무엇을 하는지, 실제 엔진으로 검증한 내용을 정리했어요.

업데이트: 2026-10-1311 분 소요

JSON이 평탄함을 거부하는 이유: 트리 대 격자

CSV는 격자예요: 행과 열로 이루어져 있고, 모든 셀은 하나의 평탄한 값을 가져요. JSON은 트리예요: 객체 안에 객체가 있고, 객체 안에 목록이 있고, 목록 안에 객체가 있어요. 평탄화는 이 두 모양 사이의 번역이고, JSON을 CSV로 바꿀 때 변환기가 사용자를 대신해 판단해야 하는 유일한 부분이에요. 왜냐하면 단 하나의 "정답"이 존재하지 않기 때문이에요.

가장 단순한 중첩 레코드를 볼게요: { "id": 1, "customer": { "name": "Ana", "city": "Madrid" } }. 격자는 두 개의 값을 품은 "customer"를 담을 방법이 없어요. 그래서 변환기는 선택해야 해요: customer에 자체 컬럼을 주고 객체를 텍스트로 욱여넣을지, 아니면 객체를 customer.name과 customer.city 컬럼으로 펼칠지. 모든 변환기는 같은 몇 가지 규칙으로 이 선택을 해요. 규칙만 알면 변환 버튼을 누르기 전에 CSV를 예측할 수 있어요.

왜 모든 변환기가 같은 규칙을 쓸까요? CSV에는 중첩 타입이 아예 없어서, 경로 기반 이름 짓기가 사실상 유일하게 합리적인 대응이기 때문이에요. 점 표기법은 JSONPath나 자바스크립트의 점 접근법과 같은 문법을 빌려와서, 개발자가 아닌 분도 customer.contact.city를 보고 "아, customer 안에 contact이 있고 그 안에 city가 있구나"라고 바로 읽을 수 있어요. 규칙이 공유된다는 건 이식성도 된다는 뜻이에요. 이 사이트에서 만든 CSV를 엑셀이나 구글 시트, 파이썬 pandas 어디에 넣어도 컬럼 이름의 의미는 그대로 통하니까요.

앞으로 평탄화할 실행 예제

아래의 모든 내용은 하나의 파일로 설명해요. 3단계 중첩을 가진 주문 3건, 단순 값 목록, 빈 목록, 빈 객체, null, 그리고 객체 목록이 들어 있어요. 이 파일을 이 사이트의 실제 변환 엔진(변환 버튼을 눌렀을 때 실행되는 것과 동일한 자바스크립트)에 그대로 돌렸고, 표시되는 모든 출력은 엔진이 실제로 만들어낸 결과예요:

orders-nested.json
[
  { "orderId": "A-101", "customer": { "name": "Ana Ruiz", "contact": { "email": "ana@example.com", "city": "Madrid" } }, "items": ["book", "pen"], "tags": [], "meta": {} },
  { "orderId": "A-102", "customer": { "name": "Ken Sato", "contact": { "email": "ken@example.com", "city": "Osaka" } }, "items": ["lamp"], "tracking": [{ "code": "TX-1", "status": "shipped" }, { "code": "TX-2", "status": "pending" }] },
  { "orderId": "A-103", "customer": { "name": "Lina Noor", "contact": { "email": null, "city": "Berlin" } }, "items": ["mug", null, "plate"] }
]

규칙 1: 중첩 객체는 점 표기법 컬럼이 돼요

첫 번째이자 가장 중요한 규칙이에요: 객체 중첩의 모든 단계는 단계들을 점으로 이은 컬럼 이름이 돼요. customer.contact.email은 세 개의 컬럼이 아니라 트리를 따라 내려간 전체 경로를 이름으로 가진 하나의 컬럼이에요. 평탄화를 켰을 때 엔진이 실제로 출력한 결과예요:

CSV output — flattening ON (verified against the real engine)
orderId,customer.name,customer.contact.email,customer.contact.city,items,tags,meta,tracking
A-101,Ana Ruiz,ana@example.com,Madrid,book; pen,,{},
A-102,Ken Sato,ken@example.com,Osaka,lamp,,,"[{""code"":""TX-1"",""status"":""shipped""},{""code"":""TX-2"",""status"":""pending""}]"
A-103,Lina Noor,,Berlin,mug; ; plate,,,

중첩 깊이 자체에는 실질적인 제한이 없어요. 4단계 객체({ a: { b: { c: { d: "deep" } } } })도 테스트했는데, 엔진은 값이 deep인 a.b.c.d라는 단일 컬럼을 만들었어요. 점으로 된 이름은 스프레드시트에서 정렬과 필터가 정상적으로 돼요. customer.contact.city가 너무 길게 느껴지면 나중에 헤더를 한 번에 바꿔주면 돼요.

헤더가 길게 느껴지면 변환 뒤에 정리하는 게 가장 깔끔해요. 엑셀이나 구글 시트에서 첫 행을 선택하고 찾기/바꾸기로 "customer.contact."를 지우면 한 번에 짧아져요. 점 표기법은 기계가 읽기 위한 이름이지 사람이 읽기 위한 이름이 아니에요. 분석할 때는 짧은 별칭으로, 원본 구조를 추적할 때는 긴 경로 이름으로 쓰면 돼요.

규칙 2: 단순 값 목록은 하나의 셀로 합쳐져요

목록이 단순 값(문자열, 숫자, 불리언, null)만 담고 있으면 전체 목록이 "; "(세미콜론+공백)으로 값을 이어 붙인 단일 셀이 돼요. 위의 items 컬럼을 보세요: ["book", "pen"]은 book; pen이라는 단일 셀이 되었고, ["mug", null, "plate"]는 mug; ; plate가 되었어요. null은 건너뛰어진 게 아니라 빈 조각으로 바뀌었어요.

실무적으로 두 가지 의미가 있어요. 첫째, 나중에 CSV 구분자로 세미콜론을 고르면 합쳐진 셀은 자동으로 따옴표로 감싸져요("book; pen"). 셀 안의 구분자가 셀을 쪼개지 못하도록 엔진이 셀 단위로 CSV 따옴표 규칙을 적용해요. 위 테스트에서 검증된 내용이에요. 둘째, 합치기는 경계 정보를 잃어요. 평탄화 뒤의 book; pen은 텍스트일 뿐 목록이 아니에요. 항목을 다시 행으로 쪼개야 한다면 규칙 3을 보세요.

합치기에는 숫자나 불리언도 그대로 들어가요. [1, true, "x"]는 1; true; x라는 텍스트 셀이 돼요. 타입 정보는 사라지지만 눈으로 읽는 데는 문제없어요. 그리고 구분자 충돌은 엔진이 알아서 처리해요: 값 안에 쉼표가 있으면 셀 전체가 따옴표로 감싸지고, 값 안에 따옴표가 있으면 따옴표가 두 개로 늘어나요. 이건 CSV 표준(RFC 4180)의 따옴표 규칙 그대로예요. 어떤 구분자를 골라도 합쳐진 셀이 깨지지 않는 이유예요.

규칙 3: 객체 목록은 하나의 JSON 텍스트 셀로 축소돼요

사람들을 가장 놀라게 하는 규칙이에요. 목록이 객체를 담고 있으면 — { code, status } 항목을 가진 tracking 배열처럼 — 컬럼이 될 수 없고(객체는 값이 아니라 구조를 가졌어요) 단순 값처럼 합쳐질 수도 없어요. 그래서 엔진은 전체 목록을 따옴표로 감싼 JSON 텍스트 셀로 저장해요. 위에서 A-102의 tracking 컬럼은 CSV 규칙에 따라 이스케이프된 전체 JSON 배열을 텍스트로 담고 있어요.

이게 한계냐고요? 솔직히 말하면, 이 모양에서는 그래요. 이 변환기에는 행 확장 토글이 없어요. 목록 항목당 하나의 CSV 행을 만들지 않아요. 추적 이벤트마다 한 행씩 정말 필요하다면 변환 전에 구조를 바꿔야 해요: 안쪽 배열을 꺼내서([{ "code": "TX-1", "status": "shipped" }, ...]만 따로) 변환하면 code/status 행이 깔끔하게 나와요. JSON 텍스트 한 컬럼은, 요청하지도 않은 행을 멋대로 만들어내는 것보다 정직한 대안이에요.

규칙 4: 빈 값과 null은 정확한 규칙을 따라요

"내 CSV가 왜 이상하지"라는 혼란이 시작되는 지점이 null과 빈 값이에요. 그래서 검증된 동작을 추측 없이 정리할게요:

  • null 값은 빈 셀이 돼요. Lina Noor의 email: null은 customer.contact.email 아래에 빈 셀을 만들었어요. "null"이라는 텍스트도, 따옴표도 없어요.
  • 빈 목록은 빈 셀이 돼요. A-101의 tags: []는 셀을 비워 두었어요. (참고: 이건 평탄화 켜짐 상태의 동작이에요. 평탄화를 끄면 같은 목록이 리터럴 텍스트 []로 나타나요.)
  • 빈 객체는 {}라는 텍스트가 돼요. A-101의 meta: {}는 {}를 담은 셀을 만들었어요. 펼칠 게 없으니 엔진은 객체의 리터럴 모양을 그대로 써요.
  • 목록 안의 null은 건너뛰어지거나 "null"로 쓰이는 대신 빈 합침 조각이 돼요. 위의 mug; ; plate처럼요.

이건 보편 법칙이 아니라 설계 결정이지만 일관돼요: null은 "여기 아무것도 없어요"라는 뜻이고, CSV는 아무것도 쓰지 않아요.

평탄화 켜기 대 끄기, 바이트 단위 비교

같은 파일, 평탄화 끄기 상태예요. 모든 최상위 키가 각자 컬럼을 유지하고, 중첩 구조는 따옴표로 감싼 JSON 텍스트로 저장돼요:

CSV output — flattening OFF (verified against the real engine)
orderId,customer,items,tags,meta,tracking
A-101,"{""name"":""Ana Ruiz"",""contact"":{""email"":""ana@example.com"",""city"":""Madrid""}}","[""book"",""pen""]",[],{},
A-102,"{""name"":""Ken Sato"",""contact"":{""email"":""ken@example.com"",""city"":""Osaka""}}","[""lamp""]",,,"[{""code"":""TX-1"",""status"":""shipped""},{""code"":""TX-2"",""status"":""pending""}]"
A-103,"{""name"":""Lina Noor"",""contact"":{""email"":null,""city"":""Berlin""}}","[""mug"",null,""plate""]",,,

끄기 모드의 세부 사항을 눈여겨보세요: 컬럼이 최상위 키(customer, items, tags, meta, tracking)로 줄어들고, 각 중첩 값은 CSV 따옴표 배가 규칙으로 이스케이프된 원시 JSON으로 문자열화되며, 빈 목록과 빈 객체는 리터럴 []와 {} 텍스트로 나타나요. 끄기는 정직하지만 못생긴 표현이에요. 정확한 중첩 구조를 보존해야 할 때(예를 들어 JSON 모양 필드를 기대하는 시스템에 행을 다시 넣기 전) 맞는 선택이에요. 분석용 — 필터, 피벗 테이블, 차트 — 으로는 평탄화 켜기가 정답이고, 그래서 기본값이에요.

끄기 모드의 JSON 텍스트 셀을 엑셀로 열면 셀이 길게 늘어나서 보기 힘들 수 있어요. 이건 데이터가 깨진 게 아니라, 엑셀이 따옴표 안의 JSON을 텍스트로만 보여주기 때문이에요. 구조를 눈으로 확인하고 싶으면 JSON 뷰어에 붙여넣는 편이 빨라요. 반대로 켜기 모드의 CSV는 열자마자 필터 버튼이 바로 먹혀요. 용도가 분석이면 켜기, 보존이면 끄기 — 이 기준으로 고르면 실패가 없어요.

행은 어디서 오나요: 배열, API 래퍼, JSON Lines

평탄화가 컬럼을 정하면, 다른 무언가가 행을 정해요. 엔진은 최상위 값을 보고 세 가지 경우를 따라요:

경우 하나: 최상위가 객체들의 배열이면 각 객체가 한 행이 돼요. 위의 주문 예제가 그래요: 객체 3개, 행 3개. 경우 둘: 최상위가 단일 객체면 행 하나가 나와요. 경우 셋이 유용한 경우예요: 최상위가 API 응답처럼 배열을 감싼 객체인 경우 — { "data": [ ... ], "total": 2 }처럼요. 엔진은 안쪽 배열을 감지하고(data, results, users, items 같은 흔한 래퍼 키를 확인해요) 각 요소를 행으로 만들며 래퍼 필드는 버려요. 검증했어요: { "data": [{ "a": 1, "b": 2 }, { "a": 3, "b": 4 }], "total": 2 }는 a와 b 컬럼을 가진 데이터 행 정확히 2개를 만들었고 total은 사라졌어요.

알아두면 좋은 입력 모양이 하나 더 있어요: 엔진은 JSON Lines(NDJSON)도 받아요. 한 줄에 하나의 JSON 객체예요. 내보낸 파일이 하나의 큰 배열이 아니라 만 줄짜리 텍스트라면 그대로 붙여넣으세요. 똑같이 변환돼요.

한 가지 더: 최상위가 단일 객체인데 그 안에 배열이 두 개 이상 있으면 엔진은 어떤 배열을 행으로 삼을까요? 흔한 래퍼 키(data, results, users, items)를 먼저 찾고, 없으면 가장 큰 배열을 고르는 식으로 동작해요. 구조가 애매하면 배열만 따로 떼어내서 변환하는 게 가장 확실해요. "똑똑한 감지"에 맡기기보다 입력 모양을 단순하게 만드는 편이 결과가 예측 가능해요.

들쭉날쭉한 데이터: 빠진 키는 그냥 빈 셀이 돼요

실제 내보내기 데이터는 들쭉날쭉해요: 주문 A-102는 tracking은 있지만 meta가 없고, A-101은 meta는 있지만 tracking이 없고, A-103은 둘 다 없어요. 엔진은 모든 행에서 발견된 모든 키의 합집합을 순서대로 컬럼 목록으로 만들어요. 그래서 위의 헤더 행이 8개 컬럼을 전부 가지고 있고, 키가 빠진 행은 그 아래에 그냥 빈 셀을 가져요.

그래서 헤더가 알파벳순이 아니라 삽입 순서(orderId가 먼저, 그 다음 처음 본 중첩 키들)로 나오는 거예요: 컬럼이 엔진을 따라 데이터 모양을 그대로 따라가기 때문이에요. 뒤쪽 행에서 완전히 새로운 중첩 경로가 나타나도 컬럼은 어쨌든 생겨요. 조용히 버려지는 건 없어요.

합집합 컬럼 방식 덕분에 나중에 행이 추가돼도 안전해요. 예를 들어 A-104에 shipping: { fee: 5 }라는 새 중첩 경로가 생기면, 기존 컬럼 뒤에 shipping.fee 컬럼이 하나 더 붙고 기존 행들은 그 칸을 비워 둬요. 스키마가 바뀌어도 과거 행이 깨지지 않는 구조예요. 다만 컬럼 순서가 "처음 본 순서"라서, 같은 파일을 두 번 변환하면 헤더 순서가 항상 같아요. 재현성이 보장되는 이유예요.

발목을 잡는 세 가지 엣지 케이스

첫째: 이미 점을 포함한 키. JSON에 리터럴 키 "user.name"과 중첩된 user: { name: ... }이 둘 다 있으면 같은 컬럼을 차지하려고 해요. 테스트했어요: 중첩 값이 이기고 리터럴 키의 값은 사라져요([{ "user.name": "x", user: { name: "y" } }]는 y를 담은 user.name 컬럼 하나를 만들었어요). 두 값이 다 중요하면 변환 전에 점이 든 키 이름을 바꿔주세요.

둘째: 혼합 타입 목록. [1, "a", { "b": 2 }] 같은 목록은 "전부 단순 값"이 아니라서, 엔진은 객체 목록처럼 취급해 전체를 하나의 JSON 텍스트 셀로 문자열화해요. 대부분이 단순 값이어도 그래요. "; " 합치기 동작을 원하면 목록을 동질적으로 유지하세요.

셋째: 깊은 중첩은 괜찮지만 컬럼 이름을 조심하세요. 10단계 중첩은 10개 조각으로 된 점 표기법 컬럼 이름을 만들어요. 기술적으로는 맞지만 실제로는 읽기 힘들어요. 전부 평탄화한 뒤, 정말 필요한 세 개 컬럼의 이름만 바꾸세요.

세 가지 모두에 통하는 습관이 하나 있어요: 다운로드 전에 미리보기 행을 눈으로 확인하는 거예요. 컬럼 이름이 예상과 다르면 입력 JSON의 키를 먼저 의심하세요. 엔진은 규칙대로만 움직이고, 규칙은 위의 네 가지에서 벗어나지 않아요.

30초 평탄화 체크리스트

다른 건 다 잊어도 이건 기억하세요: 분석용으로는 평탄화를 켜 두고, 구조 보존이 필요할 때만 끄세요. 점 표기법 컬럼과 "; "로 합쳐진 목록을 예상하세요. 항목마다 한 행씩 필요하면 객체 목록을 꺼내서 따로 변환하세요. 빈 셀은 오류가 아니라 null의 정직한 표현으로 대하세요. 토글은 클릭 한 번이지만, 이제 그 클릭이 정확히 무엇을 하는지 아시잖아요.

관련 가이드

자주 묻는 질문

JSON을 CSV로 평탄화한다는 게 무슨 뜻인가요?

평탄화는 중첩된 JSON을 평평한 스프레드시트 격자로 바꾸는 단계예요: 중첩 객체는 점 표기법 컬럼이 되고(customer.contact.city), 단순 값 목록은 "; "로 하나의 셀에 합쳐지며, 객체 목록은 단일 JSON 텍스트 셀로 축소돼요. 평탄화 없이 변환하면 중첩 값은 각 컬럼에 원시 따옴표 JSON 텍스트로 저장돼요. 중첩된 JSON을 CSV로 변환하는 방법의 핵심이 바로 이 평탄화예요.

중첩된 JSON 객체를 CSV 컬럼으로 평탄화하려면 어떻게 하나요?

평탄화 옵션이 있는 변환기를 쓰고 켜 두면 돼요. 중첩 객체는 깊이에 상관없이 점 표기법 컬럼으로 자동 확장돼요(customer.contact.email, 심지어 a.b.c.d까지). 이 사이트의 변환기는 평탄화가 기본값이에요: JSON을 붙여넣거나 업로드하고 변환하면, 각 중첩 경로가 정렬과 필터가 가능한 자체 컬럼이 돼요.

중첩된 JSON을 CSV로 변환할 때 배열은 어떻게 되나요?

배열이 무엇을 담고 있느냐에 따라 달라요. 단순 값 목록(문자열, 숫자)은 "; "로 단일 셀에 합쳐져요. ["book", "pen"]은 book; pen이 돼요. 객체 목록은 하나의 따옴표 JSON 텍스트 셀로 저장돼요. 객체는 평탄한 값이 될 수 없기 때문이에요. 빈 목록은 빈 셀이 돼요. 혼합 목록은 통째로 문자열화되니, 예측 가능한 결과를 원하면 목록을 동질적으로 유지하세요. 중첩된 JSON을 CSV로 변환할 때 배열 처리는 이 두 갈래(합치기 또는 JSON 텍스트)로 완전히 설명돼요.

평탄화한 CSV에 빈 셀이 많은 이유는 뭔가요?

빈 셀은 정상이고 정직해요: null 값은 빈 셀이 되고, 빈 목록은 빈 셀이 되며, 행마다 키가 다르면(들쭉날쭉한 데이터) 빠진 키 자리에 공백이 남아요. 빈 셀들이 원본 JSON의 null과 일치하는지 확인해 보세요. 일치한다면 변환이 정확한 거예요.

JSON을 CSV로 변환할 때 평탄화를 켜야 하나요, 꺼야 하나요?

분석용으로는 켜 두세요. 피벗 테이블, 필터, 차트에 필요한 건 점 표기법 컬럼이에요. 정확한 중첩 구조를 보존해야 할 때만 끄세요. 예를 들어 JSON 모양 필드를 기대하는 시스템에 행을 다시 넣기 전처럼요. 끄기 모드는 각 중첩 값을 따옴표로 감싼 원시 JSON 텍스트로 저장해요. 정확하지만 스프레드시트에서는 쓸 수 없어요.

중첩된 객체 배열의 항목마다 CSV 행 하나씩 받을 수 있나요?

이 변환기는 객체 목록을 행으로 펼치지 않아요. 하나의 JSON 텍스트 셀로 저장해요. 확실한 해결책은 안쪽 배열을 꺼내서 따로 변환하는 거예요(예: [{"code":"TX-1"},{"code":"TX-2"}]만 변환). 그러면 항목당 한 행씩 깔끔하게 나와요. 변환한 뒤가 아니라 변환 전에 추출하세요.

중첩된 JSON을 온라인에서 CSV로 평탄화해주는 무료 도구가 있나요?

네, 이 사이트의 변환기는 무료이고 가입이 필요 없으며, 중첩된 JSON을 기본적으로 평탄화해요(점 표기법 컬럼, "; "로 합쳐진 목록). 전부 브라우저 안에서 실행되니 데이터가 기기를 떠나지 않아요. 파일을 업로드하거나 JSON을 붙여넣고, 평탄화를 켜 두고, 구분자를 고른 뒤 CSV를 다운로드하세요.

중첩된 JSON이 있나요? 지금 바로 평탄화하세요 — 설치할 게 없어요

중첩된 JSON을 붙여넣거나 파일을 업로드하세요: 객체는 점 표기법 컬럼이 되고, 목록은 깔끔하게 합쳐지며, 변환은 전부 브라우저 안에서 실행돼요. 무료, 가입 불필요.

JSON을 CSV로 변환하는 도구 열기
모든 가이드