oneOf (Union Types)
Soxom generates language-idiomatic union types from oneOf schemas, with full discriminator support for type narrowing.
Basic Usage
Section titled “Basic Usage”OpenAPI Definition
Section titled “OpenAPI Definition”components: schemas: PaymentMethod: oneOf: - $ref: '#/components/schemas/CreditCard' - $ref: '#/components/schemas/BankAccount' discriminator: propertyName: type mapping: credit_card: '#/components/schemas/CreditCard' bank_account: '#/components/schemas/BankAccount'
CreditCard: type: object required: - type - card_number - expiry_month - expiry_year properties: type: type: string enum: [credit_card] card_number: type: string expiry_month: type: integer expiry_year: type: integer cardholder_name: type: string
BankAccount: type: object required: - type - account_number - routing_number properties: type: type: string enum: [bank_account] account_number: type: string routing_number: type: string account_holder_name: type: stringGenerated Types
Section titled “Generated Types”// Union typetype PaymentMethod = CreditCard | BankAccount;
interface CreditCard { type: 'credit_card'; cardNumber: string; expiryMonth: number; expiryYear: number; cardholderName?: string;}
interface BankAccount { type: 'bank_account'; accountNumber: string; routingNumber: string; accountHolderName?: string;}# Union typePaymentMethod = CreditCard | BankAccount
@dataclassclass CreditCard: type: Literal["credit_card"] card_number: str expiry_month: int expiry_year: int cardholder_name: Optional[str] = None
@dataclassclass BankAccount: type: Literal["bank_account"] account_number: str routing_number: str account_holder_name: Optional[str] = None// Interface for union typetype PaymentMethod interface { isPaymentMethod()}
type CreditCard struct { Type string `json:"type"` // always "credit_card" CardNumber string `json:"card_number"` ExpiryMonth int `json:"expiry_month"` ExpiryYear int `json:"expiry_year"` CardholderName string `json:"cardholder_name,omitempty"`}
func (CreditCard) isPaymentMethod() {}
type BankAccount struct { Type string `json:"type"` // always "bank_account" AccountNumber string `json:"account_number"` RoutingNumber string `json:"routing_number"` AccountHolderName string `json:"account_holder_name,omitempty"`}
func (BankAccount) isPaymentMethod() {}Discriminators
Section titled “Discriminators”Discriminators tell Soxom (and the SDK) which variant of the union to use.
Property-Based Discrimination
Section titled “Property-Based Discrimination”The most common pattern - a type property indicates the variant:
PaymentMethod: oneOf: - $ref: '#/components/schemas/CreditCard' - $ref: '#/components/schemas/BankAccount' discriminator: propertyName: type # The property to checkCustom Value Mappings
Section titled “Custom Value Mappings”Map discriminator values to schema names:
PaymentMethod: oneOf: - $ref: '#/components/schemas/CreditCard' - $ref: '#/components/schemas/BankAccount' discriminator: propertyName: type mapping: cc: '#/components/schemas/CreditCard' # "cc" maps to CreditCard bank: '#/components/schemas/BankAccount' # "bank" maps to BankAccountType Narrowing
Section titled “Type Narrowing”With discriminators, SDKs support type narrowing:
function processPayment(method: PaymentMethod) { // TypeScript narrows the type based on discriminator if (method.type === 'credit_card') { // TypeScript knows this is CreditCard console.log(`Card ending in ${method.cardNumber.slice(-4)}`); console.log(`Expires: ${method.expiryMonth}/${method.expiryYear}`); } else { // TypeScript knows this is BankAccount console.log(`Account: ${method.accountNumber}`); console.log(`Routing: ${method.routingNumber}`); }}def process_payment(method: PaymentMethod): match method: case CreditCard(): print(f"Card ending in {method.card_number[-4:]}") print(f"Expires: {method.expiry_month}/{method.expiry_year}") case BankAccount(): print(f"Account: {method.account_number}") print(f"Routing: {method.routing_number}")func processPayment(method PaymentMethod) { switch m := method.(type) { case CreditCard: fmt.Printf("Card ending in %s\n", m.CardNumber[len(m.CardNumber)-4:]) fmt.Printf("Expires: %d/%d\n", m.ExpiryMonth, m.ExpiryYear) case BankAccount: fmt.Printf("Account: %s\n", m.AccountNumber) fmt.Printf("Routing: %s\n", m.RoutingNumber) }}Without Discriminator
Section titled “Without Discriminator”If no discriminator is specified, Soxom generates a simple union:
# OpenAPI - no discriminatorResult: oneOf: - $ref: '#/components/schemas/Success' - $ref: '#/components/schemas/Error'// Generated TypeScripttype Result = Success | Error;
// Manual type checking requiredfunction handleResult(result: Result) { if ('data' in result) { // Likely Success } else if ('error' in result) { // Likely Error }}Complex Example
Section titled “Complex Example”Multiple union types in a response:
components: schemas: Event: oneOf: - $ref: '#/components/schemas/UserCreatedEvent' - $ref: '#/components/schemas/UserUpdatedEvent' - $ref: '#/components/schemas/UserDeletedEvent' discriminator: propertyName: event_type mapping: user.created: '#/components/schemas/UserCreatedEvent' user.updated: '#/components/schemas/UserUpdatedEvent' user.deleted: '#/components/schemas/UserDeletedEvent'
UserCreatedEvent: type: object properties: event_type: type: string enum: ['user.created'] user: $ref: '#/components/schemas/User' timestamp: type: string format: date-time
UserUpdatedEvent: type: object properties: event_type: type: string enum: ['user.updated'] user: $ref: '#/components/schemas/User' changes: type: object timestamp: type: string format: date-time
UserDeletedEvent: type: object properties: event_type: type: string enum: ['user.deleted'] user_id: type: string timestamp: type: string format: date-timeBest Practices
Section titled “Best Practices”- Always use discriminators for type safety
- Use consistent property names like
typeorkind - Enum the discriminator values in each variant
- Document discriminator values in descriptions
- Prefer $ref over inline schemas for variants
Next Steps
Section titled “Next Steps”- allOf (Composition) - Type composition
- anyOf - How Soxom handles anyOf