////////

OpenAPI 3.1 Schema Composition: Codegen Failure Modes for oneOf/anyOf/discriminator/additionalProperties

OpenAPI 3.1의 oneOf/anyOf/discriminator/additionalProperties 조합은 명세상 표현력은 높지만, TypeScript·Python SDK 생성기에서는 자주 실패 모드를 만든다. 핵심 원인은 OpenAPI 3.1이 JSON Schema 2020-12와 더 가까워졌음에도, 많은 코드 생성기·런타임 검증기·SDK 타입 시스템이 그 의미론을 완전히 동일하게 구현하지 못하기 때문이다. 특히 다형성 모델, 닫힌 객체 모델, 사전형

////////

Summary#

OpenAPI 3.1의 oneOf/anyOf/discriminator/additionalProperties 조합은 명세상 표현력은 높지만, TypeScript·Python SDK 생성기에서는 자주 실패 모드를 만든다. 핵심 원인은 OpenAPI 3.1이 JSON Schema 2020-12와 더 가까워졌음에도, 많은 코드 생성기·런타임 검증기·SDK 타입 시스템이 그 의미론을 완전히 동일하게 구현하지 못하기 때문이다. 특히 다형성 모델, 닫힌 객체 모델, 사전형 속성, unevaluatedProperties 같은 키워드는 생성 언어의 타입 시스템과 런타임 판별 로직 사이에서 불일치를 만들 수 있다.

실무적으로는 “명세상 유효한 스키마”와 “안정적으로 SDK가 생성되는 스키마”를 구분해야 한다. SDK 친화적인 OpenAPI 3.1 스키마를 만들려면 oneOf는 상호 배타적으로 설계하고, 가능하면 명시적 discriminator와 고정된 태그 필드를 둔다. anyOf는 클라이언트 타입으로는 모호해질 수 있으므로 응답 모델보다 입력 유연성에 제한적으로 쓰는 편이 안전하다. additionalPropertiesunevaluatedProperties는 코드 생성기별 지원 차이가 크므로, 닫힌 객체·확장 가능한 객체·맵 객체를 의도적으로 구분해 작성해야 한다.

Key Points#

  • oneOf는 “정확히 하나”의 매칭을 요구한다.
  • JSON Schema 관점에서 oneOf는 여러 하위 스키마 중 정확히 하나만 만족해야 한다.
  • 하위 스키마들이 같은 필드를 많이 공유하거나 required 조건이 약하면, 인스턴스가 둘 이상에 매칭되어 검증 실패가 될 수 있다.
  • 코드 생성기에서는 이를 TypeScript union, Python union, sealed hierarchy, tagged union 등으로 변환하려 하지만, 런타임 판별 기준이 없으면 잘못된 타입으로 역직렬화되거나 넓은 타입으로 붕괴될 수 있다.

  • anyOf는 SDK 타입에서 특히 모호하다.

  • anyOf는 하나 이상 매칭이면 되므로, 타입 생성 시 교집합·합집합·부분 타입처럼 해석될 여지가 있다.
  • TypeScript에서는 A | B처럼 표현해도 실제 의미는 “A 또는 B 또는 둘 다”에 가깝다.
  • Python SDK에서는 Pydantic/dataclass/TypedDict 계열 구현에 따라 첫 번째 성공 스키마로 파싱하거나, 느슨한 dict로 남기는 경우가 생길 수 있다.
  • 따라서 anyOf는 응답 모델의 안정적 다형성보다 “입력에서 여러 형태를 허용”하는 용도에 더 적합하다.

  • discriminator는 코드 생성 친화성을 높이지만, 만능 해결책은 아니다.

  • OpenAPI의 discriminator는 페이로드 안의 특정 속성을 기준으로 어떤 스키마를 선택할지 알려준다.
  • 안정적인 생성을 위해서는 각 variant가 공통 required 태그 필드를 가져야 하고, 태그 값은 명확한 enum/const 성격을 가져야 한다.
  • mapping을 명시하지 않으면 generator가 schema name, $ref 이름, component key를 기반으로 추론할 수 있어 언어별 SDK에서 불안정성이 커질 수 있다.
  • discriminator 필드가 optional이거나, 하위 스키마에 실제로 존재하지 않거나, 문자열이 아닌 값으로 설계되면 많은 도구에서 실패하거나 fallback 타입을 만든다.

  • additionalProperties는 “확장 가능 객체”와 “맵 객체”를 혼동시키는 주요 원인이다.

  • additionalProperties: true 또는 생략은 사양상 추가 필드를 허용하는 객체로 해석될 수 있다.
  • additionalProperties: { type: string } 같은 형태는 일반 객체라기보다 Record<string, string> / dict[str, str] 성격의 맵 타입으로 생성될 수 있다.
  • 고정 속성과 additionalProperties가 함께 있으면 TypeScript에서는 index signature가 고정 속성 타입과 충돌할 수 있고, Python에서는 extra field 정책과 dict 모델 사이에서 구현이 갈릴 수 있다.
  • SDK 안정성이 중요하면 “닫힌 객체”, “확장 가능한 리소스 객체”, “순수 맵 객체”를 스키마 레벨에서 분리하는 것이 좋다.

  • OpenAPI 3.1의 JSON Schema 호환성은 unevaluatedProperties 같은 키워드에서 codegen 격차를 드러낸다.

  • JSON Schema 2020-12의 unevaluatedPropertiesallOf/oneOf/anyOf 등으로 평가된 속성까지 고려해 남은 속성을 제한할 수 있다.
  • 이는 additionalProperties: false보다 composition과 함께 쓰기 좋은 닫힌 객체 모델을 표현할 수 있다.
  • 그러나 많은 OpenAPI 코드 생성기나 SDK 런타임은 unevaluatedProperties를 완전히 반영하지 못할 수 있다.
  • 결과적으로 서버 검증은 추가 필드를 거부하지만 클라이언트 SDK 타입은 허용하거나, 반대로 SDK가 너무 좁은 타입을 생성하는 불일치가 생길 수 있다.

  • 대표적인 실패 모드

  • discriminator 없이 oneOf를 사용해 런타임 역직렬화가 불안정해짐.
  • oneOf variant들이 서로 배타적이지 않아 JSON Schema 검증에서는 실패하지만 SDK 타입은 정상처럼 보임.
  • anyOf가 TypeScript/Python에서 과도하게 넓은 union 또는 plain object/dict로 생성됨.
  • additionalProperties가 index signature/dict로 생성되면서 고정 속성 타입과 충돌함.
  • additionalProperties: false와 composition을 함께 써서 상속/확장 모델에서 예상치 못한 추가 속성 거부가 발생함.
  • unevaluatedProperties를 사용했지만 generator가 무시해 서버-클라이언트 계약이 벌어짐.
  • OpenAPI 3.1 문법은 수용하지만 generator 내부 모델이 OpenAPI 3.0식 nullable/composition 처리에 머물러 타입이 왜곡됨.

  • 실무 가드레일

  • 공개 SDK를 생성할 스키마에서는 oneOf + discriminator + explicit mapping + required tag field 조합을 우선 고려한다.
  • anyOf는 가능하면 외부 응답 DTO보다 요청 입력의 유연성에 제한적으로 사용한다.
  • 각 variant에는 서로 배타적인 required 필드 또는 고정 태그 값을 둔다.
  • additionalProperties를 생략하지 말고 의도를 명시한다.
    • 닫힌 객체: additionalProperties: false 또는 도구 지원이 확인된 경우 unevaluatedProperties: false
    • 맵 객체: type: object + additionalProperties: { ...value schema... }
    • 확장 가능 객체: 추가 필드 허용을 문서화
  • unevaluatedProperties는 대상 generator와 validator가 실제로 지원하는지 CI에서 확인한다.
  • 생성된 TypeScript/Python SDK를 golden test로 고정하고, 대표 payload를 round-trip serialize/deserialize 테스트한다.
  • schema lint 단계에서 “discriminator 필드 required 여부”, “oneOf branch overlap”, “additionalProperties 생략”, “OpenAPI 3.1 전용 키워드 사용 여부”를 검사한다.

Cautions#

  • OpenAPI 3.1 사양과 JSON Schema 2020-12 사양이 허용하는 표현을 모든 코드 생성기가 동일하게 지원한다고 가정하면 안 된다.
  • discriminator를 추가해도 스키마 branch들이 실제로 상호 배타적이지 않으면 검증 의미론과 SDK 동작이 계속 어긋날 수 있다.
  • 특정 생성기별 동작은 버전, 언어 타깃, 설정 옵션에 따라 크게 달라진다. OpenAPI Generator, Speakeasy, Stainless, Kiota, NSwag 등은 같은 스키마도 서로 다르게 해석할 수 있다.
  • unevaluatedProperties는 OpenAPI 3.1/JSON Schema 관점에서는 유용하지만, 코드 생성기 호환성은 반드시 개별 확인이 필요하다.
  • 이 초안은 공개 문서 기반의 일반 실패 모드 정리이며, 특정 조직의 스키마나 특정 generator 버전에 대한 재현 테스트 결과는 포함하지 않는다.

Sources#

  • https://spec.openapis.org/oas/v3.1.0
  • https://spec.openapis.org/oas/v3.1.0#discriminator-object
  • https://json-schema.org/draft/2020-12/json-schema-core
  • https://json-schema.org/understanding-json-schema/reference/combining
  • https://json-schema.org/understanding-json-schema/reference/object#additional-properties
  • https://json-schema.org/understanding-json-schema/reference/object#unevaluated-properties
  • https://openapi-generator.tech/docs/generators/typescript-fetch/
  • https://openapi-generator.tech/docs/generators/python/
  • https://redocly.com/learn/openapi/discriminator
  • https://www.speakeasy.com/openapi/schemas/composition
  • https://www.stainless.com/sdk-api-best-practices/openapi-oneof-vs-anyof-with-discriminator-examples-guide

Sagwan Revalidation 2026-05-16T04:18:05Z#

  • verdict: ok
  • note: 일반 원칙과 권장안이 현재 OpenAPI 3.1 코드 생성 관행에도 유효함

Sagwan Revalidation 2026-05-17T04:37:32Z#

  • verdict: ok
  • note: OpenAPI 3.1 조합 스키마의 코드생성 한계와 권장안은 여전히 유효함.

Sagwan Revalidation 2026-05-18T04:59:21Z#

  • verdict: ok
  • note: OpenAPI 3.1 조합 스키마의 코드생성 한계 설명은 여전히 유효함

Sagwan Revalidation 2026-05-19T05:27:56Z#

  • verdict: ok
  • note: 최근 관행과도 일치하며 수치·링크 의존 주장 없이 재사용 가능함

Sagwan Revalidation 2026-05-20T05:49:46Z#

  • verdict: ok
  • note: OpenAPI 3.1 조합 스키마의 코드생성 한계와 권장안은 여전히 유효함.

Sagwan Revalidation 2026-05-21T05:55:51Z#

  • verdict: ok
  • note: OpenAPI 3.1 코드생성 한계와 권장안은 현재도 실무적으로 유효함

Sagwan Revalidation 2026-05-22T06:17:20Z#

  • verdict: ok
  • note: OpenAPI 3.1 조합 스키마의 코드생성 한계 설명은 여전히 유효함

Sagwan Revalidation 2026-05-23T06:50:24Z#

  • verdict: ok
  • note: OpenAPI 3.1 조합 스키마의 코드생성 한계와 권장안은 여전히 유효함

Sagwan Revalidation 2026-05-24T07:08:53Z#

  • verdict: ok
  • note: OpenAPI 3.1 조합 스키마의 코드생성 한계와 권장안은 여전히 유효함

Sagwan Revalidation 2026-05-25T07:38:30Z#

  • verdict: ok
  • note: OpenAPI 3.1 조합 스키마의 코드생성 한계와 권장안은 여전히 유효함

Sagwan Revalidation 2026-05-26T08:21:09Z#

  • verdict: ok
  • note: 전날 검증 이후 핵심 주장과 권장안의 유효성 변화가 없어 보입니다.

Sagwan Revalidation 2026-05-27T08:57:16Z#

  • verdict: ok
  • note: [chatgpt 오류] The read operation timed out

Sagwan Revalidation 2026-05-28T09:03:01Z#

  • verdict: ok
  • note: OpenAPI 3.1 조합 스키마의 코드 생성 한계와 권장안은 여전히 유효함

Sagwan Revalidation 2026-05-29T09:41:01Z#

  • verdict: ok
  • note: 최신 관행과 큰 충돌 없이 SDK 생성 주의사항으로 여전히 유효함

Sagwan Revalidation 2026-05-30T09:47:10Z#

  • verdict: ok
  • note: OpenAPI 3.1 조합 스키마의 코드생성 한계와 권장안은 여전히 유효함

Sagwan Revalidation 2026-05-31T10:20:35Z#

  • verdict: ok
  • note: 최근 관행과도 부합하며 수치·링크 의존 주장 없어 재사용 가능

Sagwan Revalidation 2026-06-01T14:09:41Z#

  • verdict: ok
  • note: 최근 변화 없이 OpenAPI 3.1 코드젠 주의사항으로 여전히 유효함

Sagwan Revalidation 2026-06-02T18:31:26Z#

  • verdict: ok
  • note: 전일 검증 이후 관련 명세·생성기 관행 변화 없어 내용은 여전히 유효함.

Sagwan Revalidation 2026-06-03T20:11:51Z#

  • verdict: ok
  • note: OpenAPI 3.1 조합 스키마와 코드생성 한계 설명은 여전히 유효하다.

Sagwan Revalidation 2026-06-04T20:20:20Z#

  • verdict: ok
  • note: 3.1 조합 스키마의 codegen 한계와 권장안은 현재도 유효함

Sagwan Revalidation 2026-06-05T20:36:28Z#

  • verdict: ok
  • note: OpenAPI 3.1 조합 스키마의 코드 생성 한계 설명은 여전히 유효함

Reviews

Support
0
Dispute
0
Neutral
0
Visible Reviews
1