중첩된 스키마 정의라는 수고로움

**OpenAPI Specification(OAS)**은 RESTful API의 인터페이스를 기술하는 언어 독립적 표준입니다(이전에는 “Swagger”라 불렸습니다). 자동 생성 문서, 클라이언트 SDK 생성, 모의 서버의 기반이 됩니다.

요청/응답 바디의 “Schema”를 손으로 작성하는 것—깊게 중첩된 객체를 type/properties/required/items에 매핑하는 작업—은 느리고 실수하기 쉽습니다.

JSON→OpenAPI 생성 도구는 샘플 JSON 페이로드를 받아 components/schemas 정의를 자동으로 만들어줍니다.

JSON에서 OpenAPI 스키마 생성하기 | Swagger 대응

타입 추론의 원리

생성기는 JSON을 값 단위로 순회하며 각각을 OpenAPI 타입에 대응시킵니다.

private static inferSchema(value: unknown): OpenApiSchema {
    if (value === null) return { type: 'null' };

    if (Array.isArray(value)) {
        // 모든 항목이 같은 스키마를 공유하면 그대로, 아니면 oneOf로 감쌈
        const itemSchemas = this.mergeSchemas(value.map((item) => this.inferSchema(item)));
        const items = itemSchemas.length === 1 ? itemSchemas[0] : { oneOf: itemSchemas };
        return { type: 'array', items };
    }

    if (typeof value === 'number') {
        return { type: Number.isInteger(value) ? 'integer' : 'number' };  // 정수/실수 구분
    }
    // 객체는 재귀적으로 properties로 전개되며, 존재하는 키는 모두 required로 취급
}

두 가지가 중요합니다.

  1. 숫자는 Number.isInteger로 판정됩니다—30integer, 30.5number가 됩니다.
  2. **배열 안의 타입이 혼재하면 oneOf**로 표현됩니다. ["admin", 1, true] 같은 배열은 items: { oneOf: [{type: string}, {type: integer}, {type: boolean}] }로 출력됩니다.

날짜 문자열 자동 감지

ISO 8601 형식의 문자열은 정규표현식으로 패턴 매칭되어 format: date-time이 자동으로 붙습니다.

if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/.test(stringValue)) {
    return { type: 'string', format: 'date-time' };
}

2026-01-15T12:00:00Z 같은 값은 감지되지만, 날짜만 있는 문자열(2026-01-15)이나 타임존 오프셋이 붙은 값(+09:00)은 이 패턴에 매칭되지 않으므로, 생성 후 format: date를 수동으로 추가해야 합니다.

사용법과 출력 예시

{
  "id": 123,
  "name": "Yamada Taro",
  "email": "taro@example.com",
  "roles": ["admin", "editor"],
  "profile": {
    "createdAt": "2026-01-15T12:00:00Z",
    "isActive": true
  }
}

이 JSON에서 idinteger, name/emailstring, rolesarray, profile은 중첩된 object, createdAtformat: date-time이 붙은 string으로 추론됩니다.

  1. API를 호출해 실제 JSON 데이터를 가져옵니다
  2. JSON→OpenAPI 생성 도구에 붙여넣습니다
  3. 몇 초 만에 components/schemas 형식의 정의가 출력됩니다
  4. 생성된 정의를 openapi.yaml 등에 통합합니다

존재하는 키는 무조건 “필수”가 된다—의도된 설계

현재 구현에서는 샘플 JSON에 존재하는 키가 모두 required에 들어갑니다.

for (const [key, val] of Object.entries(value)) {
    properties[key] = this.inferSchema(val);
    required.push(key);   // 존재하는 키는 무조건 required
}

이는 “샘플 JSON 1건만으로는 그 키가 정말 필수인지, 우연히 그때 값이 들어있었을 뿐인지 구분할 수 없다”는 데서 나온 의도된 단순화입니다. 실제 API에 생략 가능한 필드가 있다면 생성 후 required에서 수동으로 제거하세요.

생성 후 반드시 확인할 점

JSON에서 만든 OpenAPI 스키마는 완성품이 아니라 “정밀도 높은 초안”으로 보는 것이 안전합니다.

  • 실제로는 생략될 수 있는 항목을 required에서 제외한다
  • ID·금액·건수 같은 숫자 타입이 integer인지 number인지 확인한다
  • 날짜 문자열에 원하는 format이 붙어있는지 확인한다(자동 감지는 date-time뿐)
  • 배열이 빈 샘플만으로 채워지지 않았는지 확인한다(빈 배열은 items: {}가 됨)
  • descriptionexample을 추가해 이용자가 의미를 이해할 수 있게 한다

OpenAPI 3.1 준수

생성되는 스키마는 OpenAPI 3.1.0 기준입니다. OpenAPI 3.0의 nullable: true가 아니라, 3.1에서 표준화된 type: "null"(또는 유니온 타입) 표현을 사용합니다. 기존 프로젝트가 OpenAPI 3.0으로 통일되어 있다면 이 차이를 변환해야 할 수 있습니다.

자주 묻는 질문

Swagger에도 사용할 수 있나요?

네. OpenAPI는 이전에 Swagger로 불렸던 흐름이 있어, 지금도 “Swagger 스키마”, “Swagger UI”라는 표현이 쓰입니다. 생성한 스키마는 Swagger UI 같은 문서화 도구에도 그대로 활용할 수 있습니다.

요청 바디와 응답 바디 양쪽에 쓸 수 있나요?

네. POST/PUT 요청 JSON, GET 응답 JSON, Webhook 페이로드 등 JSON 구조를 알 수 있는 것이라면 스키마화의 출발점으로 쓸 수 있습니다.

배열 안의 타입이 제각각이면 어떻게 되나요?

oneOf를 사용해 여러 타입을 나열하는 형태로 출력됩니다. 타입을 통일할지, oneOf인 채로 허용할지는 API 설계의 일부로 판단하세요.

OpenAPI 3.0과 3.1의 차이를 신경 써야 하나요?

기존 프로젝트가 OpenAPI 3.0으로 통일되어 있다면, 출력을 가져오기 전에 nullabletype: null의 처리 방식을 확인하세요. 신규 프로젝트나 내부 도구 용도라면 OpenAPI 3.1의 표현을 그대로 써도 문제없는 경우가 많습니다.

정리

  • 타입 추론은 값을 재귀적으로 순회하는 방식. 숫자는 정수/실수로 나뉘고, 배열의 타입 혼재는 oneOf로 표현됩니다
  • 날짜 감지는 정규표현식 기반으로 date-time 형식만 대응하며, 그 외는 수동 조정이 필요합니다
  • 샘플에 존재하는 키는 모두 required가 됩니다—생략 가능한 항목은 생성 후 제거하세요
  • 출력은 OpenAPI 3.1.0 기준입니다

자동 생성은 어디까지나 초안입니다. 생성 후 descriptionexample을 추가해 팀원과 API 이용자가 읽기 좋은 문서로 완성해보세요.