When utilizing a generic schema as an argument in Typescript with Zod, the spread operator can sometimes circumvent the type checking process

My goal is to create a function that validates an object against a Zod schema in TypeScript. However, I have encountered an issue where TypeScript does not seem to properly validate the object when using a spread operator.

In the scenario below, the function myTestFunc expects a schema of any Zod schema type and a newItem that corresponds to the inferred type from the schema. TypeScript correctly flags an error when passing an object with an additional property like city: "Paris". However, when using the spread operator ...{city: "Paris"}, TypeScript does not catch the error and allows it to pass through.

import { z } from "zod";

const myTestFunc = <U extends z.AnyZodObject>(
  schema: U,
  newItem: z.infer<U>,
) => {
  console.log("some processing here");
};

const schemaA = z.object({
  name: z.string(),
  age: z.number(),
});


myTestFunc(schemaA, {
  name: "John",
  age: 25,
  city: "Paris", // <- error, as "city" is not an allowed part of the type
});

myTestFunc(schemaA, {
  name: "John",
  age: 25,
  ...{city: "Paris"}, // <- no error, so weird
});

This inconsistency with TypeScript validation is frustrating, especially when working with generic Zod objects.

I also attempted a different approach to address the issue:

import { z } from "zod";

type MyTestFuncParams<U extends z.AnyZodObject> = {
  schema: U;
  newItem: z.infer<U>;
}

const myTestFunc = <U extends z.AnyZodObject>(
  {schema,
  newItem}: MyTestFuncParams<U>,
) => {
  console.log("some processing here");
};

const schemaA = z.object({
  name: z.string(),
  age: z.number(),
});

const brokenItem = {
  name: "John",
  age: 25,
  city: "Paris",
};

myTestFunc({schema: schemaA, newItem: brokenItem}); // <- no error!

Here is what TypeScript is expecting

Here are the actual item contents

The lack of type error in this scenario is baffling. My IDE (VSCode) clearly indicates a type mismatch.

I am seeking guidance on how to modify the function to ensure proper type validation. Any assistance would be greatly appreciated!

Answer №1

myTestFunc(schemaA, {
  name: "John",
  age: 25,
  ...{city: "Paris"}, // <- no error, so weird
});

When you perform this action, you are essentially indicating that the newItem is being passed as z.infer<U>.

const tempCity: z.infer<typeof schemaA> = {
  name: "John",
  age: 25,
  ...{ city: "Paris" }, // <- no error
};

If you hover over the tempCity in the above code within a code editor, you can observe that despite tempCity having the city property, typescript is unaware of it. It only recognizes the other two properties.

const tempCity2: z.infer<typeof schemaA> = {
  name: "John",
  age: 25,
  city: "Paris", // <- error
};

Typescript highlights the issue with a red squiggly line when you pass the object literal, as at that point typescript can determine that the data's structure does not match the type. This can be verified by checking the type of tempCity2. Typescript recognizes that according to the type, city should not be part of the object.

const tempCity3 = {
  name: "John",
  age: 25,
  city: "Paris",  // <- no error
} as z.infer<typeof schemaA>;

Upon hovering over tempCity3, even though the object has three properties, the type hinting will only display name and age. This discrepancy arises because when using as, you are informing typescript that you have a better understanding of the data's type. However, this approach only functions if the object includes all properties of the intended type. Therefore, the second version of your function does not trigger an error for the same reason.

I trust this explanation clears up the type-related aspect of your query. Your function is already correctly undergoing type checking.

Spread operator not type safe

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

The typing library for Angular does not properly identify the JQueryStatic object

Encountered an issue with the Angular declaration file: Error TS2304: Cannot locate identifier 'JQueryStatic'. The typings for jQuery are installed and properly declare JQueryStatic as an interface. Looking for solutions to resolve this error. ...

Creating an interface that extends the Map object in TypeScript to maintain the order of keys

After learning that the normal object doesn't preserve key order in TypeScript, I was advised to use Map. Nevertheless, I'm struggling to figure out how to assign values once I've declared the interface. Take a look at my approach: Coding ...

Discovering the best method to retrieve user details (email address) following a successful login across all pages or components within Angular

Discovering the world of Angular and TypeScript is quite exciting. In my Angular project, I have 8 pages that include a login and registration page. I'm facing an issue where I need to access the user's email data on every page/component but the ...

Deactivate the button permanently upon a single click

In the project I'm working on, there is a verification page for e-mail addresses. When new users register, they are sent an e-mail with a link to verify their e-mail. If the link is not clicked within a certain time frame, a button appears on the page ...

Angular2: Error - trying to access 'this.' which is not defined

I have a function that is designed to retrieve and display the "best player" from an array of objects, which essentially refers to the player with the most likes. The functionality of this function works as intended and displays the desired output. However ...

Omit select dormant modules when building (Angular5)

Currently, I am collaborating on a project that is being implemented across various customer instances. Within the project, we have several lazy loaded modules, with most of them being utilized by all customers. However, there are certain modules that are ...

Do we need a peer dependency specifically for TypeScript typings or is it optional?

My TypeScript library includes a React component, and one of the optional features allows users to pass an instance of a Redux store as a prop for Redux integration. <Component reduxStore={store}></Component> Since this feature is optional, I ...

Transfer text between Angular components

Here is the landing-HTML page that I have: <div class="container"> <div> <mat-radio-group class="selected-type" [(ngModel)]="selectedType" (change)="radioChange()"> <p class="question">Which movie report would you like ...

Factory function in Angular for translating using arrow syntax

When I include TranslateModule using the following code: TranslateModule.forRoot({ loader: { provide: TranslateLoader, useFactory: HttpLoaderFactory, deps: [HttpClient] } }) where export function HttpLoaderFactory(http: H ...

Issue encountered with NextJS where the post request utilizing Bcrypt is not being recognized

In the process of developing a basic login feature using nextJS, I have successfully managed to save new usernames and encrypted passwords from the registration page. The login functionality is intended to be similar, but requires comparing the password st ...

Efficiently loading data in a table with a universal filter feature using Angular with PrimeNG

Recently, I managed to set up a datatable with the functionalities of both Lazy loading and global filter. Utilizing PrimeNG components for this implementation was a breeze. However, an issue surfaced where the global filter ceased to function when lazy lo ...

Can a C function be designed to handle both double and long double arguments simultaneously?

In the file mag.c, there is a function called mag that calculates the magnitude of an array. #include <math.h> #include "mag.h" long double mag(long double arr[], int len){ long double magnitude=0; for(int i=0;i<len;i++) ...

What is the reason for Jest attempting to resolve all components in my index.ts file?

Having a bit of trouble while using Jest (with Enzyme) to test my Typescript-React project due to an issue with an alias module. The module is being found correctly, but I believe the problem may lie in the structure of one of my files. In my jest.config ...

Arranging a list of objects in Angular 6

I am facing difficulties in sorting an array of objects The structure of the object is as follows: https://i.sstatic.net/z5UMv.png My goal is to sort the *ngFor loop based on the group_id property. component.html <ul *ngFor="let list of selectgi ...

Utilizing Node.JS and Typescript to correctly define database configuration using module.exports

I am currently utilizing Mongoose in my node.js application, which is written in Typescript. The Mongoose documentation provides clear instructions on how to connect to their database like this, but I prefer to have the configuration stored in a separate ...

How to implement ngx-infinite-scroll in Angular 4 by making a vertically scrollable table

Looking for a way to make just the table body scrollable in Angular 4 using ngx-infinite-scroll. I've tried some CSS solutions but haven't found one that works. Any help or documentation on this issue would be greatly appreciated. I attempted th ...

"Implementing a loop to dynamically add elements in TypeScript

During the loop session, I am able to retrieve data but once outside the loop, I am unable to log it. fetchDetails(){ this.retrieveData().subscribe(data => { console.log(data); this.data = data; for (var k of this.data){ // conso ...

What is the best way to calculate checksum and convert it to a 64-bit value using Javascript for handling extremely large files to avoid RAM overflow?

Question: What is the best method for generating a unique and consistent checksum across all browsers? Additionally, how can a SHA256/MD5 checksum string be converted to 64-bit? How can files be read without requiring excessive amounts of RAM when ...

Issue with deprecated TypeORM connection and isConnected functions

import { Module } from '@nestjs/common'; import { Connection } from '../../node_modules/typeorm/connection/Connection'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [TypeOrmModule.forRoot()], exports ...

Leverage the template pattern in React and react-hook-form to access a parent form property efficiently

In an effort to increase reusability, I developed a base generic form component that could be utilized in other child form components. The setup involves two main files: BaseForm.tsx import { useForm, FormProvider } from "react-hook-form" expor ...