·5분 읽기
JSON 깊은 중첩 평탄화 — flatten 함수 5가지 실전 패턴
API 응답·설정 파일·MongoDB 문서의 중첩 JSON을 평탄한 객체로 변환하는 5가지 패턴. 점 표기법·배열 인덱스·역방향 unflatten·에지 케이스까지 정리했어요.

{ }
JSON 포맷터 바로 사용하기
JSON을 예쁘게 정렬하고 검증하세요
→
JSON 평탄화, 왜 필요할까?
API 응답이나 MongoDB 문서를 받다 보면 중첩이 5단계, 10단계까지 깊어질 때가 있어요. 그대로 다루면 코드가 지저분.
```
{
"user": {
"profile": {
"address": {
"city": "Seoul"
}
}
}
}
```
평탄화하면.
```
{
"user.profile.address.city": "Seoul"
}
```
언제 쓰나.
1. **CSV/엑셀 변환** — 표 형식에 넣으려면 1단계 객체 필요
2. **MongoDB 부분 업데이트** — `$set: { 'user.profile.city': 'Seoul' }`
3. **로그 인덱싱** — Elasticsearch는 평탄한 키-값 인덱싱이 빠름
4. **i18n 번역 키** — `home.nav.signup` 같은 점 표기법이 표준
5. **설정 파일 비교** — 두 yaml/json 차이 비교 시 평탄화하면 한 줄씩
오늘은 5가지 실전 패턴을 코드와 함께 정리할게요.
패턴 1) 기본 평탄화 — 점 표기법
가장 흔한 패턴. 중첩 키를 점으로 연결.
입력.
```
{
"a": 1,
"b": { "c": 2, "d": { "e": 3 } }
}
```
출력.
```
{
"a": 1,
"b.c": 2,
"b.d.e": 3
}
```
JavaScript 구현 (재귀).
```
function flatten(obj, prefix = '', result = {}) {
for (const key in obj) {
const newKey = prefix ? prefix + '.' + key : key;
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
flatten(obj[key], newKey, result);
} else {
result[newKey] = obj[key];
}
}
return result;
}
```
주의. `null`·배열·Date는 객체로 안 들어감 (그대로 값으로). 이 처리가 빠지면 무한 재귀.
패턴 2) 배열 포함 — 인덱스 표기법
배열까지 평탄화하려면 인덱스를 키에 포함.
입력.
```
{
"users": [
{ "name": "김", "age": 30 },
{ "name": "이", "age": 25 }
]
}
```
출력 (점 인덱스).
```
{
"users.0.name": "김",
"users.0.age": 30,
"users.1.name": "이",
"users.1.age": 25
}
```
출력 (대괄호 인덱스 — JSONPath 스타일).
```
{
"users[0].name": "김",
"users[0].age": 30
}
```
JavaScript.
```
function flatten(obj, prefix = '', result = {}) {
for (const key in obj) {
const isArray = Array.isArray(obj);
const newKey = isArray
? (prefix ? prefix + '[' + key + ']' : '[' + key + ']')
: (prefix ? prefix + '.' + key : key);
if (typeof obj[key] === 'object' && obj[key] !== null) {
flatten(obj[key], newKey, result);
} else {
result[newKey] = obj[key];
}
}
return result;
}
```
MongoDB는 `users.0.name` 점 표기법, JSONPath는 `users[0].name` 대괄호. 용도에 맞춰 선택.
패턴 3) unflatten — 역방향 복원
평탄한 객체를 다시 중첩으로. 점·대괄호 표기법을 파싱.
입력.
```
{
"user.profile.city": "Seoul",
"user.profile.zip": "03000"
}
```
출력.
```
{
"user": {
"profile": {
"city": "Seoul",
"zip": "03000"
}
}
}
```
JavaScript.
```
function unflatten(flat) {
const result = {};
for (const key in flat) {
const parts = key.split('.');
let cur = result;
for (let i = 0; i < parts.length - 1; i++) {
if (!cur[parts[i]]) cur[parts[i]] = {};
cur = cur[parts[i]];
}
cur[parts[parts.length - 1]] = flat[key];
}
return result;
}
```
주의. `users.0.name`처럼 숫자가 섞이면 객체 vs 배열 구분 어려움. 정수면 배열로 만들지, 객체로 둘지 결정해야.
해결. `parseInt(part) >= 0 && !isNaN(part)`이면 배열 인덱스로 처리 — 단 `'01'` 같은 0-prefix 숫자는 객체 키로.
패턴 4) 깊이 제한 — 너무 깊으면 끊기
10단계 이상 중첩되면 키가 너무 길어져 가독성 저하. 깊이 제한.
```
function flatten(obj, prefix = '', result = {}, depth = 0, maxDepth = 3) {
for (const key in obj) {
const newKey = prefix ? prefix + '.' + key : key;
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key]) && depth < maxDepth - 1) {
flatten(obj[key], newKey, result, depth + 1, maxDepth);
} else {
result[newKey] = obj[key];
}
}
return result;
}
```
사용 예. `flatten(data, '', {}, 0, 2)` → 2단계까지만 평탄화, 그 이후는 중첩 객체 그대로 값으로.
사례. Elasticsearch 인덱싱은 보통 3~5단계 권장. 그 이상은 검색 성능 저하 + mapping 폭증. CSV 변환은 2단계까지면 충분한 경우 많음.
lodash의 `_.flatten`은 1단계만 평탄화. 깊은 평탄화는 `_.flattenDeep`. 객체 기준 평탄화는 `flat` npm 라이브러리 사용 권장.
패턴 5) 키 변환 — 케이스 통일
API 응답이 camelCase·snake_case 섞여 있으면 평탄화하면서 통일.
입력.
```
{
"user_info": {
"firstName": "길동",
"last_name": "홍"
}
}
```
출력 (모두 snake_case).
```
{
"user_info.first_name": "길동",
"user_info.last_name": "홍"
}
```
JavaScript.
```
function toSnake(key) {
return key.replace(/[A-Z]/g, c => '_' + c.toLowerCase()).replace(/^_/, '');
}
function flattenSnake(obj, prefix = '', result = {}) {
for (const key in obj) {
const newKey = prefix ? prefix + '.' + toSnake(key) : toSnake(key);
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
flattenSnake(obj[key], newKey, result);
} else {
result[newKey] = obj[key];
}
}
return result;
}
```
케이스 변환 자세한 가이드는 [대소문자 변환 — camelCase·snake_case·kebab-case](/blog/case-converter-camel-snake-kebab-5-differences)에서 이어 보세요.
에지 케이스 — null·undefined·순환참조
실전에서 부딪히는 6가지 함정.
1. **null 값**: `typeof null === 'object'` 함정. `obj[key] === null` 체크 필수
2. **undefined**: 명시적으로 키에 들어있을 때 — JSON.stringify는 빼버림. 평탄화는 둘 중 선택
3. **Date 객체**: `typeof === 'object'`라 재귀 들어가면 ISO 문자열로. `instanceof Date` 체크
4. **순환참조**: a.b = a면 무한 재귀. WeakSet으로 방문 추적
5. **빈 객체 `{}`**: 평탄화 시 키가 사라짐. 옵션으로 빈 객체 보존 여부 결정
6. **배열 안 객체**: `[1, {a:2}, 3]` 같은 혼합. 인덱스 표기법으로 처리
WeakSet으로 순환 방지.
```
function flatten(obj, prefix = '', result = {}, seen = new WeakSet()) {
if (seen.has(obj)) return result;
seen.add(obj);
...
}
```
빈 객체 보존. `Object.keys(obj).length === 0`일 때 `result[prefix] = {}` 추가.
Toolkio JSON 포맷터로 평탄화 시각화
코드 짜기 전에 [Toolkio JSON 포맷터](https://toolkio.com/tools/json-formatter)에 붙여넣고 트리뷰로 구조 파악하면 평탄화 후 어떻게 될지 미리 그림이 그려져요.
작업 흐름.
1. JSON 붙여넣기 → 포맷팅 + 트리뷰 표시
2. 깊이 단계 카운트 (예: 4단계)
3. 평탄화 후 키 개수 예상
4. JavaScript 코드로 변환 후 결과 다시 붙여넣어 검증
주의. 평탄화 후 키 개수가 너무 많으면(1만 개+) Elasticsearch mapping explosion 위험. 깊이 제한 또는 특정 path만 평탄화 권장.
MongoDB `$set` 업데이트 만들 때도 활용. 평탄화한 객체를 그대로 `$set`에 넘기면 부분 업데이트가 한 번에. 더 자세한 JSON 활용은 [JSON 포맷터 5가지 활용](/blog/json-formatter-5-uses-api-debug-developer)에서, 디버깅은 [JSON 린트·디버그 5단계](/blog/json-lint-debug-5-steps-syntax-error)에서 이어 보세요.