팀에서 자주 나오는 질문 — JSON Schema 를 진실의 원천으로 두고 TS 타입을 생성할까, 반대로 TS 가 원천이고 JSON Schema 가 부산물일까, 아니면 둘 다 별도로 두고 매번 동기화 할까. 이 가이드는 둘이 무엇을 할 수 있는지 (서로 다른 강점), 양방향 생성의 트레이드오프, Ajv/Zod 같은 런타임 검증 도구의 자리, API 경계에서 어떻게 박는지 정리한다.
둘은 무엇이 다른가
| 축 | TypeScript | JSON Schema |
|---|---|---|
| 실행 시점 | 컴파일 타임 (런타임 X) | 런타임 (Ajv, Hyperjump 등) |
| 표현력 | 유니온, 제네릭, 매핑 타입, 조건부 타입 등 풍부 | 구조 + 제약 (min/max, format, pattern) |
| 제약 검증 | 형태 검증만 (number 가 양수인지 X) | minimum / maxLength / format: email 등 풍부 |
| 언어 호환 | TS / JS 만 | 모든 언어 (Python, Java, Go, Rust 등) |
| 도구 | tsc, IDE 인텔리센스 | Ajv (검증), 코드 생성, OpenAPI 통합 |
핵심 트레이드오프
TS 타입은 컴파일 타임에만 살아 있다. 런타임에 외부에서 들어온 JSON 이 정말 그 타입인지 검증할 수 없다. JSON Schema 는 정반대 — 런타임 검증이 본업이지만 IDE/컴파일러는 모름.
실무에서 풀어야 할 문제: API 경계에서 받은 데이터를 안전하게 TS 타입으로 좁히기. 두 시스템 사이에 자동 다리를 놓아야 한다.
옵션 A — JSON Schema 가 원천 (OpenAPI 환경)
백엔드 / 외부 API 가 OpenAPI 또는 JSON Schema 를 발행하는 경우. 이미 존재하는 schema 를 정답으로 두고 TS 타입을 생성.
도구:
openapi-typescript— OpenAPI YAML/JSON →.d.ts파일.json-schema-to-typescript— pure JSON Schema → TS interface.quicktype— 다언어 타입 생성. JSON 샘플도 입력 가능.
장점: schema 변경이 즉시 TS 컴파일 에러로 드러남. 백엔드와 동기화 보장.
단점: TS 의 풍부한 타입 표현은 못 만듦. 예: type ID = string & { __brand: "UserId" } 같은 브랜드 타입은 schema 에 표현 불가.
옵션 B — TypeScript 가 원천 (TS-first 백엔드)
풀스택 TS (tRPC, Next.js Route Handlers) 또는 TS-first 백엔드. Zod/io-ts/Valibot 같은 라이브러리로 타입 + 런타임 검증을 함께 정의.
import {z} from "zod";
const User = z.object({
id: z.string().uuid(),
email: z.string().email(),
age: z.number().int().min(0),
});
type User = z.infer<typeof User>; // TS 타입 자동 도출
// 런타임 검증
const parsed = User.parse(input); // throws on invalid장점:
- 진실의 원천 1 개. TS 타입과 런타임 검증이 분리될 수 없음.
- TS 표현력 100% 활용. branded type, 유니온 narrowing 등.
- schema 정의 자체가 코드 — 리팩터링·검색·테스트 모두 익숙한 방식.
단점:
- 다른 언어로 schema 공유 어려움. Zod → JSON Schema 변환기 (
zod- to-json-schema) 가 있지만 일부 표현은 손실. - TS 가 없는 클라이언트 (모바일·외부 통합) 에는 결국 별도 schema 필요.
옵션 C — JSON 샘플로 시작 (실무에서 가장 흔함)
외부 API 응답을 받아 빠르게 타입 만들고 시작. 진실의 원천이 schema 가 아니라 실제 응답 — 가장 빠른 진입.
도구:
- JSON → TypeScript — JSON 입력 → TS interface 추론. 중첩 객체는 별도 interface 로 분리. 0 dependency, 브라우저에서 즉시.
- JSON Schema 생성기 — 같은 JSON 에서 JSON Schema Draft 2020-12 자동 생성. 타입/required/중첩 추론.
흐름: 응답 JSON → TS interface 와 JSON Schema 둘 다 생성 → TS 는 코드 에 사용, schema 는 검증·문서화에 사용.
런타임 검증 — Ajv 와 Zod
Ajv (JSON Schema)
가장 빠른 JSON Schema 검증기. 정의된 schema 를 컴파일해 검증 함수를 생성 (한 번 컴파일, 반복 호출).
import Ajv from "ajv";
const ajv = new Ajv();
const validate = ajv.compile({
type: "object",
properties: {
email: {type: "string", format: "email"},
age: {type: "integer", minimum: 0}
},
required: ["email"]
});
if (!validate(data)) console.error(validate.errors);Draft 2020-12 까지 지원. JSON Schema 검증기 가 Ajv 백엔드를 사용해 즉시 검증 결과를 보여준다.
Zod / Valibot (TS-first)
스키마 정의가 TS 코드. 위에서 본 패턴. 장점은 IDE 자동 완성, 단점은 다른 언어로 export 어려움.
Valibot 은 Zod 와 비슷한 API, tree-shake 가 더 우수해 번들 크기 ↓. 2026 년 기준 새 프로젝트는 Valibot 도 검토 가치.
API 경계에서 — 안전한 패턴
외부에서 들어오는 데이터는 반드시 런타임 검증 후 TS 타입으로 좁힌다.
Zod 사용 예 (Next.js Route Handler)
const Body = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export async function POST(req: Request) {
const json = await req.json();
const parsed = Body.safeParse(json);
if (!parsed.success) {
return Response.json({error: parsed.error.format()}, {status: 422});
}
const {email, password} = parsed.data; // 안전한 TS 타입
// ...
}Ajv + 외부 schema 사용 예
import schema from "./api-response.schema.json";
import type {ApiResponse} from "./api-response";
const validate = ajv.compile<ApiResponse>(schema);
const data = await fetchSomething();
if (!validate(data)) throw new Error("Invalid response");
// data 는 이제 ApiResponse 타입Ajv 의 제네릭 type guard 패턴. validate 가 true 반환하면 TS 가 data: ApiResponse 로 좁힘.
흔한 함정
1. TS 타입만 박고 런타임 검증 생략
fetch().then(r => r.json() as User) 는 거짓말. 런타임에 실제 모양이 다르면 silently 깨짐. 외부 데이터는 무조건 검증.
2. 두 schema 수동 동기화
TS interface 따로, JSON Schema 따로 — drift 가 시작되면 잡기 매우 어려움. 한쪽에서 생성하거나 (옵션 A/B), 같은 정의에서 둘 다 도출 (Zod + zod-to-json-schema).
3. JSON Schema 의 additionalProperties: true 디폴트
명시 안 하면 추가 필드를 허용. 무관한 필드를 탐지하려면 additionalProperties: false 명시.
4. any / unknown 으로 도망
TS 가 까다로워 any 캐스트 → 검증 의미 0. unknown + 검증 함수가 정답.
5. Branded type 을 schema 로 표현
TS 의 UserId & Brand 같은 패턴은 JSON Schema 에 직접 표현 불가. 검증 후 수동 cast 하거나, Zod 의 z.brand() 사용.
의사 결정 가이드
- 다언어 백엔드 + OpenAPI 있음 → 옵션 A (schema 원천, TS 생성).
- 풀 TS 스택 → 옵션 B (Zod). 다른 언어 필요 시 zod-to- json-schema.
- 외부 API 빠르게 통합 → 옵션 C (json-to-ts 또는 quicktype 으로 시작 → 검증 추가).
- 스토리지 (DB) 와 wire (API) 동시 정의 → Drizzle/ Prisma + Zod 또는 TypeBox 결합.
요약
- TS = 컴파일 타임 형태. JSON Schema = 런타임 검증 + 다언어.
- 진실의 원천 1 개로 통일. 양쪽 자동 도출 또는 한쪽에서 생성.
- 런타임 검증 (Ajv/Zod) 없이 외부 데이터 신뢰 X.
- 자동 도구: JSON → TypeScript · JSON Schema 생성기 · JSON Schema 검증기.
- TS-first 새 프로젝트는 Zod/Valibot, 다언어 통합은 OpenAPI.