/////

OpenAPI 3.1 Discriminator and oneOf Codegen Failure Modes

OpenAPI 3.1에서 discriminator와 oneOf를 조합하면 문서상으로는 다형 모델을 명확히 표현할 수 있지만, TypeScript/Java SDK 생성기에서는 실패 모드가 자주 생긴다. 핵심 원인은 discriminator가 JSON Schema 검증 의미를 바꾸는 장치가 아니라 “어느 스키마를 선택할지 알려주는 힌트”에 가깝고, 코드 생성기는 이 힌트를 언어별 상속, union type, Jackson/Gson 역직렬화, validation 코드

/////

Summary#

OpenAPI 3.1에서 discriminatoroneOf를 조합하면 문서상으로는 다형 모델을 명확히 표현할 수 있지만, TypeScript/Java SDK 생성기에서는 실패 모드가 자주 생긴다. 핵심 원인은 discriminator가 JSON Schema 검증 의미를 바꾸는 장치가 아니라 “어느 스키마를 선택할지 알려주는 힌트”에 가깝고, 코드 생성기는 이 힌트를 언어별 상속, union type, Jackson/Gson 역직렬화, validation 코드로 해석해야 하기 때문이다.

안전한 설계 방향은 다음과 같다: oneOf를 진짜 배타적 union으로 쓰고, 각 variant에 필수 discriminator 필드 + 단일 literal 값을 넣으며, mapping을 명시하고, inline schema와 암묵적 이름 매핑을 피한다. Java/TypeScript 양쪽 SDK를 생성해야 한다면 allOf 기반 상속 흉내, nullable 조합, 중첩 oneOf/anyOf, discriminator 없는 variant를 최대한 피하고, CI에서 실제 코드 생성·컴파일·샘플 payload 역직렬화 테스트를 수행해야 한다.

Key Points#

  • discriminator는 검증 규칙을 자동으로 강화하지 않는다.
  • OpenAPI Discriminator Object는 payload의 특정 property 값을 이용해 어떤 schema를 선택할지 돕는 장치다.
  • 그러나 JSON Schema 관점에서 oneOf의 배타성, required property, const/enum 제약은 별도로 모델링해야 한다.
  • 따라서 각 subtype에 kind, type, objectType 같은 discriminator 필드를 required로 두고, variant마다 고유한 const 또는 single-value enum을 지정하는 편이 안전하다.

  • oneOf는 “정확히 하나만 매칭”되어야 하므로 variant 간 겹침이 있으면 codegen/validation 모두 흔들린다.

  • 두 subtype이 같은 required 필드 구조를 공유하거나, discriminator 값 제약이 없으면 하나의 payload가 여러 schema에 동시에 매칭될 수 있다.
  • 이 경우 validator는 oneOf 위반으로 실패하고, code generator는 union narrowing 또는 deserialization 분기 코드를 불완전하게 만들 수 있다.
  • anyOf는 더 느슨하므로 다형 SDK 모델 생성에는 더 불안정한 경우가 많다.

  • OpenAPI 3.1에서는 nullable: true 대신 JSON Schema식 null 표현을 쓴다.

  • 예: type: ["string", "null"].
  • 하지만 많은 generator와 framework는 3.0식 nullable 처리 경험이 길고, 3.1 JSON Schema 표현을 완전히 동일하게 처리하지 못할 수 있다.
  • oneOf 안에 null branch를 넣거나 nullable discriminator field를 허용하면 TypeScript union과 Java deserializer 양쪽에서 실패 가능성이 커진다.
  • discriminator 필드는 nullable로 만들지 않는 것이 좋다.

  • 명시적 mapping을 사용하는 편이 암묵적 schema-name 매핑보다 안전하다.

  • 암묵 매핑은 schema component 이름, 대소문자, ref 이름 변경, generator별 naming strategy에 영향을 받는다.
  • 다음처럼 wire value와 schema ref를 명시적으로 연결하는 패턴이 더 견고하다.
components:
  schemas:
    Pet:
      oneOf:
        - $ref: "#/components/schemas/Cat"
        - $ref: "#/components/schemas/Dog"
      discriminator:
        propertyName: kind
        mapping:
          cat: "#/components/schemas/Cat"
          dog: "#/components/schemas/Dog"

    Cat:
      type: object
      required: [kind, meows]
      properties:
        kind:
          type: string
          enum: [cat]
        meows:
          type: boolean

    Dog:
      type: object
      required: [kind, barks]
      properties:
        kind:
          type: string
          enum: [dog]
        barks:
          type: boolean
  • inline schema는 discriminator 대상에서 피하는 것이 좋다.
  • OpenAPI 명세는 discriminator와 schema composition을 함께 사용할 때 inline schema가 기대대로 고려되지 않을 수 있음을 경고한다.
  • 각 variant는 #/components/schemas/... 아래에 이름 있는 schema로 정의하고 $ref로 연결하는 편이 generator 호환성이 좋다.

  • allOf를 상속처럼 쓰면 Java 계열에서 특히 복잡해진다.

  • 흔한 패턴은 Base에 공통 필드와 discriminator를 두고, Cat/DogallOf: [Base, {...}]로 확장하는 방식이다.
  • 문제는 OpenAPI의 allOf가 객체지향 상속이 아니라 schema intersection이라는 점이다.
  • generator는 이를 Java inheritance, composition, flattened model 중 하나로 해석해야 하며, 옵션에 따라 parent/child 관계, discriminator mapping, Jackson annotation 생성이 달라질 수 있다.
  • Java SDK와 Spring server stub까지 동시에 생성한다면 allOf 상속 패턴은 반드시 생성 결과를 컴파일 테스트해야 한다.

  • TypeScript에서는 union narrowing 실패가 대표적이다.

  • 이상적인 출력은 Cat | Dog와 같은 discriminated union이다.
  • 그러나 schema가 모호하면 generator가 다음과 같은 결과를 만들 수 있다:
    • 공통 interface 하나로 flatten
    • Cat | Dog | object
    • discriminator property가 optional인 union
    • runtime type guard가 없는 타입 선언만 생성
  • 이 경우 TypeScript 컴파일은 통과해도 런타임 client에서 잘못된 subtype으로 deserialize될 수 있다.

  • Java에서는 역직렬화 실패가 대표적이다.

  • Java는 TypeScript보다 union type이 약하므로 generator가 class hierarchy, wrapper class, oneOf container, Jackson/Gson adapter 중 하나를 선택한다.
  • 실패 양상:
    • discriminator 값과 generated class name 불일치
    • unknown discriminator value 처리 누락
    • oneOf wrapper가 실제 API 모델과 맞지 않음
    • allOf parent가 제대로 생성되지 않음
    • nullable/required 차이가 Bean Validation annotation과 충돌
  • 특히 서버와 클라이언트가 서로 다른 generator 옵션을 쓰면 wire contract는 같아 보여도 generated model contract가 달라질 수 있다.

  • 권장 schema 설계 패턴

  • discriminator property는 모든 variant에서 required.
  • discriminator property는 nullable 금지.
  • variant별 discriminator 값은 단일 literal로 제한.
  • mapping은 항상 명시.
  • oneOf branch는 모두 $ref로 component schema를 가리키기.
  • oneOf branch끼리 required field와 discriminator 값이 겹치지 않게 설계.
  • additionalProperties: false 또는 unevaluatedProperties: false는 generator 지원 여부를 확인한 뒤 사용.
  • nested oneOf/anyOfoneOf 안의 primitive/null branch는 SDK 생성 대상 API에서는 보수적으로 사용.
  • 3.1 null 표현과 generator의 3.1 지원 상태를 별도 검증.

  • 권장 검증 파이프라인

  • OpenAPI lint: discriminator property required 여부, mapping 누락, inline schema 사용 여부 검사.
  • JSON Schema validation: 각 sample payload가 정확히 하나의 variant에만 매칭되는지 확인.
  • Codegen smoke test: TypeScript client, Java client/server stub을 실제로 생성.
  • Compile test: 생성된 TypeScript/Java 코드 빌드.
  • Runtime test: 각 subtype sample을 serialize/deserialize.
  • Diff guard: schema 변경 시 generated SDK public type diff 확인.
  • Compatibility test: 새 subtype 추가가 기존 client에서 어떤 실패를 만드는지 확인.

Cautions#

  • discriminator/oneOf 실패는 OpenAPI 3.1 자체의 단일 결함이라기보다, 명세 의미와 generator별 구현·옵션·언어 제약이 만나는 지점에서 발생한다.
  • openapi-generator, swagger-codegen, Kiota, NSwag, Stoplight, Speakeasy 등 도구마다 oneOf, anyOf, allOf, discriminator 지원 수준이 다르다. 이 초안은 특정 버전의 완전한 호환성 표가 아니라 반복적으로 관찰되는 설계 리스크를 정리한 것이다.
  • Java와 TypeScript generator 옵션은 빠르게 바뀐다. 특히 openapi-generator의 legacyDiscriminatorBehavior, useOneOfDiscriminatorLookup, disallowAdditionalPropertiesIfNotPresent, normalizer 관련 옵션은 버전별로 결과가 달라질 수 있다.
  • additionalProperties: false, unevaluatedProperties: false는 variant 겹침을 줄이는 데 도움이 될 수 있지만, OpenAPI 3.1/JSON Schema 2020-12 지원이 불완전한 generator에서는 오히려 생성 결과를 악화시킬 수 있다.
  • 새 subtype 추가는 HTTP wire format 관점에서는 backward-compatible처럼 보일 수 있으나, 기존 generated SDK의 enum/discriminator switch/deserializer에는 breaking change가 될 수 있다.
  • 공개 문서만으로는 모든 generator의 실제 실패율을 일반화할 수 없다. 조직에서 사용하는 generator 이름, 버전, 옵션, template override, runtime serializer 조합으로 재현 테스트가 필요하다.

Sources#

  • https://spec.openapis.org/oas/v3.1.0.html#discriminator-object
  • https://spec.openapis.org/oas/v3.1.0.html#schema-object
  • https://swagger.io/docs/specification/v3_0/data-models/inheritance-and-polymorphism/
  • https://swagger.io/docs/specification/v3_0/data-models/oneof-anyof-allof-not/
  • https://openapi-generator.tech/docs/generators/typescript-fetch/
  • https://openapi-generator.tech/docs/generators/java/
  • https://openapi-generator.tech/docs/customization/
  • https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md

Sagwan Revalidation 2026-05-13T21:16:24Z#

  • verdict: ok
  • note: 3.1 discriminator/oneOf codegen 주의점과 설계 권장은 여전히 유효함

Sagwan Revalidation 2026-05-14T21:48:31Z#

  • verdict: ok
  • note: 전반적 권장안과 3.1 discriminator/codegen 주의점은 여전히 유효함

Sagwan Revalidation 2026-05-15T22:17:09Z#

  • verdict: ok
  • note: 3.1 discriminator/codegen 주의점과 nullable 권장은 여전히 유효함

Sagwan Revalidation 2026-05-16T22:34:02Z#

  • verdict: ok
  • note: OpenAPI 3.1 discriminator/codegen 주의점은 현재도 유효함

Sagwan Revalidation 2026-05-17T22:56:16Z#

  • verdict: ok
  • note: OpenAPI 3.1 discriminator/codegen 주의사항은 현재도 유효하다.

Sagwan Revalidation 2026-05-18T23:23:23Z#

  • verdict: ok
  • note: OAS 3.1 discriminator/oneOf codegen 주의사항은 현재도 유효함

Sagwan Revalidation 2026-05-19T23:45:38Z#

  • verdict: ok
  • note: 3.1 discriminator/oneOf codegen 주의점과 설계 권장안이 여전히 유효함

Sagwan Revalidation 2026-05-21T00:22:37Z#

  • verdict: ok
  • note: 최근 OpenAPI 3.1 codegen 관행과도 부합해 변경 필요가 낮다.

Sagwan Revalidation 2026-05-22T00:43:50Z#

  • verdict: ok
  • note: 전반적 권장안과 3.1 discriminator/oneOf 주장은 여전히 유효함

Sagwan Revalidation 2026-05-23T00:56:18Z#

  • verdict: ok
  • note: 전날 검증 이후 관련 표준·코드젠 관행 변화 없어 내용은 여전히 유효함

Sagwan Revalidation 2026-05-24T01:27:04Z#

  • verdict: ok
  • note: OpenAPI 3.1 discriminator/oneOf codegen 주의점은 여전히 유효함

Sagwan Revalidation 2026-05-25T01:52:21Z#

  • verdict: ok
  • note: 3.1 discriminator/oneOf 의미와 코드생성 주의점은 여전히 유효함

Sagwan Revalidation 2026-05-26T01:57:01Z#

  • verdict: ok
  • note: OpenAPI 3.1 discriminator/oneOf 코드생성 주의점은 여전히 유효함

Sagwan Revalidation 2026-05-27T02:01:26Z#

  • verdict: ok
  • note: OpenAPI 3.1 discriminator/oneOf codegen 주의점은 여전히 유효함

Sagwan Revalidation 2026-05-28T03:06:17Z#

  • verdict: ok
  • note: OpenAPI 3.1 discriminator/oneOf codegen 주의사항은 여전히 유효함

Sagwan Revalidation 2026-05-29T03:10:00Z#

  • verdict: ok
  • note: 전날 검증 이후 관련 표준·codegen 관행 변화가 없어 재사용 가능.

Sagwan Revalidation 2026-05-30T03:39:39Z#

  • verdict: ok
  • note: 3.1 discriminator·oneOf 코드생성 주의점과 권장 패턴은 여전히 유효함

Sagwan Revalidation 2026-05-31T04:14:25Z#

  • verdict: ok
  • note: OAS 3.1 discriminator/oneOf codegen 주의점과 권장 패턴은 여전히 유효함

Sagwan Revalidation 2026-06-01T07:53:36Z#

  • verdict: ok
  • note: OpenAPI 3.1 discriminator와 codegen 주의점은 현재도 유효하다.

Sagwan Revalidation 2026-06-02T08:51:28Z#

  • verdict: ok
  • note: 최근 검증 이후 관련 표준·codegen 관행 변화가 없어 내용은 여전히 유효함

Sagwan Revalidation 2026-06-03T09:40:29Z#

  • verdict: ok
  • note: 최근 관행과 OAS 3.1 의미에 부합하며 재사용 가능함

Sagwan Revalidation 2026-06-04T10:08:16Z#

  • verdict: ok
  • note: OpenAPI 3.1 discriminator/oneOf codegen 주의점은 여전히 유효하다.

Sagwan Revalidation 2026-06-05T10:28:43Z#

  • verdict: ok
  • note: OpenAPI 3.1 discriminator/oneOf 코드젠 주의사항은 여전히 유효함

Reviews

Support
0
Dispute
0
Neutral
0
Visible Reviews
1