@@ -531,11 +531,11 @@ const parseAllOf = ({
531531 // Collect discriminator information to add after all compositions are processed
532532 type DiscriminatorInfo = {
533533 discriminator : NonNullable < SchemaObject [ 'discriminator' ] > ;
534+ isExplicitMapping : boolean ;
534535 isRequired : boolean ;
535536 values : ReadonlyArray < string > ;
536537 } ;
537538 const discriminatorsToAdd : Array < DiscriminatorInfo > = [ ] ;
538- const addedDiscriminators = new Set < string > ( ) ;
539539
540540 for ( const compositionSchema of compositionSchemas ) {
541541 const originalInAllOf = state . inAllOf ;
@@ -573,13 +573,7 @@ const parseAllOf = ({
573573 schema : ref ,
574574 } ) ;
575575
576- // Process each discriminator found
577576 for ( const { discriminator, oneOf } of discriminators ) {
578- // Skip if we've already collected this discriminator property
579- if ( addedDiscriminators . has ( discriminator . propertyName ) ) {
580- continue ;
581- }
582-
583577 const values = discriminatorValues (
584578 state . $ref ,
585579 discriminator . mapping ,
@@ -589,29 +583,49 @@ const parseAllOf = ({
589583 oneOf ? ( ) => oneOf . some ( ( o ) => '$ref' in o && o . $ref === state . $ref ) : undefined ,
590584 ) ;
591585
592- if ( values . length > 0 ) {
593- // Check if the discriminator property is required in any of the discriminator schemas
594- const isRequired = discriminators . some (
595- ( d ) =>
596- d . discriminator . propertyName === discriminator . propertyName &&
597- // Check in the ref's required array or in the allOf components
598- ( ref . required ?. includes ( d . discriminator . propertyName ) ||
599- ( ref . allOf &&
600- ref . allOf . some ( ( item ) => {
601- const resolvedItem = item . $ref
602- ? context . resolveRef < SchemaObject > ( item . $ref )
603- : item ;
604- return resolvedItem . required ?. includes ( d . discriminator . propertyName ) ;
605- } ) ) ) ,
606- ) ;
586+ if ( values . length === 0 ) {
587+ continue ;
588+ }
607589
608- discriminatorsToAdd . push ( {
609- discriminator,
610- isRequired,
611- values,
612- } ) ;
613- addedDiscriminators . add ( discriminator . propertyName ) ;
590+ // True when state.$ref appears directly in the mapping; false when the
591+ // value fell back to the schema name because no mapping entry matched.
592+ const isExplicitMapping =
593+ discriminator . mapping !== undefined &&
594+ Object . values ( discriminator . mapping ) . includes ( state . $ref ) ;
595+
596+ // An explicit mapping always beats a same-property fallback collected
597+ // earlier (e.g. from a grandparent discriminator that doesn't list this
598+ // schema). Replace it; otherwise skip the duplicate.
599+ const existingIndex = discriminatorsToAdd . findIndex (
600+ ( d ) => d . discriminator . propertyName === discriminator . propertyName ,
601+ ) ;
602+ if ( existingIndex !== - 1 ) {
603+ if ( isExplicitMapping && ! discriminatorsToAdd [ existingIndex ] ! . isExplicitMapping ) {
604+ discriminatorsToAdd . splice ( existingIndex , 1 ) ;
605+ } else {
606+ continue ;
607+ }
614608 }
609+
610+ const isRequired = discriminators . some (
611+ ( d ) =>
612+ d . discriminator . propertyName === discriminator . propertyName &&
613+ ( ref . required ?. includes ( d . discriminator . propertyName ) ||
614+ ( ref . allOf &&
615+ ref . allOf . some ( ( item ) => {
616+ const resolvedItem = item . $ref
617+ ? context . resolveRef < SchemaObject > ( item . $ref )
618+ : item ;
619+ return resolvedItem . required ?. includes ( d . discriminator . propertyName ) ;
620+ } ) ) ) ,
621+ ) ;
622+
623+ discriminatorsToAdd . push ( {
624+ discriminator,
625+ isExplicitMapping,
626+ isRequired,
627+ values,
628+ } ) ;
615629 }
616630 }
617631 }
0 commit comments