DEV Community

Cover image for Dynamically Generating Interfaces and Validation Schemas in TypeScript with Yup
Saurabh Saha
Saurabh Saha

Posted on

Dynamically Generating Interfaces and Validation Schemas in TypeScript with Yup

In a recent project, I encountered a problem where we needed to validate an object with keys dynamically defined by a constant map and enforce that at least one key has a valid value.

The Challenge

We had a MetadataMap object that defined valid keys and their corresponding types:

const MetadataMap = {
userId: Number,
utmSource: String,
utmMedium: String,
utmCampaign: String,
} as const;

From this map, we needed to:

  1. Dynamically generate a TypeScript interface to enforce type safety.
  2. Create a Yup validation schema that validates the object based on the map.
  3. Ensure at least one key in the object has a valid, non-undefined value.
  4. Avoid hardcoding keys to make the solution maintainable.

But, TypeScript enforces static types at compile time, while Yup handles runtime validation.

Step 1: Generating the Interface
To generate the TypeScript interface from the MetadataMap, we used keyof and mapped types. Here’s how we defined it:

type Metadata = {
[K in keyof typeof MetadataMap]: typeof MetadataMap[K] extends NumberConstructor
? number
: string;
};

This approach ensured that any updates to MetadataMap were automatically reflected in the Metadata interface. For example:

// Resulting Metadata interface:
interface Metadata {
userId?: number;
utmSource?: string;
utmMedium?: string;
utmCampaign?: string;
}

Step 2: Dynamically Generating the Yup Schema

We needed to dynamically create a Yup schema that matched the keys and types in MetadataMap. Using Object.keys and a reducer, we mapped each key to its corresponding Yup validator:

const metadataSchema = Yup.object(
Object.keys(MetadataMap).reduce((schema, key) => {
const type = MetadataMap[key as keyof typeof MetadataMap];
if (type === Number) {
schema[key] = Yup.number().optional();
} else if (type === String) {
schema[key] = Yup.string().optional();
}
return schema;
}, {} as Record<string, any>)
);

This method eliminated hardcoding and ensured that changes in MetadataMap were reflected in the schema without manual updates.

Step 3: Adding the β€œAt Least One Key” Rule

The next challenge was ensuring that at least one key in the object had a defined value. We added a .test method to the Yup schema:

metadataSchema.test(
"at-least-one-key",
"Metadata must have at least one valid key.",
(value) => {
if (!value || typeof value !== "object") return false;
const validKeys = Object.keys(MetadataMap) as (keyof typeof MetadataMap)[];
return validKeys.some((key) => key in value && value[key] !== undefined);
}
);

This logic:

  1. Ensures the object is valid.
  2. Extracts valid keys dynamically from MetadataMap.
  3. Verifies that at least one key has a non-undefined value.

The Result
Here’s how the final schema behaves:

const exampleMetadata = {
userId: undefined,
utmSource: "google",
extraField: "invalid", // This key is ignored.
};

metadataSchema
.validate(exampleMetadata)
.then(() => console.log("Validation succeeded"))
.catch((err) => console.error("Validation failed:", err.errors));

In this example, validation succeeds because utmSource is a valid key with a non-undefined value, even though userId is undefined and extraField is not part of MetadataMap.

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

πŸ‘‹ Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay