@@ -485,9 +485,140 @@ function validate(n: ASTNode | undefined, schema: JSONSchema, validationResult:
485485 const testAlternatives = ( alternatives : JSONSchemaRef [ ] , maxOneMatch : boolean ) => {
486486 const matches = [ ] ;
487487
488+ // Try to detect and apply discriminator optimization dynamically
489+ let alternativesToTest = alternatives ;
490+
491+ // Attempt to find a common discriminator property across alternatives
492+ const tryDiscriminatorOptimization = ( ) => {
493+ if ( alternatives . length < 2 ) {
494+ return ; // No point optimizing with less than 2 alternatives
495+ }
496+
497+ if ( node . type === 'object' ) {
498+ // Early exit: if node has no properties, can't apply optimization
499+ if ( ! node . properties || node . properties . length === 0 ) {
500+ return ;
501+ }
502+
503+ // Find properties that have const values in ALL alternatives
504+ const propertyConstMap = new Map < string , Map < any , number [ ] > > ( ) ;
505+
506+ for ( let i = 0 ; i < alternatives . length ; i ++ ) {
507+ const altSchema = asSchema ( alternatives [ i ] ) ;
508+ if ( ! altSchema . properties ) {
509+ // If any alternative has no properties, we can't use object property discrimination
510+ return ;
511+ }
512+ for ( const propName in altSchema . properties ) {
513+ const propSchema = asSchema ( altSchema . properties [ propName ] ) ;
514+ if ( propSchema . const !== undefined ) {
515+ if ( ! propertyConstMap . has ( propName ) ) {
516+ propertyConstMap . set ( propName , new Map ( ) ) ;
517+ }
518+ const constMap = propertyConstMap . get ( propName ) ! ;
519+ if ( ! constMap . has ( propSchema . const ) ) {
520+ constMap . set ( propSchema . const , [ ] ) ;
521+ }
522+ constMap . get ( propSchema . const ) ! . push ( i ) ;
523+ }
524+ }
525+ }
526+
527+ // Early exit: if no const properties found, can't apply optimization
528+ if ( propertyConstMap . size === 0 ) {
529+ return ;
530+ }
531+
532+ // Find a property where ALL alternatives have a const value (one per alternative)
533+ for ( const [ propName , constMap ] of Array . from ( propertyConstMap . entries ( ) ) ) {
534+ const coveredAlternatives = new Set < number > ( ) ;
535+ constMap . forEach ( ( indices ) => {
536+ indices . forEach ( idx => coveredAlternatives . add ( idx ) ) ;
537+ } ) ;
538+ // This property is a valid discriminator ONLY if ALL alternatives have a const value for it
539+ if ( coveredAlternatives . size === alternatives . length ) {
540+ // Extract the discriminator value from the current node
541+ const prop = node . properties . find ( p => p . keyNode . value === propName ) ;
542+ if ( prop ?. valueNode ?. type === 'string' ) {
543+ const discriminatorValue = prop . valueNode . value ;
544+ const matchingIndices = constMap . get ( discriminatorValue ) ;
545+ if ( matchingIndices && matchingIndices . length > 0 ) {
546+ alternativesToTest = matchingIndices . map ( idx => alternatives [ idx ] ) ;
547+ return ;
548+ }
549+ }
550+ // If this property covers all alternatives but we couldn't match,
551+ break ;
552+ }
553+ }
554+ } else if ( node . type === 'array' ) {
555+ // Early exit: if node has no items, can't apply optimization
556+ if ( ! node . items || node . items . length === 0 ) {
557+ return ;
558+ }
559+
560+ // Find array item indices that have const values in ALL alternatives
561+ const indexConstMap = new Map < number , Map < any , number [ ] > > ( ) ;
562+
563+ for ( let i = 0 ; i < alternatives . length ; i ++ ) {
564+ const altSchema = asSchema ( alternatives [ i ] ) ;
565+ const itemSchemas = altSchema . prefixItems || ( Array . isArray ( altSchema . items ) ? altSchema . items : undefined ) ;
566+
567+ if ( ! itemSchemas ) {
568+ // If any alternative has no item schemas, we can't use array index discrimination
569+ return ;
570+ }
571+
572+ itemSchemas . forEach ( ( itemSchemaRef , itemIndex ) => {
573+ const itemSchema = asSchema ( itemSchemaRef ) ;
574+ if ( itemSchema . const !== undefined ) {
575+ if ( ! indexConstMap . has ( itemIndex ) ) {
576+ indexConstMap . set ( itemIndex , new Map ( ) ) ;
577+ }
578+ const constMap = indexConstMap . get ( itemIndex ) ! ;
579+ if ( ! constMap . has ( itemSchema . const ) ) {
580+ constMap . set ( itemSchema . const , [ ] ) ;
581+ }
582+ constMap . get ( itemSchema . const ) ! . push ( i ) ;
583+ }
584+ } ) ;
585+ }
586+
587+ // Early exit: if no const items found, can't apply optimization
588+ if ( indexConstMap . size === 0 ) {
589+ return ;
590+ }
591+
592+ // Find an index where ALL alternatives have a const value (one per alternative)
593+ for ( const [ itemIndex , constMap ] of Array . from ( indexConstMap . entries ( ) ) ) {
594+ const coveredAlternatives = new Set < number > ( ) ;
595+ constMap . forEach ( ( indices ) => {
596+ indices . forEach ( idx => coveredAlternatives . add ( idx ) ) ;
597+ } ) ;
598+ // This index is a valid discriminator ONLY if ALL alternatives have a const value at this index
599+ if ( coveredAlternatives . size === alternatives . length ) {
600+ // Extract the discriminator value from the current node
601+ const item = node . items [ itemIndex ] ;
602+ if ( item ?. type === 'string' ) {
603+ const discriminatorValue = item . value ;
604+ const matchingIndices = constMap . get ( discriminatorValue ) ;
605+ if ( matchingIndices && matchingIndices . length > 0 ) {
606+ alternativesToTest = matchingIndices . map ( idx => alternatives [ idx ] ) ;
607+ return ;
608+ }
609+ }
610+ // If this index covers all alternatives but we couldn't match,
611+ break ;
612+ }
613+ }
614+ }
615+ } ;
616+
617+ tryDiscriminatorOptimization ( ) ;
618+
488619 // remember the best match that is used for error messages
489620 let bestMatch : { schema : JSONSchema ; validationResult : ValidationResult ; matchingSchemas : ISchemaCollector ; } | undefined = undefined ;
490- for ( const subSchemaRef of alternatives ) {
621+ for ( const subSchemaRef of alternativesToTest ) {
491622 const subSchema = asSchema ( subSchemaRef ) ;
492623 const subValidationResult = new ValidationResult ( ) ;
493624 const subMatchingSchemas = matchingSchemas . newSub ( ) ;
0 commit comments