더글라스 크록포드가 JSON을 어떻게 대중화했는지, 왜 웹 앱과 API의 기본 형식이 되었는지—그리고 오늘날 JSON을 잘 사용하는 실용적인 팁들.

JSON(JSON, JavaScript Object Notation)은 키-값 쌍과 리스트를 사용해 데이터를 텍스트로 가볍게 표현하는 방식입니다.
웹 앱을 만든다면—데이터 형식을 깊이 고민하지 않더라도—JSON이 아마 제품을 연결하는 접착제 역할을 하고 있을 겁니다. 프론트엔드가 데이터를 요청하는 방식, 백엔드가 응답하는 방식, 모바일 앱이 상태를 동기화하는 방식, 서드파티 서비스가 이벤트를 보내는 방식 등에서 JSON이 사용됩니다. JSON이 명확하고 일관적이면 팀은 더 빠르게 출시할 수 있고, 엉성하면 모든 기능이 느려집니다. 그 이유는 데이터가 무슨 의미인지에 대해 계속 논쟁하게 되기 때문입니다.
한눈에 읽을 수 있는 작은 JSON 객체 예시입니다:
{
"userId": 42,
"name": "Sam",
"isPro": true,
"tags": ["beta", "newsletter"]
}
기술적 맥락이 없어도 보통 무슨 일이 일어나는지 추론할 수 있습니다: 사용자는 ID, 이름, 상태 플래그, 태그 목록을 갖고 있습니다.
다음 내용을 배우게 될 것입니다:
목표는 단순합니다: JSON이 무엇인지뿐 아니라 왜 거의 모든 앱이 JSON을 "사용"하는지 이해하도록 돕고, 팀들이 반복하는 흔한 실수를 피하게 하는 것입니다.
더글라스 크록포드가 JSON에 포함된 모든 아이디어를 "발명"한 건 아니지만, 그가 한 중요한 일은 실용적인 패턴을 가시화하고 이름을 붙이며 주류로 밀어넣은 것입니다.
웹 애플리케이션 초기에는 브라우저와 서버 사이에서 데이터를 옮기는 방법이 번잡했습니다. XML은 널리 사용됐지만 장황했습니다. 구분자 기반의 커스텀 포맷은 작고 간단했으나 취약했습니다. JavaScript로 데이터를 코드로 평가해 사용할 수는 있었지만, 그건 "데이터"와 "실행 가능한 스크립트"의 경계를 모호하게 만들어 버그와 보안 문제를 일으킬 소지가 컸습니다.
크록포드는 더 깔끔한 길을 제시했습니다: 평범한 데이터를 신뢰성 있게 표현할 수 있는 JavaScript 리터럴 문법의 작은 부분 집합(객체, 배열, 문자열, 숫자, 불리언, null)을 사용하자고 한 것입니다—불필요한 기능은 제외하고.
크록포드의 가장 큰 공헌 중 하나는 기술적이라기보다 사회적인 것이었습니다: 그가 이를 **JSON(JavaScript Object Notation)**이라 명명하고 json.org에 명확한 문서를 게시한 것입니다. 덕분에 팀들은 공통의 어휘를 가질 수 있었고("JSON을 보냅시다"), 짧고 읽기 쉬우며 구현하기에 엄격한 레퍼런스를 얻었습니다.
그는 또한 JSON을 프로그래밍 언어로서의 JavaScript와 독립적인 데이터 형식으로 홍보했습니다: 많은 언어들이 이를 파싱하고 생성할 수 있었고, 일반적인 데이터 구조에 자연스럽게 매핑되었습니다.
팀들이 장기적으로 한 형식에 베팅하는 것을 안전하다고 느낄 때 채택은 가속화됩니다. JSON은 다음과 같은 잘 알려진 이정표를 통해 "안전한 선택"으로 자리잡았습니다:
크록포드의 옹호, 이들 표준, 그리고 파서 생태계의 성장은 JSON을 편리한 관습에서 클라이언트와 서버가 대화하는 기본 방식으로 옮겨놓는 데 기여했습니다—특히 HTTP API에서 (/blog/json-and-apis에서 다루는 부분).
JSON이 기본 방식이 되기 전, 웹은 너무 무겁거나, 너무 일관성이 없거나, 너무 커스텀이라 팀 간 확장성이 떨어지는 혼합된 형식들에 의존했습니다.
XML이 큰 "표준" 선택지였습니다. 언어 간 작동했고 도구가 있었으며 중첩 구조를 표현할 수 있었습니다. 동시에 많은 앱이 커스텀 쿼리 문자열로 데이터를 전달했습니다(특히 초기 AJAX 스타일 요청): URL이나 POST 바디에 키/값 쌍을 집어넣는 방식이었습니다. 다른 경우에는 각각 고유한 이스케이프 규칙이 있는 임의의 텍스트 포맷(콤마로 구분된 리스트, 파이프 구분 블랍 등)을 만들곤 했고, 그 규칙을 이해하는 사람은 한 명뿐이었습니다.
공통 문제들은 이론적인 것이 아니었습니다:
대부분의 앱은 모든 문서 구조를 표현할 수 있는 포맷을 필요로 하지 않습니다. 객체, 배열, 문자열, 숫자, 불리언을 빠르고 일관되게 보내는 예측 가능한 방법이 필요할 뿐입니다. 단순한 포맷은 엔드포인트마다 내려야 하는 결정 수(그리고 실수 수)를 줄입니다.
\u003cuser\u003e
\u003cid\u003e42\u003c/id\u003e
\u003cname\u003eAda\u003c/name\u003e
\u003cisActive\u003etrue\u003c/isActive\u003e
\u003c/user\u003e
{
"id": 42,
"name": "Ada",
"isActive": true
}
둘 다 같은 아이디어를 표현하지만, JSON이 더 스캔하기 쉽고 생성하기 쉬우며 대부분 애플리케이션이 메모리에서 데이터를 모델링하는 방식에 더 가깝습니다.
JSON의 지속성은 우연이 아닙니다. 실용적인 애플리케이션 데이터를 표현하기에 충분한 최소한의 구조만 제공하도록 의도적으로 작게 설계되었기 때문에 성공했습니다.
JSON은 대부분의 앱이 데이터를 생각하는 방식에 깔끔하게 매핑되는 최소 도구를 제공합니다:
name, email을 가진 연락처)true/false그게 전부입니다. 날짜, 주석, 커스텀 숫자 타입, 참조 같은 기능은 없습니다. 이 단순함이 언어와 플랫폼 전반에서 JSON을 구현하기 쉽게 만듭니다.
JSON은 사람도 로그나 API 응답에서 한눈에 볼 수 있을 정도로 읽기 쉬우면서도 기계가 빠르게 파싱하기 쉬운 형태를 유지합니다. 불필요한 격식을 피하면서도 파서가 빠르고 신뢰성 있게 작동하도록 {}, [], : 같은 명확한 구분 기호를 유지합니다.
단점은 JSON이 매우 최소한이기 때문에 타임스탬프, 금액, 식별자 같은 것들에 대해 팀들이 별도의 관습을 합의해야 한다는 점입니다(예: 날짜는 ISO-8601 문자열 사용).
JSON의 엄격한 규칙(문자열은 항상 큰따옴표, 후행 콤마 금지 등)은 모호성을 줄입니다. 모호성이 적을수록 서로 다른 시스템이 데이터를 교환할 때 "내 환경에서는 되는데" 같은 실패가 줄어듭니다.
JSON은 JavaScript 객체 문법처럼 보이지만 JSON은 JavaScript가 아닙니다. 언어에 구애받지 않는 데이터 형식으로서 Python, Java, Go, Ruby 등 어디서든 일관된 직렬화와 상호운용을 위해 사용됩니다.
JSON은 가장 기능이 풍부한 포맷이었기 때문에 이긴 것이 아닙니다. 웹 앱이 이미 만들어지는 방식에 맞았기 때문에 이겼습니다: JavaScript 중심의 브라우저가 간단한 HTTP 요청으로 서버와 대화하는 구조에 자연스럽게 맞아떨어졌습니다.
브라우저가 JavaScript를 표준으로 채택한 후 클라이언트 측은 구조화된 데이터를 표현하는 내장 수단을 갖게 되었습니다: 객체, 배열, 문자열, 숫자, 불리언, null. JSON은 이러한 원시 타입들과 밀접하게 일치했기 때문에 브라우저가 이해하는 것과 서버가 보내는 것 사이를 옮기는 것이 자연스럽게 느껴졌습니다.
초기 Ajax 스타일 앱은 이를 가속화했습니다. 서버가 전체 HTML 페이지를 반환하는 대신 UI가 렌더링할 작은 페이로드를 반환할 수 있게 되었습니다. 예를 들어 다음과 같은 응답은 즉시 쓸모가 있습니다:
{
"user": {"id": 42, "name": "Sam"},
"unreadCount": 3
}
JSON 문법은 JavaScript처럼 보이지만 언어 중립적입니다. 웹 프론트엔드와 상호운용해야 하는 다른 언어의 서버와 클라이언트가 필요해지자 JSON 라이브러리가 등장했고—금방 표준 장비가 되었습니다. JSON 문자열을 네이티브 데이터 구조로 파싱하는 것은 보통 함수 호출 하나로 끝나고, JSON을 생성하는 것도 마찬가지로 간단합니다.
프레임워크, API 클라이언트, 디버거, 프록시, 문서화 도구들이 JSON을 전제로 만들자 다른 형식을 선택하는 건 마찰을 불러왔습니다. 개발자는 브라우저 개발자 도구에서 페이로드를 검사하고, 예제를 테스트로 복사하고 붙여넣을 수 있으며, 인코딩/디코딩과 오류 처리에 대한 성숙한 라이브러리를 신뢰할 수 있었습니다.
하나의 JSON 응답이 웹 UI, 모바일 앱, 내부 서비스, 서드파티 통합을 최소한의 변경으로 제공할 수 있습니다. 이 상호운용성 때문에 JSON은 "하나의 백엔드, 여러 프론트엔드"를 구축하는 팀에게 안전한 선택이 되었고, 클라이언트와 서버 사이의 기본 계약으로 자리잡았습니다.
JSON은 화려해서 승리한 것이 아니라 웹이 이미 작동하는 방식에 깔끔히 들어맞았기 때문에 승리했습니다. HTTP는 요청을 보내고 응답을 받는 구조로 되어 있고, JSON은 그 응답(또는 요청) 바디를 구조화된 데이터로 표현하는 쉽고 예측 가능한 방식입니다.
API 요청은 보통 메서드와 URL을 포함합니다(예: GET /users?limit=20). 서버는 상태 코드(예: 200 또는 404), 헤더, 선택적 바디로 응답합니다.
바디가 JSON일 때 중요한 헤더는:
Content-Type: application/json이 헤더는 클라이언트에게 받은 바이트를 어떻게 해석할지 알려줍니다. 들어오는 쪽(클라이언트 → 서버)에서도 Content-Type: application/json을 보내면 "JSON을 전송합니다"라는 신호가 되어 서버가 일관되게 파싱할 수 있습니다.
JSON은 많은 API에서 반복되는 패턴에 특히 잘 맞습니다.
페이지네이션은 보통 리스트를 메타데이터로 감쌉니다:
{
"data": [{"id": 1, "name": "A"}],
"pagination": {"limit": 20, "offset": 0, "total": 153}
}
필터링과 정렬은 일반적으로 URL 쿼리 문자열에서 일어나고, 결과는 JSON 배열(또는 data 필드)에 남습니다. 예: GET /orders?status=paid&sort=-created_at.
오류 응답은 클라이언트가 메시지를 표시하고 재시도를 처리할 수 있도록 표준 형태를 가지는 것이 좋습니다:
{
"error": {
"code": "invalid_request",
"message": "limit must be between 1 and 100",
"details": {"field": "limit"}
}
}
실용적 매치는 단순합니다: HTTP는 전달과 의미(동사, 상태 코드, 캐싱)를 제공하고, JSON은 데이터 자체에 대해 가볍고 사람이 읽을 수 있는 구조를 제공합니다.
사람들이 JSON과 XML을 비교할 때, 실제로는 "앱을 위한 데이터"와 "문서를 위한 데이터"를 비교하는 경우가 많습니다. 두 형식 모두 구조화된 정보를 표현할 수 있지만, JSON은 대부분 애플리케이션이 실제로 이동시키는 것—간단한 객체, 리스트, 문자열, 숫자, 불리언, null—에 더 잘 맞습니다.
XML은 설계상 장황합니다. 여는 태그와 닫는 태그를 반복하면 페이로드가 더 커지고 로그나 네트워크 검사에서 스캔하기 어려워집니다. JSON은 보통 같은 의미를 더 적은 문자와 더 적은 시각적 잡음으로 전달해 디버깅 시 유리하고 대역폭 비용을 줄이는 데도 도움이 됩니다.
이건 단지 미학 문제가 아닙니다: 작은 페이로드는 전송이 빠르고 파서와 프록시의 부담을 줄여줍니다.
대부분의 앱 데이터는 사전(키/값 맵)과 배열(리스트)처럼 보입니다: 속성을 가진 사용자, 라인 아이템을 가진 주문, 컴포넌트를 가진 페이지. JSON은 이러한 정신 모델에 직접 매핑되고, JavaScript 및 대부분의 현대 언어의 네이티브 데이터 구조와도 잘 맞습니다.
XML도 같은 구조를 표현할 수 있지만 보통 관습이 필요합니다: 속성 vs 요소, 리스트를 위한 반복 자식 요소, "무엇이 숫자로 간주되는가"에 대한 추가 규칙 등.
XML은 문서 중심(use case)에 강합니다: 혼합된 콘텐츠(마크업과 섞인 텍스트), 퍼블리싱 워크플로우, 성숙한 XML 도구 생태계를 가진 영역(예: 특정 엔터프라이즈 통합) 등에서는 유리합니다. 페이로드가 객체 그래프보다 문서에 가깝다면 XML이 좋은 선택일 수 있습니다.
프론트엔드, 백엔드, API 간에 애플리케이션 데이터를 주고받는 것이 주 목표라면 JSON이 보통 더 단순하고 직접적인 선택입니다. 문서 마크업이 필요하거나 XML 중심의 도메인에 통합해야 한다면 XML이 더 적합할 수 있습니다.
JSON은 "JavaScript 객체"처럼 보이므로 팀들은 종종 JavaScript처럼 다뤄도 된다고 가정합니다. 그 지점에서 버그가 슬쩍 들어옵니다: JSON은 더 엄격하고 더 작고 더 관대하지 않습니다.
몇 가지 "내 환경에서는 되는데" 문제가 반복적으로 나타납니다:
{name: "Ada"}는 JSON이 아닙니다; { "name": "Ada" }가 맞습니다.{ "a": 1, }는 많은 파서에서 실패합니다.//나 /* ... */는 유효하지 않습니다. 주석이 필요하면 문서에 남기거나 개발 중에는 별도 필드를 신중히 사용하세요.이 제약들은 의도적입니다: 파서를 간단하고 일관되게 유지합니다.
JSON에는 숫자에 대한 단 하나의 타입인 number만 있습니다. 정수, 소수, 날짜 같은 내장 타입은 없습니다.
"19.99")."2025-12-26T10:15:30Z")입니다. 추측을 필요로 하는 커스텀 날짜 형식은 피하세요.JSON은 유니코드를 지원하지만 실제 시스템은 여전히 인코딩과 이스케이프에서 문제를 겪습니다:
"와 백슬래시 \\).항상 실제 JSON 파서(JSON.parse 또는 사용 언어의 동등 기능)로 파싱하세요. 빠르다고 보여도 eval 스타일 접근은 피하세요. 특히 공개 API의 경계에서는 입력을 검증해 예기치 않은 필드나 타입이 비즈니스 로직에 침투하지 않도록 하세요.
JSON 페이로드는 단순한 "전송 중인 데이터"가 아니라 팀, 시스템, 그리고 미래의 당신 사이의 장기 인터페이스입니다. 지속되는 페이로드와 분기마다 다시 쓰게 만드는 페이로드의 차이는 대개 지루한 규율에서 옵니다: 일관성, 신중한 변경 관리, 예측 가능한 엣지 케이스 처리.
명명 규칙을 정하고 전역적으로 지키세요:
camelCase 또는 snake_case) 섞지 마세요.userId를 id로 바꾸는 것은 의미가 명확해 보여도 호환성에 치명적일 수 있습니다."count": 3 vs "count": "3")는 추적하기 어려운 버그를 유발합니다.대부분의 버전 관련 문제는 변경을 추가적으로 만드는 방식으로 피할 수 있습니다:
/v2/...)하거나 헤더에 명확한 버전 신호를 포함하세요—의미를 조용히 바꾸지는 마세요.클라이언트는 실패를 동일한 형태로 받을 때 가장 잘 처리합니다:
{
"error": {
"code": "INVALID_ARGUMENT",
"message": "email must be a valid address",
"details": { "field": "email" }
}
}
훌륭한 JSON 문서는 성공 및 실패 응답의 실제 예시를 포함합니다(완전한 필드 포함). 예시를 프로덕션 동작과 동기화 상태로 유지하고 어떤 필드가 선택적, nullable, 또는 폐기 예정인지 명확히 하세요. 예시가 실제 응답과 일치하면 통합 속도가 빨라지고 실패가 줄어듭니다.
vibe-coding 워크플로우로 빠르게 기능을 만들어내는 경우라면 JSON 계약이 더 중요해집니다: 빠른 반복은 좋지만 클라이언트와 서비스가 서로 어긋나기 시작하면 문제가 됩니다.
Koder.ai에서는 팀들이 일반적으로 React 프론트엔드와 Go + PostgreSQL 백엔드를 생성한 뒤 API 스펙을 기획 모드에서 반복합니다. 스냅샷과 롤백 같은 기능은 작은 JSON 변경이 파괴적이었을 때 도움이 되고, 소스 코드 내보내기는 계약을 리포지토리에 보관하고 테스트로 강제하는 데 유용합니다.
JSON은 생성하기 쉽습니다. 이것이 강점이자 함정입니다. 한 서비스가 "age": "27"(문자열)을 보내고 다른 서비스가 27(숫자)을 기대하면 JSON 자체는 이를 막지 못합니다. 결과는 보통 최악의 종류의 버그입니다: 프로덕션에서 클라이언트 크래시가 나거나 특정 데이터에서만 발생하는 미묘한 UI 결함이 생깁니다.
검증은 잘못되었거나 예기치 않은 데이터를 그것에 의존하는 주체—프론트엔드, 파트너 통합, 분석 파이프라인, 모바일 앱 등—에 닿기 전에 잡아내는 것입니다.
자주 발생하는 실패 포인트는 필수 필드 누락, 키 이름 변경, 잘못된 타입, 형식이 거의 맞지만 다른 값(예: 일관성 없는 날짜 형식) 등입니다. API 경계에서의 작은 검증 단계가 장애를 명확한 오류 메시지로 바꿀 수 있습니다.
JSON Schema는 JSON이 어떻게 생겼어야 하는지를 기술하는 표준 방식입니다: 필수 속성, 허용 타입, 열거형, 패턴 등. 다음과 같은 경우에 특히 유용합니다:
스키마가 있으면 서버에서 요청을 검증하고, 테스트에서 응답을 검증하고, 문서를 생성할 수 있습니다. 많은 팀이 OpenAPI와 함께 스키마를 연동해 계약을 명시적으로 만들며, 이미 개발자 문서를 게시하고 있다면 /docs에서 스키마 예제를 연결해 일관성을 유지할 수 있습니다.
모든 팀이 초기에 완전한 스키마 툴링을 필요로 하지는 않습니다. 실용적인 옵션은:
유용한 규칙: 예시와 계약 테스트로 시작하고, 통합과 변경이 늘어나면 JSON Schema를 추가하세요.
몇 필드를 주고받을 때 JSON은 "가볍다"고 느껴집니다. 그러나 규모가 커지고—불안정한 네트워크의 모바일 클라이언트, 고트래픽 API, 분석 집중 페이지—JSON은 형성(shape)과 전송을 잘하지 않으면 성능 문제나 신뢰성 위험이 됩니다.
가장 흔한 스케일링 문제는 파싱이 아니라 과도한 데이터를 전송하는 것입니다.
페이지네이션은 간단한 승리입니다: 예측 가능한 청크(예: limit + cursor)를 반환해 클라이언트가 수천 개 레코드를 한 번에 다운로드하지 않도록 하세요. 중첩된 객체를 반환하는 엔드포인트의 경우 클라이언트가 필요한 필드만 요청하도록(선택 필드, 또는 "include" 확장) 허용하는 부분 응답(partial responses)을 고려하세요. 이렇게 하면 한 화면이 name과 status만 필요할 때 모든 과거 내역과 설정 필드를 받는 "오버페칭"을 방지할 수 있습니다.
실용적인 규칙: 응답을 데이터베이스가 조인하기 쉬운 방식이 아니라 사용자 행동(화면이 지금 당장 필요로 하는 것)에 맞춰 설계하세요.
API가 큰 JSON 응답을 제공한다면 압축으로 전송 크기를 크게 줄일 수 있습니다. 많은 서버가 응답을 자동으로 gzip 또는 brotli로 압축할 수 있고, 대부분의 클라이언트는 추가 코드 없이 이를 처리합니다.
캐싱은 다른 레버입니다. 높은 수준에서는 다음을 목표로 하세요:
이렇게 하면 반복 다운로드를 줄이고 트래픽 스파이크를 완화할 수 있습니다.
매우 큰 출력(내보내기, 이벤트 피드, 대용량 동기화)의 경우 응답을 스트리밍하거나 점진적 파싱을 고려해 클라이언트가 전체 문서를 메모리에 로드하기 전에 유용한 작업을 수행할 수 있게 하세요. 대부분의 앱에는 필요없지만 "하나의 큰 JSON 블롭"이 타임아웃을 일으키기 시작하면 유용한 옵션입니다.
JSON은 로그에 남기기 쉽습니다. 이는 도움이 되기도 하고 위험하기도 합니다. 로그를 제품 표면처럼 다루세요:
잘하면 더 빨리 디버그하면서 우발적인 데이터 노출 위험을 줄일 수 있습니다.
JSON은 "완성"된 것이 아니라 안정적입니다. 앞으로 바뀌는 것은 주변 생태계입니다: 더 나은 에디터, 더 강력한 검증, 안전한 API 계약, 팀이 실수로 파괴적 변경을 만드는 걸 막는 툴링이 늘어납니다.
JSON은 광범위한 지원, 디버깅의 용이성, 일반 데이터 구조와의 매핑 때문에 대부분 웹과 모바일 앱의 기본 와이어 포맷으로 남을 가능성이 큽니다.
가장 큰 변화는 타입이 명시된 API로의 이동입니다: 팀들은 여전히 JSON을 전송하지만 JSON Schema, OpenAPI, 코드 생성기 같은 도구로 더 정밀하게 정의합니다. 그 결과 추측해야 하는 경우가 줄고, 자동 완성 기능이 좋아지며, 오류를 더 일찍 잡을 수 있습니다—JSON 자체를 포기하지 않고도 가능합니다.
많은 레코드를 효율적으로 전송하거나 저장해야 할 때(로그, 분석 이벤트, 내보내기) 하나의 거대한 JSON 배열은 불편합니다. JSON Lines(또는 NDJSON)는 행마다 하나의 JSON 객체를 넣어 이 문제를 해결합니다. 라인 단위로 스트리밍하기 좋고, 라인 단위 처리와 커맨드라인 툴과도 잘 어울립니다.
다음은 스프린트보다 오래 살 페이로드에 대한 빠른 사전 점검 목록입니다:
2025-12-26T10:15:00Z)null를 구분하고 문서화더 깊이 들어가고 싶다면 /blog의 관련 가이드를 살펴보세요—특히 스키마 검증, API 버전 관리, 장기 호환성을 위한 페이로드 설계 같은 주제들을 권합니다.