Skip to content

Commit 89c8a5b

Browse files
feat: add discriminator support for JSON schema validation
1 parent e0f39f0 commit 89c8a5b

File tree

3 files changed

+518
-1
lines changed

3 files changed

+518
-1
lines changed

src/jsonSchema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export interface JSONSchema {
8888
allowComments?: boolean; // VSCode extension
8989
allowTrailingCommas?: boolean; // VSCode extension
9090
completionDetail?: string; // VSCode extension
91+
discriminator?: { propertyName?: string; propertyIndex?: number; mapping?: { [value: string]: number }; }; // VSCode extension
9192
}
9293

9394
export interface JSONSchemaMap {

src/parser/jsonParser.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,9 +485,59 @@ function validate(n: ASTNode | undefined, schema: JSONSchema, validationResult:
485485
const testAlternatives = (alternatives: JSONSchemaRef[], maxOneMatch: boolean) => {
486486
const matches = [];
487487

488+
// Apply discriminator optimization if available
489+
const discriminator = schema.discriminator;
490+
let alternativesToTest = alternatives;
491+
492+
if (discriminator) {
493+
// Extract discriminator value from node
494+
let discriminatorValue: string | undefined;
495+
if (node.type === 'object' && discriminator.propertyName) {
496+
const prop = node.properties.find(p => p.keyNode.value === discriminator.propertyName);
497+
if (prop?.valueNode?.type === 'string') {
498+
discriminatorValue = prop.valueNode.value;
499+
}
500+
} else if (node.type === 'array' && discriminator.propertyIndex !== undefined) {
501+
const item = node.items[discriminator.propertyIndex];
502+
if (item?.type === 'string') {
503+
discriminatorValue = item.value;
504+
}
505+
}
506+
507+
if (discriminatorValue !== undefined) {
508+
// Use explicit mapping if available
509+
if (discriminator.mapping) {
510+
const index = discriminator.mapping[discriminatorValue];
511+
if (index !== undefined && index >= 0 && index < alternatives.length) {
512+
alternativesToTest = [alternatives[index]];
513+
}
514+
} else {
515+
// Auto-detect matching schemas
516+
const matchingIndices = [];
517+
for (let i = 0; i < alternatives.length; i++) {
518+
const altSchema = asSchema(alternatives[i]);
519+
if (discriminator.propertyName && altSchema.properties?.[discriminator.propertyName]) {
520+
if (asSchema(altSchema.properties[discriminator.propertyName]).const === discriminatorValue) {
521+
matchingIndices.push(i);
522+
}
523+
} else if (discriminator.propertyIndex !== undefined) {
524+
const itemSchema = altSchema.prefixItems?.[discriminator.propertyIndex]
525+
?? (Array.isArray(altSchema.items) ? altSchema.items[discriminator.propertyIndex] : undefined);
526+
if (itemSchema && asSchema(itemSchema).const === discriminatorValue) {
527+
matchingIndices.push(i);
528+
}
529+
}
530+
}
531+
if (matchingIndices.length > 0) {
532+
alternativesToTest = matchingIndices.map(i => alternatives[i]);
533+
}
534+
}
535+
}
536+
}
537+
488538
// remember the best match that is used for error messages
489539
let bestMatch: { schema: JSONSchema; validationResult: ValidationResult; matchingSchemas: ISchemaCollector; } | undefined = undefined;
490-
for (const subSchemaRef of alternatives) {
540+
for (const subSchemaRef of alternativesToTest) {
491541
const subSchema = asSchema(subSchemaRef);
492542
const subValidationResult = new ValidationResult();
493543
const subMatchingSchemas = matchingSchemas.newSub();

0 commit comments

Comments
 (0)