@@ -485,136 +485,7 @@ 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 ( ) ;
488+ const alternativesToTest = _tryDiscriminatorOptimization ( alternatives ) ?? alternatives ;
618489
619490 // remember the best match that is used for error messages
620491 let bestMatch : { schema : JSONSchema ; validationResult : ValidationResult ; matchingSchemas : ISchemaCollector ; } | undefined = undefined ;
@@ -751,7 +622,77 @@ function validate(n: ASTNode | undefined, schema: JSONSchema, validationResult:
751622 }
752623 }
753624
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 ) ) ) ;
659+
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+ } ;
754671
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+ }
755696
756697 function _validateNumberNode ( node : NumberASTNode ) : void {
757698 const val = node . value ;
0 commit comments