You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
JSON Schema provides the dependentRequired keyword that creates property dependencies in objects. It specifies that if a certain property exists in an object, then other specified properties must also be present. This enables conditional validation where the requirement for properties depends on the presence of others.
Unlike the more general if/then/else conditionals, dependentRequired is specifically designed for property dependencies in objects, providing a more concise and direct way to express these relationships.
The Problem Dependent Required Solves
In many API scenarios, certain fields are only meaningful or required when other fields are present. For example:
If a user selects a payment method, additional fields for that method become required
If a shipping option is chosen, address fields must be provided
If a feature flag is enabled, configuration for that feature becomes required
Traditional validation can't express these conditional dependencies efficiently. The dependentRequired keyword solves this by creating explicit relationships between properties, ensuring that data meets business rules and domain constraints.
This results in:
Better API Design - More accurately model real-world relationships between data
Enhanced Validation - Prevent incomplete or inconsistent data submissions
Clearer Documentation - Make dependencies explicit to API consumers
Currently, the @typespec/json-schema package doesn't provide an explicit decorator for this keyword, but it can be added manually via the generic @extension decorator. This proposal adds a dedicated decorator for improved clarity and developer experience.
Proposal
Add a new decorator to the TypeSpec JSON Schema library:
/** * Specifies that if a property exists in an object, then other specified properties must also be present. * * Maps directly to the JSON Schema `dependentRequired` keyword. When applied to a model, this * enhances validation by enforcing that certain properties must be present when other specified * properties exist in the instance data. * * @param dependencies An object whose keys are property names and values are arrays of property names * that are required when the key property exists in the instance * @example * model Payment { * method?: "credit" | "bank"; * card_number?: string; * * @dependentRequired(#{ "card_number": ["method"] }) * } */externdecdependentRequired(target: Model|Reflection.ModelProperty,dependencies: valueofRecord<string,string[]>);
This decorator would allow direct and intuitive specification of property dependencies in TypeSpec.
Examples
Basic Property Dependencies
modelPaymentForm{
name: string;
credit_card?: string;// Optional credit card number
billing_address?: string;// Optional billing address// If credit_card is provided, billing_address MUST also be provided// This creates a logical relationship between these two properties
@dependentRequired(#{"credit_card": ["billing_address"]})}
This example demonstrates a fundamental property dependency: if the credit_card property is present in an instance of PaymentForm, then the billing_address property must also be present. The JSON Schema validator will enforce this rule, rejecting any instance that includes a credit card without a billing address.
Multiple Property Dependencies
modelShippingForm{
name: string;
address?: string;// Optional address line
city?: string;// Optional city
state?: string;// Optional state/province
zip_code?: string;// Optional postal code
country?: string;// Optional country// Multiple dependency rules can be specified in a single decorator
@dependentRequired(#{// If address is present, these location details are required"address": ["city","state","zip_code"],// If country is specified, full address must be provided"country": ["address"]})}
This more complex example demonstrates how multiple dependencies can be specified in a single decorator:
If address is present, then city, state, and zip_code must also be present (ensuring a complete address)
If country is present, then address must also be present (ensuring the country isn't provided in isolation)
This pattern is useful for creating multi-level dependencies and ensuring data completeness.
Using with Model Properties
modelUserProfile{// Apply dependency rules to a nested object property
@dependentRequired(#{"payment_details": ["contact_information"]})
properties: {
basic_info: BasicInfo;// Required basic information
contact_information?: ContactInfo;// Optional contact details
payment_details?: PaymentDetails;// Optional payment information// Teaching point: When payment_details is present, contact_information// is required because the system needs contact details to process payments}}
This example shows how to apply the decorator directly to model properties that are objects. The dependencies work the same way within the nested object structure. This pattern is useful when working with complex objects that have their own internal dependency relationships.
Combining with Other Validation
modelAccountSettings{
username: string;// Required username
email?: string;// Optional email address
phone?: string;// Optional phone number
two_factor_type?: "email"|"phone"|"app";// Optional 2FA method
two_factor_app_key?: string;// Optional app key for app-based 2FA// Specify that when 2FA is configured, the user must have provided// contact methods for recovery and verification
@dependentRequired(#{"two_factor_type": ["email","phone"]})// Also require a minimum number of properties for a valid account
@minProperties(3)// Teaching point: Multiple decorators can be combined to create// comprehensive validation rules for an object}
Here, dependentRequired is combined with other validation keywords to create a more complete validation schema. The example enforces that:
If two-factor authentication is enabled, both email and phone must be provided for account recovery
At least 3 properties must be present in any valid account
This demonstrates how different validation constraints can work together to enforce complex business rules.
Technical Implementation
Library State Keys
Add a new state key in lib.ts:
exportconst$lib=createTypeSpecLibrary({// ... existing code ...state: {// ... existing state ..."JsonSchema.dependentRequired": {description: "Contains data configured with @dependentRequired decorator"},},}asconst);
Decorator Implementation
Add getter/setter in decorators.ts:
exportconst[/** Get dependencies set by `@dependentRequired` decorator */getDependentRequired,setDependentRequired,/** {@inheritdoc DependentRequiredDecorator} */$dependentRequired,]=createDataDecorator<DependentRequiredDecorator,Record<string,string[]>>(JsonSchemaStateKeys["JsonSchema.dependentRequired"]);
Schema Generation
Update #applyConstraints in json-schema-emitter.ts:
#applyConstraints(type: Scalar|Model|ModelProperty|Union|UnionVariant|Enum,schema: ObjectBuilder<unknown>,){// ... existing code ...// Apply dependentRequired constraints if specifiedconstdependentRequired=getDependentRequired(this.emitter.getProgram(),type);if(dependentRequired!==undefined){schema.set("dependentRequired",dependentRequired);}// ... remainder of existing code ...}
Runtime Behavior and Semantics
This decorator emits the JSON Schema dependentRequired keyword as defined in JSON Schema 2020-12.
The property names specified in the dependencies do not need to be required in the TypeSpec model - they can be optional properties (marked with ?).
Multiple @dependentRequired decorators on the same target will be merged by the TypeSpec compiler.
If a property specified in the dependencies doesn't exist in the model, the schema will still be generated, but validation might not behave as expected.
The validation is enforced at runtime by the JSON Schema validator, not by TypeSpec at compile-time.
Alternative Approaches Considered
1. Individual Property Dependency Decorators
Instead of passing a single object with all dependencies, create a more granular decorator:
The implementation should validate that property names used in dependentRequired actually exist in the model. This would help catch typos and errors during development rather than at runtime.
2. Proper JSON Schema Generation
The implementation should ensure that the dependentRequired property is properly generated according to the JSON Schema specification. The 2020-12 version of JSON Schema is already used by the emitter.
3. Scope Limitations
The decorator is limited to Model and ModelProperty targets, as these are the only elements where property dependencies make sense. This is intentional to prevent misuse.
4. Combining with Other Validation
The implementation should ensure that dependentRequired works correctly alongside other validation keywords like @minProperties, @maxProperties, etc.
Optional Enhancements
Property Validation: Add compile-time checks to verify that property names exist in the model.
Improved Type Safety: Use TypeSpec's type system to ensure property names match the model's structure.
Typesafe API: A future version could use a more TypeScript-friendly API that leverages type inference.
Real-World Use Cases
1. E-commerce Payment Forms
// An e-commerce payment form with conditional field requirementsmodelPayment{
total: number;// Required payment amount
method?: "credit"|"bank"|"paypal";// Optional payment method// Credit card specific fields
card_number?: string;
card_expiry?: string;
card_cvv?: string;// Bank transfer specific fields
account_number?: string;
routing_number?: string;// PayPal specific fields
paypal_email?: string;// When specific payment methods are selected, their respective// fields become required
@dependentRequired(#{// If credit card is the method, these fields are required"card_number": ["method","card_expiry","card_cvv"],// If bank transfer is chosen, account details are required"account_number": ["method","routing_number"],// If PayPal is chosen, email is required"paypal_email": ["method"]})// Teaching point: This creates a natural grouping of fields// based on the payment method selected}
This example demonstrates how an e-commerce payment form can use dependentRequired to ensure that all necessary fields for a specific payment method are provided. The decorator creates logical groupings of related fields that must be present together.
2. User Configuration Settings
// User notification preferences with conditional requirementsmodelNotificationSettings{// Base settings
receive_notifications: boolean;
notification_types?: Array<"email"|"sms"|"push">;// Channel-specific settings
email_address?: string;
phone_number?: string;
device_token?: string;// Ensure appropriate contact info is provided for each notification type
@dependentRequired(#{// If email notifications are enabled, need email address"notification_types": ["receive_notifications"],// Contact method dependencies"email_address": ["notification_types"],"phone_number": ["notification_types"],"device_token": ["notification_types"]})// Teaching point: The dependentRequired here ensures that// notifications can actually be delivered by requiring the// appropriate contact methods}
This example shows how user settings can have interdependencies, where enabling certain notification types requires the corresponding contact information to be provided.
Benefits
Direct Access to JSON Schema dependentRequired: Provides an explicit decorator for a useful JSON Schema feature.
Improved Developer Experience: More intuitive and discoverable than using generic @extension.
Type Safety: Leverages TypeSpec's type system for schema validation with proper typing for property dependencies.
Clear Intent: Expresses the semantic meaning of property dependencies directly in the TypeSpec model.
Consistency: Follows the pattern of other JSON Schema feature decorators.
Reduces Validation Logic: Moves business rules into the schema definition, reducing the need for custom validation code.
Limitations
No Automatic Property Validation: The implementation doesn't automatically validate that properties referenced in dependencies exist in the model without additional logic.
Limited to Property Dependencies: The decorator only handles property dependencies and not more complex conditional validation (which would be handled by if/then/else).
Object Structures Only: This feature only applies to objects with properties, not to other types of schemas.
Runtime Validation: Dependencies are checked during runtime validation, not during compile-time or type-checking.
JSON Schema Dependent Required in TypeSpec
This proposal adds support for JSON Schema dependent required validation (
dependentRequired
) to the@typespec/json-schema
package.Background and Motivation
JSON Schema provides the
dependentRequired
keyword that creates property dependencies in objects. It specifies that if a certain property exists in an object, then other specified properties must also be present. This enables conditional validation where the requirement for properties depends on the presence of others.Unlike the more general
if
/then
/else
conditionals,dependentRequired
is specifically designed for property dependencies in objects, providing a more concise and direct way to express these relationships.The Problem Dependent Required Solves
In many API scenarios, certain fields are only meaningful or required when other fields are present. For example:
Traditional validation can't express these conditional dependencies efficiently. The
dependentRequired
keyword solves this by creating explicit relationships between properties, ensuring that data meets business rules and domain constraints.This results in:
Currently, the
@typespec/json-schema
package doesn't provide an explicit decorator for this keyword, but it can be added manually via the generic@extension
decorator. This proposal adds a dedicated decorator for improved clarity and developer experience.Proposal
Add a new decorator to the TypeSpec JSON Schema library:
This decorator would allow direct and intuitive specification of property dependencies in TypeSpec.
Examples
Basic Property Dependencies
This example demonstrates a fundamental property dependency: if the
credit_card
property is present in an instance ofPaymentForm
, then thebilling_address
property must also be present. The JSON Schema validator will enforce this rule, rejecting any instance that includes a credit card without a billing address.Multiple Property Dependencies
This more complex example demonstrates how multiple dependencies can be specified in a single decorator:
address
is present, thencity
,state
, andzip_code
must also be present (ensuring a complete address)country
is present, thenaddress
must also be present (ensuring the country isn't provided in isolation)This pattern is useful for creating multi-level dependencies and ensuring data completeness.
Using with Model Properties
This example shows how to apply the decorator directly to model properties that are objects. The dependencies work the same way within the nested object structure. This pattern is useful when working with complex objects that have their own internal dependency relationships.
Combining with Other Validation
Here,
dependentRequired
is combined with other validation keywords to create a more complete validation schema. The example enforces that:This demonstrates how different validation constraints can work together to enforce complex business rules.
Technical Implementation
Library State Keys
Add a new state key in
lib.ts
:Decorator Implementation
Add getter/setter in
decorators.ts
:Schema Generation
Update
#applyConstraints
injson-schema-emitter.ts
:Runtime Behavior and Semantics
dependentRequired
keyword as defined in JSON Schema 2020-12.?
).@dependentRequired
decorators on the same target will be merged by the TypeSpec compiler.Alternative Approaches Considered
1. Individual Property Dependency Decorators
Instead of passing a single object with all dependencies, create a more granular decorator:
Pros:
Cons:
2. Using Existing Extension Decorator
The existing
@extension
decorator could handle this without a new decorator:Pros:
Cons:
Technical Considerations
1. Property Name Validation
The implementation should validate that property names used in
dependentRequired
actually exist in the model. This would help catch typos and errors during development rather than at runtime.2. Proper JSON Schema Generation
The implementation should ensure that the
dependentRequired
property is properly generated according to the JSON Schema specification. The 2020-12 version of JSON Schema is already used by the emitter.3. Scope Limitations
The decorator is limited to Model and ModelProperty targets, as these are the only elements where property dependencies make sense. This is intentional to prevent misuse.
4. Combining with Other Validation
The implementation should ensure that
dependentRequired
works correctly alongside other validation keywords like@minProperties
,@maxProperties
, etc.Optional Enhancements
Real-World Use Cases
1. E-commerce Payment Forms
This example demonstrates how an e-commerce payment form can use
dependentRequired
to ensure that all necessary fields for a specific payment method are provided. The decorator creates logical groupings of related fields that must be present together.2. User Configuration Settings
This example shows how user settings can have interdependencies, where enabling certain notification types requires the corresponding contact information to be provided.
Benefits
Direct Access to JSON Schema dependentRequired: Provides an explicit decorator for a useful JSON Schema feature.
Improved Developer Experience: More intuitive and discoverable than using generic
@extension
.Type Safety: Leverages TypeSpec's type system for schema validation with proper typing for property dependencies.
Clear Intent: Expresses the semantic meaning of property dependencies directly in the TypeSpec model.
Consistency: Follows the pattern of other JSON Schema feature decorators.
Reduces Validation Logic: Moves business rules into the schema definition, reducing the need for custom validation code.
Limitations
No Automatic Property Validation: The implementation doesn't automatically validate that properties referenced in dependencies exist in the model without additional logic.
Limited to Property Dependencies: The decorator only handles property dependencies and not more complex conditional validation (which would be handled by
if
/then
/else
).Object Structures Only: This feature only applies to objects with properties, not to other types of schemas.
Runtime Validation: Dependencies are checked during runtime validation, not during compile-time or type-checking.
Checklist
The text was updated successfully, but these errors were encountered: