개발자를 위한 JSON 데이터 처리 가이드
JSON(JavaScript Object Notation)은 현대 웹 개발의 표준 데이터 교환 형식입니다. API 통신, 설정 파일, 데이터 저장 등 모든 곳에서 사용되는 JSON을 제대로 다루는 것은 필수 개발 역량입니다.
1. JSON 기본 개념과 구조 {#json-basics}
JSON의 핵심 특징
경량성과 가독성
{
"user": {
"id": 12345,
"name": "김개발",
"email": "dev@example.com",
"preferences": {
"theme": "dark",
"language": "ko",
"notifications": true
},
"skills": ["JavaScript", "Python", "React"],
"lastLogin": "2025-07-31T10:30:00Z"
}
}
언어 독립적 표준
- JavaScript에서 시작되었지만 모든 주요 프로그래밍 언어 지원
- HTTP API의 사실상 표준 데이터 형식
- XML 대비 40-50% 적은 데이터 크기
- 파싱 속도가 XML 대비 3-5배 빠름
JSON 데이터 타입과 구조
기본 데이터 타입
const jsonDataTypes = {
// 문자열 (반드시 큰따옴표 사용)
string: "Hello World",
// 숫자 (정수, 소수, 지수 표기법)
number: 42,
decimal: 3.14159,
scientific: 1.23e-4,
// 불린
boolean: true,
// null (undefined는 JSON에 없음)
null_value: null,
// 배열
array: [1, 2, 3, "mixed", true, null],
// 객체 (중첩 가능)
object: {
"nested": {
"deeply": {
"value": "found"
}
}
}
};
JSON vs JavaScript 객체 차이점
// JavaScript 객체 (유효하지만 JSON이 아님)
const jsObject = {
name: 'John', // 키에 따옴표 없음
age: undefined, // undefined 사용
date: new Date(), // Date 객체
func: function() {}, // 함수
comment: 'this works' // 단일 따옴표
};
// 유효한 JSON
const validJSON = {
"name": "John", // 키에 큰따옴표 필수
"age": null, // undefined 대신 null
"date": "2025-07-31", // 문자열로 표현
"comment": "this works" // 큰따옴표만 허용
};
실제 JSON 구조 예시
REST API 응답
{
"status": "success",
"code": 200,
"message": "사용자 정보를 성공적으로 조회했습니다",
"data": {
"user": {
"id": "user_123",
"profile": {
"name": "김개발",
"avatar": "https://example.com/avatar.jpg",
"bio": "풀스택 개발자입니다",
"location": "서울, 대한민국"
},
"stats": {
"followers": 1250,
"following": 340,
"posts": 89,
"joinedAt": "2023-01-15T09:00:00.000Z"
},
"preferences": {
"privacy": {
"profilePublic": true,
"emailVisible": false,
"showOnlineStatus": true
},
"notifications": {
"email": ["mentions", "followers"],
"push": ["messages", "comments"],
"frequency": "daily"
}
}
}
},
"meta": {
"requestId": "req_abc123",
"timestamp": "2025-07-31T12:34:56.789Z",
"executionTime": "23ms",
"rateLimit": {
"remaining": 97,
"resetAt": "2025-07-31T13:00:00.000Z"
}
}
}
설정 파일 (package.json 스타일)
{
"name": "my-awesome-project",
"version": "1.2.3",
"description": "최고의 웹 애플리케이션",
"main": "index.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "jest",
"build": "webpack --mode production",
"lint": "eslint . --fix"
},
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.5.0",
"jsonwebtoken": "^9.0.2"
},
"devDependencies": {
"nodemon": "^3.0.1",
"jest": "^29.6.2",
"eslint": "^8.45.0"
},
"engines": {
"node": ">=16.0.0",
"npm": ">=8.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/username/my-awesome-project"
},
"keywords": ["nodejs", "express", "api", "mongodb"],
"author": {
"name": "김개발",
"email": "dev@example.com",
"url": "https://kimdev.blog"
},
"license": "MIT"
}
2. 검증과 파싱 기법 {#validation-parsing}
JSON 유효성 검증
기본 파싱과 에러 처리
class JSONValidator {
static parse(jsonString) {
try {
const parsed = JSON.parse(jsonString);
return {
success: true,
data: parsed,
error: null
};
} catch (error) {
return {
success: false,
data: null,
error: this.categorizeError(error, jsonString)
};
}
}
static categorizeError(error, jsonString) {
const errorInfo = {
name: error.name,
message: error.message,
type: 'unknown',
position: null,
suggestion: null
};
// 구문 오류 분석
if (error.message.includes('position')) {
const match = error.message.match(/position (\d+)/);
if (match) {
errorInfo.position = parseInt(match[1]);
errorInfo.context = this.getContextAroundPosition(jsonString, errorInfo.position);
}
}
// 일반적인 오류 패턴 분석
if (error.message.includes('Unexpected token')) {
errorInfo.type = 'syntax_error';
errorInfo.suggestion = '문법 오류: 따옴표, 쉼표, 괄호를 확인하세요';
} else if (error.message.includes('Unexpected end')) {
errorInfo.type = 'incomplete_json';
errorInfo.suggestion = 'JSON이 완전하지 않습니다. 닫는 괄호를 확인하세요';
} else if (error.message.includes('Unexpected string')) {
errorInfo.type = 'quote_error';
errorInfo.suggestion = '키 이름에 큰따옴표가 필요합니다';
}
return errorInfo;
}
static getContextAroundPosition(str, position, radius = 20) {
const start = Math.max(0, position - radius);
const end = Math.min(str.length, position + radius);
const before = str.substring(start, position);
const after = str.substring(position, end);
return {
before,
after,
character: str[position] || 'EOF',
line: str.substring(0, position).split('\n').length,
column: position - str.lastIndexOf('\n', position - 1)
};
}
}
// 사용 예시
const invalidJSON = `{
"name": "John",
"age": 30,
"city": "Seoul" // 마지막 쉼표 누락
"country": "Korea"
}`;
const result = JSONValidator.parse(invalidJSON);
console.log(result);
// {
// success: false,
// error: {
// type: 'syntax_error',
// suggestion: '문법 오류: 따옴표, 쉼표, 괄호를 확인하세요',
// position: 45,
// context: { ... }
// }
// }
고급 JSON 스키마 검증
class JSONSchemaValidator {
constructor(schema) {
this.schema = schema;
}
validate(data) {
const errors = [];
this.validateObject(data, this.schema, '', errors);
return {
valid: errors.length === 0,
errors: errors,
data: data
};
}
validateObject(obj, schema, path, errors) {
// 타입 검증
if (schema.type && typeof obj !== schema.type) {
errors.push({
path: path,
message: `Expected ${schema.type}, got ${typeof obj}`,
value: obj
});
return;
}
// 필수 필드 검증
if (schema.required) {
schema.required.forEach(field => {
if (!(field in obj)) {
errors.push({
path: path + '.' + field,
message: `Required field missing: ${field}`,
value: undefined
});
}
});
}
// 속성별 검증
if (schema.properties) {
Object.keys(schema.properties).forEach(key => {
if (key in obj) {
const newPath = path ? `${path}.${key}` : key;
this.validateField(obj[key], schema.properties[key], newPath, errors);
}
});
}
// 추가 속성 검증
if (schema.additionalProperties === false) {
const allowedKeys = Object.keys(schema.properties || {});
Object.keys(obj).forEach(key => {
if (!allowedKeys.includes(key)) {
errors.push({
path: path + '.' + key,
message: `Additional property not allowed: ${key}`,
value: obj[key]
});
}
});
}
}
validateField(value, fieldSchema, path, errors) {
// 타입별 상세 검증
switch (fieldSchema.type) {
case 'string':
this.validateString(value, fieldSchema, path, errors);
break;
case 'number':
this.validateNumber(value, fieldSchema, path, errors);
break;
case 'array':
this.validateArray(value, fieldSchema, path, errors);
break;
case 'object':
this.validateObject(value, fieldSchema, path, errors);
break;
}
}
validateString(str, schema, path, errors) {
if (schema.minLength && str.length < schema.minLength) {
errors.push({
path: path,
message: `String too short. Minimum length: ${schema.minLength}`,
value: str
});
}
if (schema.maxLength && str.length > schema.maxLength) {
errors.push({
path: path,
message: `String too long. Maximum length: ${schema.maxLength}`,
value: str
});
}
if (schema.pattern && !new RegExp(schema.pattern).test(str)) {
errors.push({
path: path,
message: `String does not match pattern: ${schema.pattern}`,
value: str
});
}
if (schema.enum && !schema.enum.includes(str)) {
errors.push({
path: path,
message: `Value must be one of: ${schema.enum.join(', ')}`,
value: str
});
}
}
}
// 사용 예시
const userSchema = {
type: 'object',
required: ['name', 'email'],
properties: {
name: {
type: 'string',
minLength: 2,
maxLength: 50
},
email: {
type: 'string',
pattern: '^[^@]+@[^@]+\\.[^@]+$'
},
age: {
type: 'number',
minimum: 0,
maximum: 150
},
role: {
type: 'string',
enum: ['user', 'admin', 'moderator']
}
},
additionalProperties: false
};
const validator = new JSONSchemaValidator(userSchema);
const result = validator.validate({
name: 'Kim',
email: 'invalid-email',
age: 25,
role: 'user',
extra: 'not allowed'
});
console.log(result.errors);
// [
// {
// path: 'email',
// message: 'String does not match pattern: ^[^@]+@[^@]+\\.[^@]+$',
// value: 'invalid-email'
// },
// {
// path: 'extra',
// message: 'Additional property not allowed: extra',
// value: 'not allowed'
// }
// ]
안전한 JSON 파싱
보안 고려사항
class SecureJSONParser {
constructor(options = {}) {
this.maxDepth = options.maxDepth || 10;
this.maxKeys = options.maxKeys || 1000;
this.maxStringLength = options.maxStringLength || 1000000;
this.maxArrayLength = options.maxArrayLength || 10000;
}
parse(jsonString) {
// 크기 제한 검사
if (jsonString.length > this.maxStringLength) {
throw new Error(`JSON string too large: ${jsonString.length} > ${this.maxStringLength}`);
}
let parsed;
try {
parsed = JSON.parse(jsonString);
} catch (error) {
throw new Error(`Invalid JSON: ${error.message}`);
}
// 구조 검증
this.validateStructure(parsed, 0);
return parsed;
}
validateStructure(obj, depth) {
if (depth > this.maxDepth) {
throw new Error(`JSON nesting too deep: ${depth} > ${this.maxDepth}`);
}
if (Array.isArray(obj)) {
if (obj.length > this.maxArrayLength) {
throw new Error(`Array too large: ${obj.length} > ${this.maxArrayLength}`);
}
obj.forEach(item => this.validateStructure(item, depth + 1));
} else if (obj && typeof obj === 'object') {
const keys = Object.keys(obj);
if (keys.length > this.maxKeys) {
throw new Error(`Too many object keys: ${keys.length} > ${this.maxKeys}`);
}
keys.forEach(key => {
if (key.length > 100) { // 키 길이 제한
throw new Error(`Object key too long: ${key.length} > 100`);
}
this.validateStructure(obj[key], depth + 1);
});
}
}
// 안전한 stringify (순환 참조 처리)
stringify(obj, space = null) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[Circular Reference]';
}
seen.add(value);
}
// 함수나 undefined 값 처리
if (typeof value === 'function') {
return '[Function]';
}
if (value === undefined) {
return '[Undefined]';
}
return value;
}, space);
}
}
// 사용 예시
const secureParser = new SecureJSONParser({
maxDepth: 5,
maxKeys: 100,
maxStringLength: 50000
});
try {
const data = secureParser.parse(dangerousJSON);
console.log('안전하게 파싱됨:', data);
} catch (error) {
console.error('파싱 실패:', error.message);
}
3. 고급 처리 기법 {#advanced-techniques}
JSON 스트리밍 처리
대용량 JSON 파일 스트리밍
class JSONStreamProcessor {
constructor() {
this.buffer = '';
this.depth = 0;
this.inString = false;
this.escapeNext = false;
this.objectStack = [];
}
// 청크 단위로 JSON 처리
processChunk(chunk) {
this.buffer += chunk;
const results = [];
let i = 0;
while (i < this.buffer.length) {
const char = this.buffer[i];
// 문자열 내부 처리
if (this.inString) {
if (this.escapeNext) {
this.escapeNext = false;
} else if (char === '\\') {
this.escapeNext = true;
} else if (char === '"') {
this.inString = false;
}
} else {
// 문자열 시작
if (char === '"') {
this.inString = true;
}
// 객체/배열 시작
else if (char === '{' || char === '[') {
this.depth++;
this.objectStack.push({
type: char === '{' ? 'object' : 'array',
start: i
});
}
// 객체/배열 끝
else if (char === '}' || char === ']') {
this.depth--;
if (this.depth === 0 && this.objectStack.length > 0) {
// 완전한 객체 발견
const start = this.objectStack[0].start;
const jsonStr = this.buffer.substring(start, i + 1);
try {
const parsed = JSON.parse(jsonStr);
results.push(parsed);
} catch (error) {
console.error('JSON 파싱 오류:', error.message);
}
// 버퍼에서 처리된 부분 제거
this.buffer = this.buffer.substring(i + 1);
this.objectStack = [];
i = -1; // 루프 재시작
}
}
}
i++;
}
return results;
}
// 스트림 완료 처리
flush() {
if (this.buffer.trim()) {
console.warn('처리되지 않은 데이터가 남아있습니다:', this.buffer);
}
}
}
// Node.js 스트림과 함께 사용
const fs = require('fs');
const path = require('path');
async function processLargeJSONFile(filePath) {
const processor = new JSONStreamProcessor();
const stream = fs.createReadStream(filePath, { encoding: 'utf8', highWaterMark: 16 * 1024 });
return new Promise((resolve, reject) => {
const results = [];
stream.on('data', (chunk) => {
const objects = processor.processChunk(chunk);
results.push(...objects);
});
stream.on('end', () => {
processor.flush();
resolve(results);
});
stream.on('error', reject);
});
}
JSON 변환과 매핑
복잡한 데이터 변환
class JSONTransformer {
constructor(transformRules) {
this.rules = transformRules;
}
transform(data) {
return this.applyRules(data, this.rules);
}
applyRules(data, rules) {
if (Array.isArray(data)) {
return data.map(item => this.applyRules(item, rules));
}
if (data && typeof data === 'object') {
const result = {};
Object.entries(rules).forEach(([targetKey, rule]) => {
if (typeof rule === 'string') {
// 단순 키 매핑
result[targetKey] = this.getNestedValue(data, rule);
} else if (typeof rule === 'function') {
// 함수를 통한 변환
result[targetKey] = rule(data);
} else if (rule.source) {
// 복합 규칙
let value = this.getNestedValue(data, rule.source);
// 타입 변환
if (rule.type) {
value = this.convertType(value, rule.type);
}
// 기본값 설정
if (value === undefined && rule.default !== undefined) {
value = rule.default;
}
// 검증
if (rule.validate && !rule.validate(value)) {
throw new Error(`Validation failed for ${targetKey}: ${value}`);
}
result[targetKey] = value;
}
});
return result;
}
return data;
}
getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => {
return current && current[key] !== undefined ? current[key] : undefined;
}, obj);
}
convertType(value, type) {
switch (type) {
case 'string':
return String(value);
case 'number':
return Number(value);
case 'boolean':
return Boolean(value);
case 'date':
return new Date(value);
case 'array':
return Array.isArray(value) ? value : [value];
default:
return value;
}
}
}
// 사용 예시: API 응답을 프론트엔드 형식으로 변환
const apiResponse = {
user_info: {
first_name: "김",
last_name: "개발",
email_address: "dev@example.com",
created_date: "2023-01-15T09:00:00Z",
is_active: "true",
profile_data: {
avatar_url: "https://example.com/avatar.jpg",
bio_text: "풀스택 개발자"
}
}
};
const transformRules = {
id: 'user_info.id',
name: (data) => `${data.user_info.first_name}${data.user_info.last_name}`,
email: 'user_info.email_address',
avatar: 'user_info.profile_data.avatar_url',
bio: 'user_info.profile_data.bio_text',
isActive: {
source: 'user_info.is_active',
type: 'boolean'
},
joinedAt: {
source: 'user_info.created_date',
type: 'date'
},
displayName: {
source: 'user_info.display_name',
default: '사용자'
}
};
const transformer = new JSONTransformer(transformRules);
const frontendData = transformer.transform(apiResponse);
console.log(frontendData);
// {
// name: "김개발",
// email: "dev@example.com",
// avatar: "https://example.com/avatar.jpg",
// bio: "풀스택 개발자",
// isActive: true,
// joinedAt: Date object,
// displayName: "사용자"
// }
JSON 패치와 업데이트
RFC 6902 JSON Patch 구현
class JSONPatch {
static apply(document, patches) {
let result = JSON.parse(JSON.stringify(document)); // 깊은 복사
patches.forEach(patch => {
result = this.applyOperation(result, patch);
});
return result;
}
static applyOperation(document, operation) {
const { op, path, value, from } = operation;
switch (op) {
case 'add':
return this.add(document, path, value);
case 'remove':
return this.remove(document, path);
case 'replace':
return this.replace(document, path, value);
case 'move':
return this.move(document, from, path);
case 'copy':
return this.copy(document, from, path);
case 'test':
return this.test(document, path, value) ? document :
(() => { throw new Error(`Test failed at ${path}`) })();
default:
throw new Error(`Unknown operation: ${op}`);
}
}
static add(document, path, value) {
const segments = this.parsePath(path);
const target = this.navigateToParent(document, segments);
const lastSegment = segments[segments.length - 1];
if (Array.isArray(target)) {
const index = lastSegment === '-' ? target.length : parseInt(lastSegment);
target.splice(index, 0, value);
} else {
target[lastSegment] = value;
}
return document;
}
static remove(document, path) {
const segments = this.parsePath(path);
const target = this.navigateToParent(document, segments);
const lastSegment = segments[segments.length - 1];
if (Array.isArray(target)) {
target.splice(parseInt(lastSegment), 1);
} else {
delete target[lastSegment];
}
return document;
}
static replace(document, path, value) {
const segments = this.parsePath(path);
const target = this.navigateToParent(document, segments);
const lastSegment = segments[segments.length - 1];
target[lastSegment] = value;
return document;
}
static parsePath(path) {
if (path === '') return [];
return path.substring(1).split('/').map(segment =>
segment.replace(/~1/g, '/').replace(/~0/g, '~')
);
}
static navigateToParent(document, segments) {
let current = document;
for (let i = 0; i < segments.length - 1; i++) {
const segment = segments[i];
if (Array.isArray(current)) {
current = current[parseInt(segment)];
} else {
current = current[segment];
}
if (current === undefined) {
throw new Error(`Path not found: ${segments.slice(0, i + 1).join('/')}`);
}
}
return current;
}
// 두 JSON 객체의 차이점을 패치로 생성
static createPatch(original, modified) {
const patches = [];
this.generatePatches(original, modified, '', patches);
return patches;
}
static generatePatches(original, modified, path, patches) {
if (original === modified) return;
// 타입이 다르면 replace
if (typeof original !== typeof modified ||
Array.isArray(original) !== Array.isArray(modified)) {
patches.push({ op: 'replace', path, value: modified });
return;
}
if (Array.isArray(modified)) {
// 배열 처리
const maxLength = Math.max(original.length, modified.length);
for (let i = maxLength - 1; i >= 0; i--) {
const currentPath = `${path}/${i}`;
if (i >= modified.length) {
patches.push({ op: 'remove', path: currentPath });
} else if (i >= original.length) {
patches.push({ op: 'add', path: currentPath, value: modified[i] });
} else {
this.generatePatches(original[i], modified[i], currentPath, patches);
}
}
} else if (modified && typeof modified === 'object') {
// 객체 처리
const allKeys = new Set([...Object.keys(original || {}), ...Object.keys(modified)]);
allKeys.forEach(key => {
const currentPath = `${path}/${key.replace(/~/g, '~0').replace(/\//g, '~1')}`;
if (!(key in modified)) {
patches.push({ op: 'remove', path: currentPath });
} else if (!(key in (original || {}))) {
patches.push({ op: 'add', path: currentPath, value: modified[key] });
} else {
this.generatePatches(original[key], modified[key], currentPath, patches);
}
});
} else {
// 원시 값 변경
patches.push({ op: 'replace', path, value: modified });
}
}
}
// 사용 예시
const original = {
user: {
name: "김개발",
age: 30,
skills: ["JavaScript", "Python"]
}
};
const modified = {
user: {
name: "김개발",
age: 31,
skills: ["JavaScript", "Python", "React"],
location: "Seoul"
}
};
// 패치 생성
const patches = JSONPatch.createPatch(original, modified);
console.log(patches);
// [
// { op: 'replace', path: '/user/age', value: 31 },
// { op: 'add', path: '/user/skills/2', value: 'React' },
// { op: 'add', path: '/user/location', value: 'Seoul' }
// ]
// 패치 적용
const result = JSONPatch.apply(original, patches);
console.log(result); // modified와 동일한 결과
4. 성능 최적화와 모범 사례 {#optimization-tips}
성능 최적화 기법
JSON 파싱 성능 향상
class PerformantJSONProcessor {
constructor() {
this.cache = new Map();
this.schemaCache = new Map();
}
// 캐시된 파싱
parseWithCache(jsonString, cacheKey = null) {
const key = cacheKey || this.generateCacheKey(jsonString);
if (this.cache.has(key)) {
return this.cache.get(key);
}
const result = JSON.parse(jsonString);
// 캐시 크기 제한 (LRU 방식)
if (this.cache.size >= 100) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, result);
return result;
}
// 청크 단위 처리로 메모리 사용량 최적화
parseInChunks(largeJSONString, chunkSize = 1024 * 1024) {
const chunks = [];
let currentIndex = 0;
while (currentIndex < largeJSONString.length) {
const chunk = largeJSONString.substring(currentIndex, currentIndex + chunkSize);
chunks.push(chunk);
currentIndex += chunkSize;
}
// Worker 스레드를 사용한 병렬 처리 (Node.js)
return Promise.all(chunks.map(chunk =>
this.processChunkInWorker(chunk)
));
}
// 지연 로딩을 위한 JSON 프록시
createLazyJSON(data) {
const cache = new Map();
return new Proxy(data, {
get(target, property) {
if (cache.has(property)) {
return cache.get(property);
}
const value = target[property];
// 큰 객체나 배열은 지연 처리
if (typeof value === 'object' && value !== null) {
const lazyValue = Array.isArray(value) ?
this.createLazyArray(value) :
this.createLazyJSON(value);
cache.set(property, lazyValue);
return lazyValue;
}
return value;
}
});
}
// 메모리 효율적인 JSON 문자열화
stringifyEfficiently(obj, options = {}) {
const {
maxDepth = 10,
excludeKeys = [],
transformValues = {},
prettify = false
} = options;
const seen = new WeakSet();
let depth = 0;
return JSON.stringify(obj, (key, value) => {
// 제외할 키 필터링
if (excludeKeys.includes(key)) {
return undefined;
}
// 깊이 제한
if (depth > maxDepth) {
return '[Too Deep]';
}
// 순환 참조 처리
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return '[Circular]';
}
seen.add(value);
depth++;
}
// 값 변환
if (transformValues[key]) {
return transformValues[key](value);
}
// 큰 문자열 잘라내기
if (typeof value === 'string' && value.length > 1000) {
return value.substring(0, 997) + '...';
}
return value;
}, prettify ? 2 : 0);
}
generateCacheKey(jsonString) {
// 간단한 해시 함수
let hash = 0;
for (let i = 0; i < jsonString.length; i++) {
const char = jsonString.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // 32비트 정수로 변환
}
return hash.toString();
}
}
메모리 사용량 최적화
class MemoryEfficientJSON {
// 스트리밍 JSON 파서 (대용량 파일용)
static async processLargeFile(filePath, processor) {
const fs = require('fs');
const { pipeline } = require('stream/promises');
const { Transform } = require('stream');
let buffer = '';
let objectCount = 0;
const CHUNK_SIZE = 64 * 1024; // 64KB 청크
const jsonTransform = new Transform({
transform(chunk, encoding, callback) {
buffer += chunk.toString();
// 완전한 JSON 객체를 찾아서 처리
let startIndex = 0;
let braceCount = 0;
let inString = false;
let escapeNext = false;
for (let i = 0; i < buffer.length; i++) {
const char = buffer[i];
if (escapeNext) {
escapeNext = false;
continue;
}
if (char === '\\' && inString) {
escapeNext = true;
continue;
}
if (char === '"') {
inString = !inString;
continue;
}
if (inString) continue;
if (char === '{') {
if (braceCount === 0) startIndex = i;
braceCount++;
} else if (char === '}') {
braceCount--;
if (braceCount === 0) {
// 완전한 객체 발견
const jsonStr = buffer.substring(startIndex, i + 1);
try {
const obj = JSON.parse(jsonStr);
processor(obj, objectCount++);
} catch (error) {
console.error(`JSON 파싱 오류 (객체 ${objectCount}):`, error.message);
}
// 처리된 부분을 버퍼에서 제거
buffer = buffer.substring(i + 1);
i = -1; // for 루프 재시작
}
}
}
callback();
}
});
await pipeline(
fs.createReadStream(filePath),
jsonTransform
);
return objectCount;
}
// 객체 풀링으로 GC 압박 줄이기
static createObjectPool(createFn, resetFn, maxSize = 100) {
const pool = [];
return {
acquire() {
return pool.length > 0 ? pool.pop() : createFn();
},
release(obj) {
if (pool.length < maxSize) {
resetFn(obj);
pool.push(obj);
}
},
size() {
return pool.length;
}
};
}
}
// 사용 예시: 대용량 JSON 로그 파일 처리
async function processLogFile(filePath) {
let processedCount = 0;
let errorCount = 0;
await MemoryEfficientJSON.processLargeFile(filePath, (logEntry, index) => {
try {
// 로그 엔트리 처리
if (logEntry.level === 'error') {
errorCount++;
console.log(`Error ${errorCount}:`, logEntry.message);
}
processedCount++;
// 진행 상황 표시
if (processedCount % 1000 === 0) {
console.log(`Processed ${processedCount} entries, ${errorCount} errors`);
}
} catch (error) {
console.error('Processing error:', error);
}
});
console.log(`Total processed: ${processedCount}, Total errors: ${errorCount}`);
}
모범 사례와 안전 수칙
보안 체크리스트
const securityBestPractices = {
// 1. 입력 검증
validateInput: {
maxSize: '1MB 이하로 제한',
allowedOrigins: '신뢰할 수 있는 소스만 허용',
sanitization: '특수 문자 및 제어 문자 필터링',
schemaValidation: '엄격한 스키마 검증 적용'
},
// 2. 파싱 제한
parsingLimits: {
maxDepth: '객체 중첩 10레벨 이하',
maxKeys: '객체당 1000개 키 이하',
maxArrayLength: '배열 길이 10000개 이하',
timeout: '파싱 타임아웃 5초 설정'
},
// 3. 메모리 보호
memoryProtection: {
streaming: '대용량 데이터는 스트리밍 처리',
pooling: '객체 풀링으로 GC 압박 최소화',
monitoring: '메모리 사용량 실시간 모니터링',
cleanup: '처리 완료 후 즉시 메모리 해제'
},
// 4. 오류 처리
errorHandling: {
gracefulFailure: '파싱 실패 시 안전한 폴백',
logging: '상세한 오류 로깅 (민감 정보 제외)',
alerting: '심각한 오류 발생 시 알림',
recovery: '자동 복구 메커니즘 구현'
}
};
성능 최적화 체크리스트
- JSON 스키마 검증을 통한 조기 오류 탐지
- 캐싱을 활용한 반복 파싱 최적화
- 스트리밍 처리로 메모리 사용량 제어
- 압축 전송으로 네트워크 오버헤드 감소
- 지연 로딩으로 초기 로드 시간 단축
가독성과 유지보수성
- 일관된 네이밍 컨벤션 적용
- 적절한 주석과 문서화
- 스키마 정의를 통한 구조 명확화
- 버전 관리를 통한 하위 호환성 유지
- 테스트 케이스를 통한 안정성 확보
실무 적용 가이드
프로젝트별 JSON 전략
API 서버 개발
// Express.js API 서버에서의 JSON 처리
const express = require('express');
const app = express();
// JSON 파싱 미들웨어 설정
app.use(express.json({
limit: '10mb',
type: 'application/json',
verify: (req, res, buf) => {
// 원본 body 저장 (서명 검증용)
req.rawBody = buf;
}
}));
// JSON 스키마 검증 미들웨어
const validateJSON = (schema) => (req, res, next) => {
const validator = new JSONSchemaValidator(schema);
const result = validator.validate(req.body);
if (!result.valid) {
return res.status(400).json({
error: 'Invalid request data',
details: result.errors
});
}
next();
};
// 사용 예시
app.post('/api/users', validateJSON(userSchema), (req, res) => {
// 검증된 JSON 데이터로 작업
const user = req.body;
// ... 비즈니스 로직
});
프론트엔드 상태 관리
// Redux/Zustand 등에서 JSON 데이터 관리
const createJSONStore = () => ({
data: {},
loading: false,
error: null,
// 안전한 JSON 업데이트
updateData: (path, value) => {
set(state => {
const newData = { ...state.data };
setNestedValue(newData, path, value);
return { data: newData };
});
},
// JSON Patch를 이용한 효율적 업데이트
applyPatch: (patches) => {
set(state => ({
data: JSONPatch.apply(state.data, patches)
}));
},
// 타입 안전한 데이터 접근
getData: (path, defaultValue = null) => {
return getNestedValue(get().data, path) ?? defaultValue;
}
});
마무리
JSON은 현대 웹 개발의 기본이지만, 제대로 다루기 위해서는 깊은 이해와 체계적인 접근이 필요합니다. 보안, 성능, 유지보수성을 모두 고려한 JSON 처리 시스템을 구축하면 안정적이고 확장 가능한 애플리케이션을 만들 수 있습니다.
효과적인 JSON 처리의 핵심:
- 검증 우선: 엄격한 스키마 검증으로 안전성 확보
- 성능 고려: 메모리와 처리 시간 최적화
- 오류 처리: 예외 상황에 대한 철저한 준비
- 확장성: 미래 요구사항 변화에 대한 유연한 대응
뚝딱툴 JSON 처리로 지금 바로 JSON 검증, 정렬, 압축을 경험해보세요!