JSON Schema로 데이터 구조 정의하고 검증하기

설정 파일 하나 잘못 건드렸다가 배포가 실패한 경험, 다들 한 번쯤 있지 않을까요? tsconfig.json에 오타가 있었다거나 GitHub Actions 워크플로우 파일에서 들여쓰기를 하나 잘못 넣었다거나 😅 JSON 파일은 사람이 읽고 쓰기엔 편하지만 구조가 조금만 복잡해지면 어떤 필드가 필수인지, 값의 타입이 뭔지 헷갈리기 시작합니다.

이런 문제를 해결하기 위해 등장한 것이 JSON Schema인데요. JSON 데이터가 어떤 구조를 가져야 하는지를 JSON 형태로 정의하는 표준이라고 생각하면 됩니다. 이번 글에서는 JSON Schema의 기본 문법부터 실전 활용까지 차근차근 살펴보겠습니다.

JSON Schema가 뭔가요?

JSON Schema는 JSON 데이터의 구조, 타입, 제약 조건을 정의하는 어휘(vocabulary)입니다. 쉽게 말해 “이 JSON 데이터는 이런 모양이어야 해”라는 규칙을 JSON 형태로 작성하는 것이죠.

예를 들어 사용자 정보를 담은 JSON이 있다고 해볼게요.

{
  "name": "홍길동",
  "email": "gildong@example.com",
  "age": 30
}

이 데이터가 올바른 형태인지 어떻게 보장할 수 있을까요? name은 반드시 문자열이어야 하고, age는 음수가 아닌 정수여야 하고, email은 이메일 형식을 따라야 합니다. 이런 규칙을 코드에 하나하나 짜넣을 수도 있지만 그러면 규칙이 코드 곳곳에 흩어져서 관리하기 어려워집니다.

JSON Schema를 쓰면 이런 규칙을 한 곳에 선언적으로 모아놓을 수 있습니다.

user.schema.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "age": {
      "type": "integer",
      "minimum": 0
    }
  },
  "required": ["name", "email"]
}

$schema는 이 스키마가 어떤 버전의 JSON Schema 스펙을 따르는지 명시합니다. 현재 최신 버전은 2020-12 draft이고 대부분의 도구가 이 버전을 지원합니다.

이 스키마 하나만 있으면 데이터 검증, 문서화, IDE 자동완성까지 한 번에 해결됩니다. TypeScript가 JavaScript 코드에 타입을 부여하는 것처럼, JSON Schema는 JSON 데이터에 타입과 제약 조건을 부여하는 셈이죠.

기본 타입

JSON Schema에서 type 키워드는 가장 기본이 되는 검증 도구입니다. JSON이 지원하는 모든 자료형을 검증할 수 있어요.

{ "type": "string" }

위 스키마는 값이 문자열인지 확인합니다. "hello"는 통과하지만 42true는 실패해요.

사용 가능한 타입은 총 7가지입니다.

  • "string" — 문자열
  • "number" — 실수를 포함한 숫자
  • "integer" — 정수만 허용
  • "boolean"true 또는 false
  • "array" — 배열
  • "object" — 객체
  • "null"null

여러 타입을 동시에 허용하고 싶다면 배열로 지정하면 됩니다.

{ "type": ["string", "null"] }

이렇게 하면 문자열이거나 null인 값을 모두 통과시킵니다. API 응답에서 선택적 필드를 표현할 때 쓸 만하죠.

문자열 검증

문자열 타입에는 길이 제한, 정규식 패턴, 형식 검증 같은 키워드를 추가로 사용할 수 있습니다.

{
  "type": "string",
  "minLength": 1,
  "maxLength": 100,
  "pattern": "^[a-zA-Z가-힣\\s]+$"
}

minLengthmaxLength는 문자열의 최소/최대 길이를 지정하고 pattern은 정규식으로 형태를 검증합니다. 위 예시는 영문, 한글, 공백만 허용하는 이름 필드에 적합하겠죠.

format 키워드를 쓰면 자주 쓰이는 형식을 간편하게 검증할 수 있습니다.

{ "type": "string", "format": "date-time" }

JSON Schema 2020-12에서 지원하는 주요 format 값은 이렇습니다.

  • "date-time" — ISO 8601 날짜+시간 (2026-03-17T09:00:00Z)
  • "date" — 날짜만 (2026-03-17)
  • "time" — 시간만 (09:00:00)
  • "email" — 이메일 주소
  • "uri" — URI
  • "uuid" — UUID
  • "ipv4" / "ipv6" — IP 주소
  • "regex" — 정규식 패턴

한 가지 주의할 점이 있는데요. 2020-12 버전부터 format은 기본적으로 어노테이션(메타데이터)으로만 동작하고 실제 검증을 수행하려면 검증 도구에서 별도로 옵션을 활성화해야 합니다.

숫자 검증

숫자 타입에는 범위와 배수 조건을 지정할 수 있습니다.

{
  "type": "integer",
  "minimum": 1,
  "maximum": 150
}

minimummaximum은 해당 값을 포함하는 범위입니다. 만약 경계값을 제외하고 싶다면 exclusiveMinimumexclusiveMaximum을 사용합니다.

{
  "type": "number",
  "exclusiveMinimum": 0,
  "exclusiveMaximum": 1
}

이 스키마는 0보다 크고 1보다 작은 숫자만 허용합니다. 확률값이나 비율을 표현할 때 딱이에요.

multipleOf는 특정 수의 배수인지 검증합니다.

{
  "type": "number",
  "multipleOf": 0.01
}

소수점 두 자리까지만 허용하는 금액 필드를 만들 때 쓰기 좋습니다.

객체 검증

실무에서 JSON Schema를 가장 많이 쓰게 되는 부분이 바로 객체 검증입니다. properties로 각 필드의 스키마를 정의하고, required로 필수 필드를 지정합니다.

{
  "type": "object",
  "properties": {
    "id": { "type": "integer" },
    "name": { "type": "string" },
    "role": {
      "type": "string",
      "enum": ["admin", "user", "guest"]
    }
  },
  "required": ["id", "name"]
}

여기서 enum 키워드는 허용되는 값의 목록을 정의합니다. role은 반드시 "admin", "user", "guest" 중 하나여야 합니다. 값이 딱 하나로 고정되어야 한다면 const 키워드를 쓸 수도 있습니다.

기본적으로 properties에 정의되지 않은 필드도 허용되는데요. 이를 막으려면 additionalProperties를 사용합니다.

{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "age": { "type": "integer" }
  },
  "additionalProperties": false
}

이렇게 하면 nameage 외의 필드가 있으면 검증에 실패합니다. API 요청 바디를 엄격하게 통제하고 싶을 때 좋아요.

additionalProperties에 스키마를 넣으면 추가 필드의 타입을 제한할 수도 있습니다.

{
  "type": "object",
  "properties": {
    "name": { "type": "string" }
  },
  "additionalProperties": { "type": "string" }
}

이 경우 name 외에 어떤 필드든 올 수 있지만, 값은 반드시 문자열이어야 합니다.

배열 검증

배열도 내부 요소의 타입과 개수를 세밀하게 제어할 수 있습니다.

{
  "type": "array",
  "items": { "type": "string" },
  "minItems": 1,
  "maxItems": 10,
  "uniqueItems": true
}

items는 배열 요소 하나하나가 만족해야 하는 스키마입니다. 위 스키마는 1개 이상 10개 이하의 중복 없는 문자열 배열을 의미해요. 태그 목록 같은 데이터를 검증할 때 안성맞춤이에요.

위치별로 서로 다른 타입을 가진 튜플(tuple)도 표현할 수 있습니다.

{
  "type": "array",
  "prefixItems": [{ "type": "string" }, { "type": "integer" }],
  "items": false
}

이 스키마는 정확히 ["문자열", 정수] 형태의 2-요소 배열만 허용합니다. items: false를 지정해서 정의된 위치 외의 추가 요소를 금지하는 것이 포인트입니다.

스키마 조합

여러 스키마를 결합해야 할 때는 allOf, anyOf, oneOf, not 키워드를 사용합니다.

allOf는 나열된 스키마를 모두 만족해야 합니다.

{
  "allOf": [{ "type": "string" }, { "minLength": 5 }]
}

anyOf는 하나 이상 만족하면 되고, oneOf는 정확히 하나만 만족해야 합니다.

{
  "oneOf": [
    { "type": "string", "maxLength": 5 },
    { "type": "number", "minimum": 0 }
  ]
}

이 스키마는 “5자 이하 문자열” 또는 “0 이상의 숫자” 중 정확히 하나에만 해당해야 합니다.

not은 특정 조건을 만족하지 않아야 통과합니다.

{
  "not": { "type": "null" }
}

이처럼 조합 키워드를 사용하면 복잡한 비즈니스 로직도 스키마로 표현할 수 있습니다.

조건부 스키마

특정 필드의 값에 따라 다른 검증 규칙을 적용해야 할 때가 있는데요. JSON Schema는 if/then/else 키워드로 조건부 검증을 지원합니다.

{
  "type": "object",
  "properties": {
    "type": { "type": "string", "enum": ["personal", "business"] },
    "company": { "type": "string" },
    "taxId": { "type": "string" }
  },
  "required": ["type"],
  "if": {
    "properties": { "type": { "const": "business" } }
  },
  "then": {
    "required": ["company", "taxId"]
  }
}

type"business"이면 companytaxId가 필수가 되고, 그 외의 경우에는 선택적 필드로 남습니다. 결제 폼이나 회원가입 폼처럼 동적으로 필수 필드가 바뀌는 상황에 딱 맞습니다.

스키마 재사용

스키마가 커지면 중복되는 정의가 생기기 마련인데요. $defs$ref를 사용하면 스키마를 모듈화해서 재사용할 수 있습니다.

{
  "$defs": {
    "address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" },
        "zipCode": { "type": "string", "pattern": "^\\d{5}$" }
      },
      "required": ["street", "city", "zipCode"]
    }
  },
  "type": "object",
  "properties": {
    "homeAddress": { "$ref": "#/$defs/address" },
    "workAddress": { "$ref": "#/$defs/address" }
  }
}

$defs 안에 공통 스키마를 정의해놓고, $ref로 참조하는 방식입니다. #/$defs/address에서 #은 현재 문서의 루트를 가리키는 JSON Pointer입니다.

외부 파일에 있는 스키마를 참조하는 것도 가능합니다.

{ "$ref": "https://example.com/schemas/address.schema.json" }

대규모 프로젝트에서 스키마를 여러 파일로 나눠 관리할 때 요긴합니다.

IDE 자동완성과 SchemaStore

JSON Schema의 가장 실용적인 혜택 중 하나는 IDE 지원인데요. VS Code, WebStorm, IntelliJ 같은 에디터에서 JSON 파일을 편집할 때 자동완성과 실시간 검증을 받을 수 있습니다.

이게 가능한 건 SchemaStore라는 프로젝트 덕분입니다. SchemaStore는 tsconfig.json, package.json, GitHub Actions 워크플로우 파일 등 수천 개의 JSON/YAML 파일에 대한 스키마를 모아놓은 오픈소스 저장소예요. 하루에 1TB가 넘는 스키마 파일을 제공할 만큼 널리 쓰이고 있습니다.

VS Code에서 tsconfig.json을 열었을 때 필드 이름이 자동완성되고, 잘못된 값에 빨간 줄이 그어지는 것도 SchemaStore에 등록된 스키마 덕분입니다.

직접 만든 JSON 파일에도 스키마를 연결할 수 있어요.

my-config.json
{
  "$schema": "https://example.com/my-config.schema.json",
  "port": 3000,
  "debug": true
}

파일 최상위에 $schema 필드를 추가하면 에디터가 해당 스키마를 자동으로 가져와서 검증과 자동완성을 제공합니다.

VS Code에서는 settings.json에서 파일 패턴별로 스키마를 매핑할 수도 있습니다.

.vscode/settings.json
{
  "json.schemas": [
    {
      "fileMatch": ["my-config.*.json"],
      "url": "./schemas/my-config.schema.json"
    }
  ]
}

온라인 검증 도구

스키마를 작성하면서 실시간으로 검증해보고 싶다면 JSON Schema Playground를 써보세요. 왼쪽에 스키마를 작성하고 오른쪽에 JSON 데이터를 넣으면 즉시 검증 결과를 확인할 수 있습니다. 스키마 문법을 처음 배울 때 이것저것 실험해보기에 좋습니다.

실전 예제: API 응답 스키마

지금까지 배운 키워드들을 조합해서 실제로 쓸 법한 스키마를 하나 만들어 볼게요. 페이지네이션이 적용된 API 응답 형태입니다.

api-response.schema.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Paginated API Response",
  "description": "페이지네이션이 적용된 사용자 목록 API 응답",
  "$defs": {
    "user": {
      "type": "object",
      "properties": {
        "id": { "type": "integer" },
        "name": { "type": "string", "minLength": 1 },
        "email": { "type": "string", "format": "email" },
        "role": { "enum": ["admin", "user", "guest"] },
        "createdAt": { "type": "string", "format": "date-time" }
      },
      "required": ["id", "name", "email", "role"],
      "additionalProperties": false
    }
  },
  "type": "object",
  "properties": {
    "data": {
      "type": "array",
      "items": { "$ref": "#/$defs/user" }
    },
    "pagination": {
      "type": "object",
      "properties": {
        "page": { "type": "integer", "minimum": 1 },
        "perPage": { "type": "integer", "minimum": 1, "maximum": 100 },
        "total": { "type": "integer", "minimum": 0 }
      },
      "required": ["page", "perPage", "total"],
      "additionalProperties": false
    }
  },
  "required": ["data", "pagination"],
  "additionalProperties": false
}

$defsuser 스키마를 분리해서 재사용하고 있고 additionalProperties: false로 정의되지 않은 필드를 차단합니다. titledescription 같은 어노테이션 키워드는 검증에는 영향을 주지 않지만 문서화 도구가 스키마에서 API 문서를 자동 생성할 때 활용합니다.

AI 도구 정의에도 JSON Schema

JSON Schema는 사람과 시스템 사이의 데이터 계약뿐 아니라, AI와 도구 사이의 인터페이스 정의에도 쓰이고 있습니다. 대표적인 예가 MCP(Model Context Protocol)인데요. MCP에서 AI 모델이 호출할 수 있는 도구(tool)를 정의할 때, 입력 파라미터의 구조를 JSON Schema로 기술합니다.

{
  name: "calculate",
  description: "수학 계산을 수행합니다",
  inputSchema: {
    type: "object",
    properties: {
      expression: {
        type: "string",
        description: "계산할 수식 (예: 2 + 3 * 4)",
      },
    },
    required: ["expression"],
  },
}

inputSchema 부분이 바로 JSON Schema입니다. type, properties, required 같은 키워드가 이 글에서 배운 것과 동일하죠. AI 모델은 이 스키마를 읽고 어떤 파라미터를 어떤 형태로 넘겨야 하는지 파악합니다.

JSON Schema가 언어에 종속되지 않는 표준이기 때문에 가능한 일인데요. MCP 서버를 TypeScript로 만들든 Python으로 만들든, 도구의 입력 형태를 동일한 방식으로 기술할 수 있습니다. OpenAI의 function calling이나 Claude의 tool use도 마찬가지로 JSON Schema 기반이어서, 한 번 배워두면 여러 AI 플랫폼에서 두루 활용할 수 있습니다.

JSON Schema와 코드 기반 검증

JSON Schema는 언어에 종속되지 않는 범용 표준이지만, 특정 프로그래밍 언어에서 사용하기에는 다소 번거로울 수 있습니다. 스키마를 JSON으로 작성해야 해서 타입 추론이 안 되고, IDE에서 스키마 자체를 작성할 때의 개발 경험이 아쉬운 편이죠.

그래서 JavaScript/TypeScript 생태계에서는 Zod처럼 코드로 스키마를 정의하는 라이브러리가 인기를 얻고 있는데요. Zod는 스키마를 TypeScript 코드로 작성하면서 동시에 타입 추론까지 받을 수 있어서, 애플리케이션 코드 안에서 검증 로직을 관리하기에 훨씬 편합니다.

둘의 쓰임새가 좀 다른데요. JSON Schema는 시스템 간 데이터 교환의 계약(contract)을 정의하는 데 강합니다. API 스펙 문서, 설정 파일 포맷, IDE 지원처럼 경계를 넘나드는 영역이거든요. 반면 Zod는 애플리케이션 내부에서 폼 입력, API 응답 파싱 같은 런타임 검증에 강합니다. 유효성 검증이 왜 필요한지에 대해서는 이전 글에서 자세히 다룬 적이 있으니 참고하세요.

실제로 두 가지를 함께 쓰는 경우도 많습니다. API 명세는 JSON Schema(OpenAPI)로 정의하고, 애플리케이션 코드에서는 Zod로 검증하는 식입니다.

마치며

JSON Schema는 단순해 보이지만 실무에서 활용 범위가 넓습니다. API 스펙 정의, 설정 파일 검증, 폼 데이터 검증, 코드 생성, IDE 자동완성에 더해 MCP 같은 AI 도구 인터페이스까지 다양한 곳에서 쓰이고 있죠.

특히 OpenAPI(Swagger)가 JSON Schema를 기반으로 API 스펙을 정의하기 때문에, REST API를 설계하는 개발자라면 JSON Schema 문법에 익숙해지는 것이 좋습니다. SchemaStore 덕분에 별도 설정 없이도 수천 가지 설정 파일에 대한 자동완성과 검증 지원을 받을 수 있으니, 이미 알게 모르게 JSON Schema의 혜택을 누리고 있는 셈이기도 합니다.

이 글에서 다룬 키워드만 잘 이해해도 대부분의 실무 상황에서 스키마를 작성하는 데 어려움이 없을 거예요.

더 자세한 내용은 Understanding JSON Schema를 참고하세요.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord