고급 JSON 구조: 복잡한 데이터 모델링 마스터하기
복잡한 데이터 구조를 JSON으로 효과적으로 표현하는 방법을 배워보세요. 그래프, 트리, 다형성, 참조 처리까지 완벽 정리.
Big JSON Team
• Technical WriterExpert in JSON data manipulation, API development, and web technologies. Passionate about creating tools that make developers' lives easier.
# 고급 JSON 구조: 복잡한 데이터 모델링 마스터하기
JSON의 기본을 넘어 복잡한 데이터 구조를 효과적으로 표현하고 처리하는 고급 기법을 마스터하세요.
트리 구조
중첩 객체로 트리 표현
{
"id": 1,
"name": "루트",
"children": [
{
"id": 2,
"name": "자식1",
"children": [
{
"id": 4,
"name": "손자1",
"children": []
}
]
},
{
"id": 3,
"name": "자식2",
"children": []
}
]
}
재귀적 순회
function traverseTree(node, callback) {
callback(node);
if (node.children && node.children.length > 0) {
node.children.forEach(child => {
traverseTree(child, callback);
});
}
}
// 사용
traverseTree(tree, (node) => {
console.log(node.name);
});
깊이 우선 탐색 (DFS)
function dfs(node, targetId) {
if (node.id === targetId) {
return node;
}
for (const child of node.children || []) {
const result = dfs(child, targetId);
if (result) return result;
}
return null;
}
너비 우선 탐색 (BFS)
function bfs(root, targetId) {
const queue = [root];
while (queue.length > 0) {
const node = queue.shift();
if (node.id === targetId) {
return node;
}
if (node.children) {
queue.push(...node.children);
}
}
return null;
}
플랫 배열로 트리 표현
[
{ "id": 1, "name": "루트", "parentId": null },
{ "id": 2, "name": "자식1", "parentId": 1 },
{ "id": 3, "name": "자식2", "parentId": 1 },
{ "id": 4, "name": "손자1", "parentId": 2 }
]
장점:
- 쿼리 쉬움
- 업데이트 간편
- 순환 참조 가능
function buildTree(flatData) {
const map = {};
const roots = [];
// 맵 생성
flatData.forEach(item => {
map[item.id] = { ...item, children: [] };
});
// 트리 구성
flatData.forEach(item => {
if (item.parentId === null) {
roots.push(map[item.id]);
} else {
map[item.parentId].children.push(map[item.id]);
}
});
return roots;
}
그래프 구조
인접 리스트
{
"nodes": [
{ "id": "A", "label": "노드 A" },
{ "id": "B", "label": "노드 B" },
{ "id": "C", "label": "노드 C" }
],
"edges": [
{ "from": "A", "to": "B", "weight": 5 },
{ "from": "A", "to": "C", "weight": 3 },
{ "from": "B", "to": "C", "weight": 2 }
]
}
그래프 순회
function buildAdjacencyList(graph) {
const adj = {};
graph.nodes.forEach(node => {
adj[node.id] = [];
});
graph.edges.forEach(edge => {
adj[edge.from].push({
to: edge.to,
weight: edge.weight
});
});
return adj;
}
function dfsGraph(adj, start, visited = new Set()) {
if (visited.has(start)) return;
console.log(start);
visited.add(start);
for (const neighbor of adj[start] || []) {
dfsGraph(adj, neighbor.to, visited);
}
}
// 사용
const adj = buildAdjacencyList(graph);
dfsGraph(adj, 'A');
최단 경로 (Dijkstra)
function dijkstra(graph, start) {
const adj = buildAdjacencyList(graph);
const distances = {};
const visited = new Set();
const pq = [{ node: start, distance: 0 }];
// 초기화
graph.nodes.forEach(node => {
distances[node.id] = Infinity;
});
distances[start] = 0;
while (pq.length > 0) {
pq.sort((a, b) => a.distance - b.distance);
const { node, distance } = pq.shift();
if (visited.has(node)) continue;
visited.add(node);
for (const neighbor of adj[node] || []) {
const newDist = distance + neighbor.weight;
if (newDist < distances[neighbor.to]) {
distances[neighbor.to] = newDist;
pq.push({ node: neighbor.to, distance: newDist });
}
}
}
return distances;
}
다형성 (Polymorphism)
타입 필드 사용
{
"shapes": [
{
"type": "circle",
"radius": 5,
"color": "red"
},
{
"type": "rectangle",
"width": 10,
"height": 5,
"color": "blue"
},
{
"type": "triangle",
"base": 8,
"height": 6,
"color": "green"
}
]
}
타입별 처리
function calculateArea(shape) {
switch (shape.type) {
case 'circle':
return Math.PI shape.radius 2;
case 'rectangle':
return shape.width shape.height;
case 'triangle':
return (shape.base shape.height) / 2;
default:
throw new Error(알 수 없는 타입: ${shape.type});
}
}
// 사용
const shapes = data.shapes;
const areas = shapes.map(calculateArea);
console.log('면적:', areas);
TypeScript에서 타입 가드
type Circle = {
type: 'circle';
radius: number;
};
type Rectangle = {
type: 'rectangle';
width: number;
height: number;
};
type Shape = Circle | Rectangle;
function isCircle(shape: Shape): shape is Circle {
return shape.type === 'circle';
}
function calculateArea(shape: Shape): number {
if (isCircle(shape)) {
return Math.PI shape.radius * 2;
} else {
return shape.width shape.height;
}
}
참조와 포인터
ID 기반 참조
{
"users": [
{ "id": 1, "name": "홍길동" },
{ "id": 2, "name": "김철수" }
],
"posts": [
{
"id": 101,
"title": "첫 게시물",
"authorId": 1,
"commentIds": [201, 202]
}
],
"comments": [
{
"id": 201,
"text": "좋은 글이네요",
"authorId": 2,
"postId": 101
},
{
"id": 202,
"text": "감사합니다",
"authorId": 1,
"postId": 101
}
]
}
참조 해결 (Resolution)
function resolveReferences(data) {
const users = new Map(data.users.map(u => [u.id, u]));
const posts = new Map(data.posts.map(p => [p.id, p]));
const comments = new Map(data.comments.map(c => [c.id, c]));
// 게시물에 작성자와 댓글 추가
return data.posts.map(post => ({
...post,
author: users.get(post.authorId),
comments: post.commentIds.map(id => ({
...comments.get(id),
author: users.get(comments.get(id).authorId)
}))
}));
}
const resolved = resolveReferences(data);
console.log(resolved[0].author.name); // "홍길동"
GraphQL 스타일 중첩
{
"post": {
"id": 101,
"title": "첫 게시물",
"author": {
"id": 1,
"name": "홍길동",
"posts": [
{ "id": 101, "title": "첫 게시물" }
]
},
"comments": [
{
"id": 201,
"text": "좋은 글이네요",
"author": {
"id": 2,
"name": "김철수"
}
}
]
}
}
시계열 데이터
타임스탬프 기반
{
"metrics": [
{
"timestamp": "2026-01-16T10:00:00Z",
"cpu": 45.2,
"memory": 68.5,
"disk": 72.1
},
{
"timestamp": "2026-01-16T10:01:00Z",
"cpu": 47.8,
"memory": 69.2,
"disk": 72.3
}
]
}
집계 및 분석
function aggregateByHour(metrics) {
const hourly = {};
metrics.forEach(metric => {
const hour = new Date(metric.timestamp).setMinutes(0, 0, 0);
if (!hourly[hour]) {
hourly[hour] = {
count: 0,
cpuSum: 0,
memorySum: 0,
diskSum: 0
};
}
hourly[hour].count++;
hourly[hour].cpuSum += metric.cpu;
hourly[hour].memorySum += metric.memory;
hourly[hour].diskSum += metric.disk;
});
return Object.entries(hourly).map(([hour, stats]) => ({
hour: new Date(parseInt(hour)),
avgCpu: stats.cpuSum / stats.count,
avgMemory: stats.memorySum / stats.count,
avgDisk: stats.diskSum / stats.count
}));
}
지리 데이터 (GeoJSON)
Point
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [126.9780, 37.5665]
},
"properties": {
"name": "서울",
"population": 9776000
}
}
LineString
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[126.9780, 37.5665],
[129.0756, 35.1796]
]
},
"properties": {
"route": "서울-부산"
}
}
Polygon
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[126.9, 37.5],
[127.0, 37.5],
[127.0, 37.6],
[126.9, 37.6],
[126.9, 37.5]
]
]
},
"properties": {
"name": "강남구"
}
}
버전 관리
명시적 버전
{
"version": "2.0",
"data": {
"users": [
{
"id": 1,
"name": "홍길동",
"email": "hong@example.com"
}
]
}
}
마이그레이션
function migrate(data) {
const version = data.version || '1.0';
switch (version) {
case '1.0':
// v1 to v2
data = migrateV1toV2(data);
// fall through
case '2.0':
// v2 to v3
data = migrateV2toV3(data);
break;
default:
console.log('최신 버전');
}
return data;
}
function migrateV1toV2(data) {
return {
version: '2.0',
data: {
users: data.users.map(user => ({
id: user.id,
name: user.name,
email: user.contact // 필드 이름 변경
}))
}
};
}
스키마 검증
JSON Schema로 복잡한 구조 검증
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"nodes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"type": {
"enum": ["circle", "rectangle"]
}
},
"required": ["id", "type"],
"allOf": [
{
"if": {
"properties": { "type": { "const": "circle" } }
},
"then": {
"properties": {
"radius": { "type": "number", "minimum": 0 }
},
"required": ["radius"]
}
},
{
"if": {
"properties": { "type": { "const": "rectangle" } }
},
"then": {
"properties": {
"width": { "type": "number", "minimum": 0 },
"height": { "type": "number", "minimum": 0 }
},
"required": ["width", "height"]
}
}
]
}
}
}
}
성능 최적화
인덱싱
class IndexedData {
constructor(data) {
this.data = data;
this.indexes = {};
}
createIndex(field) {
this.indexes[field] = new Map();
this.data.forEach((item, index) => {
const value = item[field];
if (!this.indexes[field].has(value)) {
this.indexes[field].set(value, []);
}
this.indexes[field].get(value).push(index);
});
}
findByIndex(field, value) {
if (!this.indexes[field]) {
throw new Error(인덱스가 없습니다: ${field});
}
const indices = this.indexes[field].get(value) || [];
return indices.map(i => this.data[i]);
}
}
// 사용
const indexed = new IndexedData(users);
indexed.createIndex('city');
const seoulUsers = indexed.findByIndex('city', '서울');
캐싱
class CachedQuery {
constructor(data) {
this.data = data;
this.cache = new Map();
}
query(predicate) {
const key = predicate.toString();
if (this.cache.has(key)) {
console.log('캐시 히트');
return this.cache.get(key);
}
const result = this.data.filter(predicate);
this.cache.set(key, result);
return result;
}
clearCache() {
this.cache.clear();
}
}
압축 기법
키 압축
// 원본
const original = [
{ "firstName": "홍", "lastName": "길동", "age": 30 },
{ "firstName": "김", "lastName": "철수", "age": 25 }
];
// 압축
const compressed = {
"keys": ["firstName", "lastName", "age"],
"data": [
["홍", "길동", 30],
["김", "철수", 25]
]
};
// 압축 해제
function decompress(compressed) {
return compressed.data.map(values => {
const obj = {};
compressed.keys.forEach((key, i) => {
obj[key] = values[i];
});
return obj;
});
}
델타 인코딩
// 시계열 데이터
const timeseries = [
{ timestamp: 1642348800, value: 100 },
{ timestamp: 1642348860, value: 102 },
{ timestamp: 1642348920, value: 105 }
];
// 델타 인코딩
const delta = {
base: { timestamp: 1642348800, value: 100 },
deltas: [
{ timestamp: 60, value: 2 },
{ timestamp: 60, value: 3 }
]
};
결론
고급 JSON 구조를 마스터하면 복잡한 데이터를 효율적으로 표현하고 처리할 수 있습니다. 적절한 구조 선택이 성능과 유지보수성에 큰 영향을 미칩니다.
핵심 요약:- ✅ 트리는 중첩 또는 플랫 구조로
- ✅ 그래프는 인접 리스트로
- ✅ 다형성은 타입 필드로
- ✅ 참조는 ID 기반으로
- ✅ 성능은 인덱싱과 캐싱으로
지금 바로 JSON Simplify에서 복잡한 JSON을 시각화해보세요!
관련 글
JSON Schema 이해하기: 완벽한 가이드 2026
JSON Schema로 데이터를 검증하고 문서화하는 방법을 배우세요. 기본부터 고급 패턴까지 다루는 종합 가이드입니다.
JSON 경로 찾기: JSONPath 완벽 가이드와 실용 도구
JSON 데이터에서 원하는 값의 경로를 찾고 JSONPath를 마스터하세요. 실용적인 예제와 도구를 통해 완벽히 배워봅니다.
대용량 JSON 다루기: 성능 최적화와 실용 기법
기가바이트 크기의 JSON 파일을 효율적으로 처리하는 방법을 배워보세요. 스트리밍, 청크 처리, 메모리 최적화까지 완벽 정리.