JSON 에코시스템 이미지

현대 개발에서 JSON은 단순한 데이터 형식을 넘어 프론트엔드와 백엔드를 잇는 ‘계약’이 되었습니다. 하나의 JSON 샘플에서 타입 정의·런타임 검증·API 명세서까지 파생시킬 수 있는 것이 JSON 에코시스템의 강점입니다.

이 글에서는 하나의 API 응답을 예로, 포맷팅 → 타입 생성 → 검증 → API 정의라는 흐름을 실제 코드로 이어 해설합니다.

출발점: 하나의 JSON 응답

다음과 같은 사용자 정보 응답을 예로 듭니다.

{
  "id": 42,
  "name": "Alice",
  "email": "[email protected]",
  "roles": ["admin"],
  "createdAt": "2026-06-19T10:00:00Z"
}

이 단 하나의 샘플이 이후 모든 것의 출발점이 됩니다.

1단계: 포맷팅해 구조를 파악하기

실제로 들어오는 JSON은 줄바꿈 없는 한 줄인 경우가 많아 그대로는 구조를 읽을 수 없습니다. 먼저 JSON 포매터로 포맷팅해 중첩의 깊이, 배열 여부, 각 값의 타입을 확인합니다. 포맷팅하고 나서야 “roles가 배열인가 단일 값인가” 같은 구조가 보이기 시작합니다.

2단계: TypeScript 타입 생성하기

구조를 파악했다면 대응하는 TypeScript 타입을 만듭니다.

interface User {
  id: number;
  name: string;
  email: string;
  roles: string[];
  createdAt: string;
}

JSON → TypeScript 타입 생성을 쓰면 붙여넣기만으로 이 타입을 얻을 수 있습니다. 이로써 컴파일 시점의 타입 검사가 동작합니다.

3단계: Zod로 런타임 안정성 보장하기

TypeScript의 타입은 컴파일 시점의 검사일 뿐입니다. 외부 API가 정말 타입대로 데이터를 반환한다는 보장은 없고, 사양 변경이나 버그로 형태가 무너질 수도 있습니다. 그래서 런타임 검증을 위해 Zod 스키마를 준비합니다.

import { z } from "zod";

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  roles: z.array(z.string()),
  createdAt: z.string().datetime(),
});

// 타입은 스키마에서 도출할 수 있음
type User = z.infer<typeof UserSchema>;

// 입구에서 검증
const user = UserSchema.parse(await res.json());

z.infer를 쓰면 스키마에서 TypeScript 타입을 자동 도출할 수 있어 타입과 검증을 한곳에서 관리할 수 있습니다. 부적절한 데이터는 parse 시점에 예외로 걸러낼 수 있습니다.

4단계: OpenAPI로 팀의 공통 언어 만들기

마지막으로, 같은 JSON에서 API 명세서(OpenAPI / Swagger)의 스키마를 만들면 프론트·백·QA·외부 연동처에서 공유할 수 있는 ‘공통 언어’가 됩니다.

components:
  schemas:
    User:
      type: object
      properties:
        id:       { type: integer }
        name:     { type: string }
        email:    { type: string, format: email }
        roles:    { type: array, items: { type: string } }
        createdAt: { type: string, format: date-time }

JSON → OpenAPI 정의 생성으로 이 스키마의 초안을 빠르게 만들 수 있습니다. 문서 작성 수고를 줄이고 설계 논의에 시간을 쓸 수 있습니다.

전체 그림: 하나의 JSON에서 퍼져 나가기

   실제 JSON 응답

   ┌──────┼───────────┬──────────────┐
 포맷팅   TS 타입    Zod 스키마     OpenAPI
 (파악)  (정적 검사) (런타임 검사)  (사양 공유)

요점은 이들이 따로따로의 작업이 아니라 하나의 샘플에서 파생되는 일련의 흐름이라는 점입니다. 실제 데이터를 기점으로 삼으면 문서와 구현, 타입과 검증의 어긋남을 막을 수 있습니다.

자주 묻는 질문

TypeScript 타입이 있으면 Zod는 필요 없지 않나요?

타입은 컴파일 시점에만 작동하며 런타임에 들어오는 데이터는 검증하지 않습니다. 외부 API·폼 입력·LocalStorage처럼 “신뢰할 수 없는 입력”에는 Zod 같은 런타임 검증을 병용하면 안전합니다. 내부에서만 완결되는 신뢰할 수 있는 데이터라면 타입만으로도 괜찮습니다.

YAML과 JSON, 어느 쪽으로 관리해야 하나요?

기계가 처리하는 API 통신은 JSON, 사람이 편집하는 설정 파일(CI/CD나 Kubernetes 등)은 주석을 쓸 수 있는 YAML이 적합합니다. 둘은 JSON ⇔ YAML 변환으로 상호 변환할 수 있으니 용도에 따라 구분해 쓸 수 있습니다.

하나의 샘플만으로 타입을 만들어도 괜찮나요?

선택적 필드나 null이 될 수 있는 항목은 하나의 샘플만으로는 놓칩니다. 가능한 한 필드가 갖춰진 대표적인 응답을 쓰고, 생성 후 ?| null을 사양에 맞춰 보완하는 것이 확실합니다.