본문 바로가기

파일 형식 백과사전

받은 JSON을 엑셀로 봐야 할 때, XLSX로 변환하는 가장 깔끔한 방법

API에서 내려받은 데이터나 동료가 보내 준 파일이 .json인데, 정작 검토는 엑셀로 해야 하는 상황이 자주 생깁니다. 메모장으로 열면 중괄호와 따옴표가 빽빽해서 어디가 한 행인지조차 가늠이 안 됩니다.

JSON을 엑셀(XLSX)로 바꾸는 일 자체는 코드 몇 줄이면 끝납니다. 다만 JSON이 평평한 표 모양이냐, 값 안에 또 값이 들어 있는 중첩 구조냐에 따라 결과가 크게 갈립니다. 실제로 변환해서 어떤 엑셀이 만들어지는지 직접 보여 드리겠습니다.

 

변환에 쓴 환경

아래 모든 결과는 직접 돌려서 나온 값입니다. 측정 환경은 윈도우 11, 파이썬 3.14.3이고, 라이브러리는 pandas 3.0.1과 엑셀 기록 엔진인 openpyxl 3.1.5를 썼습니다. 두 라이브러리는 한 번만 설치하면 됩니다.

pip install pandas openpyxl

1단계. 평평한 JSON이라면 두 줄이면 됩니다

가장 흔한 형태는 객체가 줄지어 있는 배열입니다. 각 객체가 같은 항목(키)을 가진 한 명의 정보라면, 그대로 엑셀의 한 행이 됩니다. 아래는 직원 3명을 담은 한글 포함 JSON입니다.

[
  {"id": 1, "name": "김민수", "dept": "영업팀", "city": "서울"},
  {"id": 2, "name": "이서연", "dept": "개발팀", "city": "부산"},
  {"id": 3, "name": "박지훈", "dept": "디자인팀", "city": "대구"}
]

이 파일을 members.json이라는 이름으로 저장했다고 하면, 변환은 아래 두 줄이 핵심입니다. 첫 줄은 JSON을 표(데이터프레임)로 읽고, 둘째 줄은 그 표를 엑셀 파일로 씁니다.

import pandas as pd

df = pd.read_json("members.json")
df.to_excel("members.xlsx", index=False)

index=False는 맨 왼쪽에 0, 1, 2 같은 줄번호 열이 따라붙는 것을 막아 줍니다. 이게 없으면 쓸데없는 첫 열이 하나 생깁니다.

2단계. 정말 잘 만들어졌는지 확인

변환이 끝났다는 메시지가 없어도 파일은 조용히 생깁니다. 그래서 실제로 크기와 행수, 시트를 찍어 확인했습니다.

members.xlsx 크기: 5038 bytes
행수(헤더 제외): 3 / 열수: 4
열 이름: ['id', 'name', 'dept', 'city']
시트 이름: ['Sheet1'] / 시트 max_row: 4, max_col: 4

데이터 3행에 헤더 1줄이 더해져 엑셀 시트의 실제 마지막 행이 4가 됩니다. JSON의 키 이름이 그대로 엑셀의 열 제목이 되고, 객체 하나가 한 행이 됩니다. 평평한 구조라면 이걸로 끝입니다.


여기서 막힙니다. 값 안에 또 값이 들어 있는 JSON

실무에서 받는 JSON은 이렇게 반듯하지 않은 경우가 많습니다. 연락처가 묶음으로 들어 있거나, 보유 기술이 목록으로 들어 있는 식입니다.

[
  {"id": 1, "name": "김민수",
   "contact": {"email": "a@x.com", "phone": "010-1111"},
   "skills": ["python", "sql"]},
  {"id": 2, "name": "이서연",
   "contact": {"email": "b@x.com", "phone": "010-2222"},
   "skills": ["excel"]}
]

이걸 위와 똑같이 read_jsonto_excel로 변환하면 파일은 만들어집니다. 그런데 엑셀 셀을 열어 보면 묶음과 목록이 글자 그대로 한 칸에 욱여넣어져 있습니다. 실제로 생성된 엑셀의 셀 값을 그대로 꺼내 봤습니다.

contact 열의 셀 : {'email': 'a@x.com', 'phone': '010-1111'}
skills 열의 셀  : ['python', 'sql']

중괄호와 대괄호까지 통째로 한 셀에 들어갑니다. 엑셀에서 이메일만 따로 정렬하거나 거를 수가 없는 상태입니다. 중첩 구조는 표 한 칸에 자연스럽게 펴지지 않는다는 점이 변환의 가장 큰 함정입니다.

3단계. 중첩은 평탄화로 펴 줍니다

이럴 때 pandas의 json_normalize가 묶음을 별도 열로 풀어 줍니다. 점(.)으로 이어진 이름으로 자동으로 펼칩니다.

import json, pandas as pd

with open("nested.json", encoding="utf-8") as f:
    data = json.load(f)

df = pd.json_normalize(data)
df.to_excel("flat_nested.xlsx", index=False)

이렇게 하면 열 구성이 달라집니다. 직접 변환해 본 결과입니다.

변환 방식만들어진 열
그냥 read_jsonid, name, contact, skills
json_normalizeid, name, skills, contact.email, contact.phone

묶음이던 contactcontact.emailcontact.phone 두 열로 갈라졌습니다. 이제 엑셀에서 이메일 열만 따로 다룰 수 있습니다.

다만 한 가지 한계가 있습니다. 목록인 skills는 평탄화 후에도 여전히 ['python', 'sql'] 그대로 한 칸에 남습니다. 묶음(객체)은 열로 펴지지만, 목록(배열)은 행마다 길이가 달라 자동으로 펴지지 않기 때문입니다. 목록을 여러 행으로 나누려면 별도 처리가 더 필요합니다.


그래서 어떻게 판단하면 될까

받은 JSON을 먼저 한 번 열어 모양을 보고 방법을 고르는 편이 빠릅니다.

JSON 모양이렇게
키가 같은 객체가 줄줄이 (값이 다 단순)read_json 후 to_excel
값 안에 묶음(객체)이 들어 있음json_normalize로 평탄화
값 안에 목록(배열)이 들어 있음평탄화 후 목록 열은 따로 처리

중괄호가 한 셀에 통째로 들어갔다면 평탄화가 빠진 것이니, json_normalize로 다시 변환하면 됩니다.

자주 묻는 질문

Q. 코드 없이 엑셀에서 바로 JSON을 열 수는 없나요?
최신 엑셀의 [데이터] 탭에 가져오기 기능이 있어 일부 JSON을 표로 불러올 수 있습니다. 다만 중첩 구조는 단계가 늘어나고 결과가 들쭉날쭉해, 구조가 복잡하면 위 방식이 더 일관됩니다.

Q. 한글이 깨지지 않나요?
위 결과에서 김민수, 영업팀 같은 한글이 엑셀에서 그대로 보였습니다. XLSX는 내부적으로 유니코드를 쓰기 때문에 CSV처럼 인코딩 때문에 깨지는 일은 거의 없습니다.

Q. 시트 이름을 바꾸고 싶어요.
df.to_excel("out.xlsx", sheet_name="직원목록", index=False)처럼 sheet_name을 지정하면 됩니다. 지정하지 않으면 기본값 Sheet1이 됩니다.

 

JSON을 엑셀로 옮기는 일은 단순 변환이 아니라, 그 JSON이 표로 펴지는 모양인지 먼저 살피는 일에 가깝습니다. 같은 데이터 형식 사이의 변환이 궁금하다면, 표 데이터를 CSV와 JSON으로 저장할 때 용량이 왜 달라지는지 다룬 글도 이어서 보시면 구조 차이가 더 또렷해집니다.