개발자를 위한 JSON: 구문, 검증 및 일반적인 실수
· 12분 읽기
목차
JSON이란 무엇이며 왜 중요한가
JSON(JavaScript Object Notation)은 웹에서 데이터 교환을 위한 사실상의 표준이 되었습니다. 이름에서 JavaScript와의 연관성을 암시하지만, JSON은 완전히 언어 독립적이며 거의 모든 현대 프로그래밍 언어에서 지원됩니다.
2000년대 초 Douglas Crockford에 의해 처음 명세화된 JSON은 XML의 경량 대안으로 등장했습니다. 단순성과 사람이 읽기 쉬운 특성 덕분에 API, 구성 파일 및 데이터 저장을 위한 완벽한 형식이 되었습니다.
오늘날 JSON은 REST API와 NoSQL 데이터베이스부터 구성 파일과 데이터 파이프라인에 이르기까지 모든 것을 지원합니다. JSON을 철저히 이해하는 것은 단순히 도움이 되는 것이 아니라 현대 소프트웨어 개발에 필수적입니다.
빠른 팁: JSON은 프로그래밍 언어나 데이터베이스 형식이 아닙니다. 순수하게 데이터 직렬화 형식, 즉 구조화된 데이터를 텍스트로 표현하는 방법입니다.
JSON 구문 규칙
JSON의 구문은 속일 정도로 단순하지만, 엄격함이 많은 개발자를 당황하게 만듭니다. JavaScript 객체와 달리 JSON은 구문 변형에 대해 전혀 관용을 베풀지 않습니다.
다음은 핵심 구조를 보여주는 유효한 JSON 예제입니다:
{
"name": "RunDev",
"version": 2,
"features": ["formatter", "validator", "converter"],
"config": {
"theme": "dark",
"autoSave": true
},
"deprecated": null
}
협상 불가능한 규칙
모든 JSON 문서는 다음의 엄격한 규칙을 따라야 합니다:
- 키는 반드시 큰따옴표로 묶인 문자열이어야 함 —
"name"은 유효하지만,name이나'name'은 파싱 오류를 일으킵니다 - 문자열은 반드시 큰따옴표를 사용해야 함 —
"hello"는 작동하지만,'hello'는 안 됩니다 - 후행 쉼표 금지 —
[1, 2, 3]은 올바르지만,[1, 2, 3,]은 실패합니다 - 주석 허용 안 됨 —
// comment나/* comment */모두 유효하지 않습니다 - undefined가 아닌 null 사용 — JavaScript의
undefined는 JSON에 존재하지 않습니다 - 숫자는 특정 형식을 따름 — 선행 0 금지(
010이 아닌10), 16진수 표기법 금지,Infinity나NaN금지
왜 이렇게 엄격한가?
JSON의 엄격함은 의도적입니다. 모호성을 제거함으로써 JSON은 모든 유효한 JSON 문서가 모든 플랫폼과 언어에서 동일하게 파싱될 수 있도록 보장합니다. 이러한 예측 가능성이 JSON을 데이터 교환에 신뢰할 수 있게 만듭니다.
주석의 부재는 종종 비판받지만, 명확한 키 이름과 구조를 통해 개발자가 데이터를 자체 문서화하도록 강제합니다. 주석이 필요한 구성 파일의 경우 JSONC(주석이 있는 JSON) 또는 JSON5를 고려하세요.
전문가 팁: 배포 전에 일반적인 구문 오류를 자동으로 수정하고 JSON이 유효한지 확인하려면 JSON 포매터를 사용하세요.
JSON 데이터 타입 이해하기
JSON은 정확히 6가지 데이터 타입을 지원합니다. 이 제한된 세트는 형식을 단순하게 유지하면서 대부분의 데이터 표현 요구를 충족합니다.
| 타입 | 예제 | 참고사항 |
|---|---|---|
| 문자열 | "hello world" |
반드시 큰따옴표로 묶어야 함. 이스케이프 시퀀스 지원: \n, \t, \u0041, \", \\ |
| 숫자 | 42, 3.14, -1, 1e10 |
정수와 부동소수점 구분 없음. 16진수, 8진수 또는 Infinity와 같은 특수 값 없음 |
| 불리언 | true, false |
소문자만 가능. "true"는 문자열이지 불리언이 아님 |
| Null | null |
소문자만 가능. 의도적인 값의 부재를 나타냄 |
| 배열 | [1, "two", true] |
순서가 있는 목록. 혼합 타입 포함 가능(하지만 동질적인 배열이 더 명확함) |
| 객체 | {"key": "value"} |
순서가 없는 키-값 쌍. 키는 문자열이어야 하며 고유해야 함 |
문자열과 이스케이프 시퀀스
JSON 문자열은 특수 문자를 위한 여러 이스케이프 시퀀스를 지원합니다:
\"— 큰따옴표\\— 백슬래시\/— 슬래시(선택사항이지만 유효함)\b— 백스페이스\f— 폼 피드\n— 줄바꿈\r— 캐리지 리턴\t— 탭\uXXXX— 유니코드 문자(예:\u0041은 "A")
여러 줄 문자열은 이스케이프 시퀀스를 사용해야 합니다. 리터럴 줄바꿈은 허용되지 않습니다:
{
"correct": "Line 1\nLine 2\nLine 3",
"incorrect": "Line 1
Line 2
Line 3"
}
숫자: 허용되는 것과 허용되지 않는 것
JSON 숫자는 대부분의 프로그래밍 언어보다 더 제한적입니다:
- 유효:
42,-17,3.14159,-0.5,1.23e10,1.23e-10 - 무효:
.5(0.5여야 함),010(8진수 표기법),0xFF(16진수),Infinity,NaN
JSON 명세는 정밀도 제한을 정의하지 않지만, 대부분의 구현은 IEEE 754 배정밀도 부동소수점을 사용하므로 정수는 2^53 - 1(9,007,199,254,740,991)까지 안전합니다.
전문가 팁: JavaScript의 안전한 정수 범위를 초과하는 큰 정수의 경우, 파싱 중 정밀도 손실을 방지하기 위해 문자열로 저장하는 것을 고려하세요.
객체와 키 고유성
JSON 명세는 객체 키가 고유해야 한다고 명시하지만, 파서가 중복을 어떻게 처리해야 하는지는 강제하지 않습니다. 구현마다 다르게 동작합니다:
- JavaScript의
JSON.parse()는 마지막 항목을 유지합니다 - 일부 검증기는 중복 키를 완전히 거부합니다
- 다른 것들은 첫 번째 항목을 유지합니다
모범 사례: 다양한 파서에서 예측할 수 없는 동작을 피하기 위해 항상 키가 고유한지 확인하세요.
일반적인 실수와 피하는 방법
경험 많은 개발자도 JSON 구문 오류를 범합니다. 다음은 가장 빈번한 실수와 수정 방법입니다:
| 실수 | 잘못된 예 | 올바른 예 |
|---|---|---|
| 작은따옴표 | {'name': 'test'} |
{"name": "test"} |
| 후행 쉼표 | {"a": 1, "b": 2,} |
{"a": 1, "b": 2} |
| 따옴표 없는 키 | {name: "test"} |
{"name": "test"} |
| 주석 | {"a": 1 // comment} |
주석 제거 또는 JSONC 사용 |
| Undefined 값 | {"a": undefined} |
{"a": null} |
| 선행 0 | {"port": 080} |
{"port": 80} |
| 리터럴 줄바꿈 | "line1 |
"line1\nline2" |
| 함수 값 | {"fn": function(){}} |
지원되지 않음—문자열 사용 또는 재구조화 |
JavaScript 객체 함정
JSON 오류의 가장 큰 원인은 JavaScript 객체 리터럴 구문처럼 취급하는 것입니다. 비슷해 보이지만 중요한 차이점이 있습니다:
// 유효한 JavaScript 객체
const jsObj = {
name: 'test', // 따옴표 없는 키, 작은따옴표
age: 30,
active: true,
getData: function() { return this.name; }, // 함수 허용
// 주석 허용
};
// 유효한 JSON (문자열로)
const jsonStr = `{
"name": "test",
"age": 30,
"active": true
}`;
기억하세요: JSON은 데이터 형식이지 코드가 아닙니다. 함수, 주석 또는 실행 가능한 로직을 포함할 수 없습니다.
문자 인코딩 문제
JSON은 UTF-8, UTF-16 또는 UTF-32로 인코딩되어야 합니다. UTF-8이 가장 일반적이고 권장되는 인코딩입니다. 다음을 주의하세요:
- 파일 시작 부분의 BOM(바이트 순서 표시)—일부 파서는 이를 거부합니다
- 파싱 실패를 일으킬 수 있는 잘못된 UTF-8 시퀀스
- 이스케이프되어야 하는 제어 문자(U+0000부터 U+001F까지)
빠른 팁: 구문 오류, 인코딩 문제 및 구조적 문제를 즉시 식별하려면 JSON 검증기에 JSON을 붙여넣으세요.
JSON 검증: 도구와 기법
검증은 런타임 실패를 일으키기 전에 오류를 잡는 데 중요합니다. 다양한 환경에서 JSON을 검증하는 방법은 다음과 같습니다.
명령줄 검증
내장 도구를 사용한 빠른 검증:
# Python (내장, 어디서나 사용 가능)
python3 -m json.tool data.json
# 예쁘게 출력하고 검증
python3 -m json.tool data.json output.json
# jq (강력한 JSON 프로세서)
jq . data.json
# 오류 세부정보가 있는 jq
jq . data.json || echo "Invalid JSON"
# Node.js 원라이너
node -e "console.log(JSON.parse(require('fs').readFileSync('data.json')))"
# jsonlint 사용 (npm을 통해 설치)
jsonlint data.json
프로그래밍 방식 검증
애플리케이션 코드에서 JSON 검증:
// JavaScript/Node.js
function isValidJSON(str) {
try {
JSON.parse(str);
return true;
} catch (e) {
console.error('JSON Error:', e.message);
return false;
}
}
# Python
import json
def is_valid_json(json_str):
try:
json.loads(json_str)
return True
except json.JSONDecodeError as e:
print(f'JSON Error: {e}')
return False
// Go
import "encoding/json"
func isValidJSON(data []byte) bool {
var js json.RawMessage
return json.Unmarshal(data, &js) == nil
}
스키마 검증
구문 검증은 JSON이 올바른 형식인지만 확인합니다. 스키마 검증은 데이터 구조와 타입이 요구사항과 일치하는지 확인합니다.
JSON Schema는 JSON 구조를 정의하는 표준입니다:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150
},
"email": {
"type": "string",
"format": "email"
}
},
"required": ["name", "email"]
}
인기 있는 스키마 검증 라이브러리:
- JavaScript: Ajv (가장 빠름), joi, yup
- Python: jsonschema, pydantic
- Go: gojsonschema, jsonschema
- Java: everit-org/json-schema, networknt/json-schema-validator
전문가 팁: 코드에 구현하기 전에 샘플 데이터에 대해 스키마를 테스트하려면 JSON 스키마 검증기를 사용하세요.
온라인 검증 도구
개발 중 빠른 확인을 위해:
- RunDev JSON 검증기 — 상세한 오류 메시지와 함께 즉시 검증
- RunDev JSON 포매터 — 한 번에 검증하고 정리
- JSONLint — 클래식 온라인 검증기
- JSON Schema Validator — 대화형으로 스키마 테스트
파싱 및 직렬화 모범 사례
파싱은 JSON 텍스트를 네이티브 데이터 구조로 변환합니다. 직렬화는 그 반대를 수행합니다. 두 작업 모두 성능 및 보안 영향이 있습니다.
안전한 파싱 관행
항상 JSON 파싱을 오류 처리로 감싸세요:
// JavaScript - 기본 파싱
try {
const data = JSON.parse(jsonString);
// 데이터 사용
} catch (error) {
console.error('Failed to parse JSON:', error.message);
// 오류를 적절히 처리
}
// JavaScript - reviver 함수 사용
const data = JSON.parse(jsonString, (key, value) => {
// 문자열에서 날짜 변환
if (key.endsWith('Date') && typeof value === 'string') {
return new Date(value);
}
return value;
});
직렬화 모범 사례
객체가 JSON으로 변환되는 방식 제어:
// JavaScript - 기본 직렬화
const json = JSON.stringify(data);
// 들여쓰기로 예쁘게 출력
const json = JSON.stringify(data, null, 2);
// replacer로 사용자 정의 직렬화
const json = JSON.stringify(data, (key, value) => {
// 민감한 필드 제거
if (key === 'password' || key === 'apiKey') {
return undefined;
}
// BigInt를 문자열로 변환
if (typeof value === 'bigint'