@@ -485,9 +485,11 @@ function validate(n: ASTNode | undefined, schema: JSONSchema, validationResult:
485485 const testAlternatives = ( alternatives : JSONSchemaRef [ ] , maxOneMatch : boolean ) => {
486486 const matches = [ ] ;
487487
488+ const alternativesToTest = _tryDiscriminatorOptimization ( alternatives ) ?? alternatives ;
489+
488490 // remember the best match that is used for error messages
489491 let bestMatch : { schema : JSONSchema ; validationResult : ValidationResult ; matchingSchemas : ISchemaCollector ; } | undefined = undefined ;
490- for ( const subSchemaRef of alternatives ) {
492+ for ( const subSchemaRef of alternativesToTest ) {
491493 const subSchema = asSchema ( subSchemaRef ) ;
492494 const subValidationResult = new ValidationResult ( ) ;
493495 const subMatchingSchemas = matchingSchemas . newSub ( ) ;
@@ -620,7 +622,77 @@ function validate(n: ASTNode | undefined, schema: JSONSchema, validationResult:
620622 }
621623 }
622624
625+ function _tryDiscriminatorOptimization ( alternatives : JSONSchemaRef [ ] ) : JSONSchemaRef [ ] | undefined {
626+ if ( alternatives . length < 2 ) {
627+ return undefined ;
628+ }
629+
630+ const buildConstMap = ( getSchemas : ( alt : JSONSchema , idx : number ) => [ string | number , JSONSchema ] [ ] | undefined ) => {
631+ const constMap = new Map < string | number , Map < any , number [ ] > > ( ) ;
632+
633+ for ( let i = 0 ; i < alternatives . length ; i ++ ) {
634+ const schemas = getSchemas ( asSchema ( alternatives [ i ] ) , i ) ;
635+ if ( ! schemas ) {
636+ return undefined ; // Early exit if any alternative can't be processed
637+ }
638+
639+ schemas . forEach ( ( [ key , schema ] ) => {
640+ if ( schema . const !== undefined ) {
641+ if ( ! constMap . has ( key ) ) {
642+ constMap . set ( key , new Map ( ) ) ;
643+ }
644+ const valueMap = constMap . get ( key ) ! ;
645+ if ( ! valueMap . has ( schema . const ) ) {
646+ valueMap . set ( schema . const , [ ] ) ;
647+ }
648+ valueMap . get ( schema . const ) ! . push ( i ) ;
649+ }
650+ } ) ;
651+ }
652+ return constMap ;
653+ } ;
654+
655+ const findDiscriminator = ( constMap : Map < string | number , Map < any , number [ ] > > , getValue : ( key : string | number ) => any ) => {
656+ for ( const [ key , valueMap ] of constMap ) {
657+ const coveredAlts = new Set < number > ( ) ;
658+ valueMap . forEach ( indices => indices . forEach ( idx => coveredAlts . add ( idx ) ) ) ;
623659
660+ if ( coveredAlts . size === alternatives . length ) {
661+ const discriminatorValue = getValue ( key ) ;
662+ const matchingIndices = valueMap . get ( discriminatorValue ) ;
663+ if ( matchingIndices ?. length ) {
664+ return matchingIndices . map ( idx => alternatives [ idx ] ) ;
665+ }
666+ break ; // Found valid discriminator but no match
667+ }
668+ }
669+ return undefined ;
670+ } ;
671+
672+ if ( node . type === 'object' && node . properties ?. length ) {
673+ const constMap = buildConstMap ( ( schema ) =>
674+ schema . properties ? Object . entries ( schema . properties ) . map ( ( [ k , v ] ) => [ k , asSchema ( v ) ] ) : undefined
675+ ) ;
676+ if ( constMap ) {
677+ return findDiscriminator ( constMap , ( propName ) => {
678+ const prop = node . properties . find ( p => p . keyNode . value === propName ) ;
679+ return prop ?. valueNode ?. type === 'string' ? prop . valueNode . value : undefined ;
680+ } ) ;
681+ }
682+ } else if ( node . type === 'array' && node . items ?. length ) {
683+ const constMap = buildConstMap ( ( schema ) => {
684+ const itemSchemas = schema . prefixItems || ( Array . isArray ( schema . items ) ? schema . items : undefined ) ;
685+ return itemSchemas ? itemSchemas . map ( ( item , idx ) => [ idx , asSchema ( item ) ] ) : undefined ;
686+ } ) ;
687+ if ( constMap ) {
688+ return findDiscriminator ( constMap , ( itemIndex ) => {
689+ const item = node . items [ itemIndex as number ] ;
690+ return item ?. type === 'string' ? item . value : undefined ;
691+ } ) ;
692+ }
693+ }
694+ return undefined ;
695+ }
624696
625697 function _validateNumberNode ( node : NumberASTNode ) : void {
626698 const val = node . value ;
0 commit comments