What is the best way to create a type guard for a path containing a dynamic field

In certain scenarios, my field can potentially contain both a schema and an object where this schema is defined by key. How can a guard effectively tackle this issue?

Below is an example of the code:

import * as z from 'zod';
import type { ZodTypeAny } from 'zod'

type Key = 'test' | 'test2' | 'test3'

type Schema = {
  schema?: ZodTypeAny |  {
    [Property in Key]?: ZodTypeAny
  }
}

const userSchema = z.object({
  name: z.string(),
  email: z.string(),
});


const getSchema = (key: Key, path: Schema) {
  return path.schema?.[key] || path.schema
}

getSchema('test', { schema: {'test':  userSchema}})
getSchema('test', { schema: userSchema})

You can test the code on TypeScript Playground here

Answer №1

Although I'm not well-versed in Zod, based on your description, this code snippet seems to align with what you're looking for:

import * as z from 'zod';
import type { ZodTypeAny } from 'zod'

type Key = 'test' | 'test2' | 'test3'

type SchemaObject = {
    [Property in Key]?: ZodTypeAny
}

type Schema = {
  schema?: ZodTypeAny | SchemaObject
}

const userSchema = z.object({
  name: z.string(),
  email: z.string(),
});

function isSchemaObject(z: Schema['schema']): z is SchemaObject {
  return Object.prototype.hasOwnProperty.call(z, 'test')
    || Object.prototype.hasOwnProperty.call(z, 'test2') 
    || Object.prototype.hasOwnProperty.call(z, 'test3') 
}

const getSchema = (key: Key, path: Schema) => {
  const schema = path.schema;
  return isSchemaObject(schema) ? schema[key] : schema
}

getSchema('test', { schema: {'test':  userSchema}})
getSchema('test', { schema: userSchema})

This code introduces a SchemaObject type for easier reference and employs a type guard isSchemaObject to determine the component of the union type.

Answer №2

To determine if schema is of type ZodType<any, any, any>, you can add a type predicate. Here's an example:

const isSchemaZodType = (schema: unknown): schema is ZodTypeAny => schema instanceof z.ZodType<any, any, any>

You can then use it in your code like this:

const getSchema = (key: Key, path: Schema) => {
  if (!path.schema) {
    return path
  }

  if (isSchemaZodType(path.schema)) {
    return path.schema
  }

  return path.schema[key]
}

Feel free to test it out on the TypeScript Playground here

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

MUI version 5 with styled-components and ListItemButton: The specified property 'to'/'component' is not recognized

While transitioning from MUI v4 to v5, I encountered a hurdle: in v4, I utilized makeStyles(), but now aim to fully switch to styled(). Unfortunately, I am struggling to get Typescript to recognize a styled(ListItemButton)(...) with to= and component= attr ...

Try querying again if you receive no results from an http.get request in Angular using RXJS Operators

In my Angular service, I sometimes encounter an issue where I receive an empty array. In such cases, I would like to trigger a fresh query. let request = this.http.post(this.searchlUrl, payload).pipe( retryWhen(errors => errors.pipe(delay(100 ...

Troubleshooting Issue with Angular Library: Live Reload Feature Not Functioning

In setting up my Angular workspace, I have 3 libraries and one application (with more to be added in the future). This is how the TypeScript paths are configured: "paths": { "@lib/a/*": [ "projects/libs/a/*", ...

Ways to access the chosen value from Ionic's popover modal

I have been working on a simple Ionic 4 Angular app and I am using an Ionic popover modal. The code below shows how I open the popover modal in my application: //home.page.ts async openModal(ev: Event) { const modal = await this.popoverController.create({ ...

Angular 8 delivers an observable as a result following a series of asynchronous requests

I am working on a simple function that executes 3 asynchronous functions in sequence: fetchData() { this.fetchUsers('2') .pipe( flatMap((data: any) => { return this.fetchPosts(data.id); }), fl ...

What is the reason behind the ability to reassign an incompatible function to another in TypeScript?

I discovered this question while using the following guide: https://basarat.gitbooks.io/typescript/content/docs/types/type-compatibility.html#types-of-arguments. Here is an example snippet of code: /** Type Heirarchy */ interface Point2D { x: number; y: ...

Managing API responses using Redux and Typescript

As a beginner in Typescript, I am struggling to integrate Redux with it. The documentation on using Redux with Typescript is confusing me. I am attempting to fetch data and dispatch it to my reducer for future use, just as I did before adopting Typescript ...

Unable to pass a parameter through an Angular http.get request

I've encountered an issue where I am attempting to pass the page number and page size values to a web API, but for some reason, no parameters are being passed. I have thoroughly debugged the application in VS Code, and verified that the pagingModel ob ...

Icon for closing Mui Snackbar

I am facing an issue with my notification component that uses the mui snackbar to display alerts. I want to display multiple notifications stacked vertically, but when I try to close one notification using the "Close" icon, it ends up closing both that o ...

What is the best way to save a string for future use in Angular after receiving it from a POST request API?

I have been assigned to a project involving javascript/typescript/angular, even though I have limited experience with these technologies. As a result, please bear with me as I may lack some knowledge in this area. In the scenario where a user logs in, ther ...

When `strictNullChecks` is turned on, how does the `void` type differ from the `undefined` literal type?

When strictNullChecks is turned on: (u: undefined, v: void, n: null) => { v = u; u = v; // type error: Type 'void' is not assignable to type 'undefined' v = n; // type error: Type 'null' is not assignable to type &ap ...

What is the reason for not hashing the password in this system?

My password hashing code using Argon2 is shown below: import { ForbiddenException, Injectable } from '@nestjs/common'; import { PrismaService } from 'src/prisma/prisma.service'; import { AuthDto } from './dto'; import * as arg ...

Verify the completeness of data types within an array in typescript

I am currently developing a comprehensive match function that I want to ensure is exhaustive during compile time. Although adding a default case would help with this, I am intrigued by some interesting dependent typing techniques I have come across. It wou ...

Utilize a dynamically defined union type to create a versatile callback function

I'm currently working on creating a message subscription function. A basic version without types is shown below: function createMessage(message) { postMessage(message) } function addSubscriber(messageType, callback) { handleNewMessage(message =&g ...

Create dynamic modals in ReactJS that appear when a row is clicked

Engaged in a project for an undisclosed entity where patient data is retrieved from their API and displayed as modal on the page. Clicking on a modal reveals more threat information in another modal. The objective is for these modals to render based on a c ...

What is the best way to prevent the hassle of manually reloading a VS Code extension each time I make updates

While working on my VS Code extension, I keep encountering the issue of opening a new instance of VS Code every time I run the extension to view recent changes. This becomes especially tedious when using VS Code remote and having to enter my password twice ...

express-validator not providing any feedback from endpoint when integrated with TypeScript

I've been working on validating the response body for my endpoint, but I'm running into an issue where I'm not getting a response from that endpoint when using express-validator. I'm confident that I have followed the official documenta ...

Pinia is having trouble importing the named export 'computed' from a non-ECMAScript module. Only the default export is accessible in this case

Having trouble using Pinia in Nuxt 2.15.8 and Vue 2.7.10 with Typescript. I've tried numerous methods and installed various dependencies, but nothing seems to work. After exhausting all options, I even had to restart my main folders on GitHub. The dep ...

Can a TypeScript generator function be accurately typed with additional functionality?

Generator functions come with prototype properties that allow for the addition of behavior. The generated generators inherit this behavior. Unfortunately, TypeScript does not seem to recognize this feature, leaving me unsure of how to make it aware. For i ...

Using TypeScript's conditional types for assigning types in React

I'm tasked with creating a component that can belong to two different types. Let's call them Type A = { a: SomeCustomType } Type B = { b: SomeOtherDifferentType } Based on my understanding, I can define the type of this component as function C ...