Enforcing strict property validation on variables passed into TypeScript functions

Is there a method to enforce excess-property checking, not only for an inline object literal but also one derived from a variable?

For instance, let's say I have an interface and a function

interface Animal {
    speciesName: string
    legCount: number,
}

function serializeBasicAnimalData(a: Animal) {
    // something
}


If I invoke

serializeBasicAnimalData({
    legCount: 65,
    speciesName: "weird 65-legged animal",
    specialPowers: "Devours plastic"
})

I would receive an error -- which is desired in my scenario. I only want the function to accept a general animal description without additional details.


However, if I initially assign it to a variable, the error does not occur:

var weirdAnimal = {
    legCount: 65,
    speciesName: "weird 65-legged animal",
    specialPowers: "Devours plastic"
};
serializeBasicAnimalData(weirdAnimal);


So, my question is: Is there a way to compel TypeScript to conduct "excess property checking" on the function parameter regardless of whether it's an inline object or an object previously assigned to a variable?

Answer ā„–1

Here is a solution to your issue: The problem lies in Typescript's reliance on structural typing, which is considered more advantageous than Nominal typing but still presents its own challenges.

type StrictPropertyCheck<T, TExpected, TError> = Exclude<keyof T, keyof TExpected> extends never ? {} : TError;

interface Animal {
    speciesName: string
    legCount: number,
}

function serializeBasicAnimalData<T extends Animal>(a: T & StrictPropertyCheck<T, Animal, "Only allowed properties of Animal">) {
    // something
}

var weirdAnimal = {
    legCount: 65,
    speciesName: "weird 65-legged animal",
    specialPowers: "Devours plastic"
};
serializeBasicAnimalData(weirdAnimal); // this will now fail as intended

Answer ā„–2

This solution was crucial for ensuring the correct object shape in my Redux implementation.

I found a helpful resource in this informative article and Shannon's response within this discussion here. By incorporating both, I discovered a more succinct approach to achieving the desired outcome:

export type StrictPropertyValidation<T, TExpected, TError> = T extends TExpected
  ? Exclude<keyof T, keyof TExpected> extends never
    ? T
    : TError
  : TExpected

Here, by including T extends TExpected in the StrictPropertyCheck, a subtle yet effective improvement is introduced. I felt it important to reference the article mentioned above to assist any others seeking similar guidance.

Application in Redux action creation:

export type UserCredentials = {
  email: string
  password: string
}

export type ValidatedCreds<T> = T &
  StrictPropertyValidation<
    T,
    UserCredentials,
    'ATTENTION: CREDENTIALS OBJECT CONTAINS REDUNDANT PROPERTIES'
  >

export type AuthActionType =
  | {
      type: AuthAction.LOGIN
      payload: ValidatedCreds<UserCredentials>
    }
  | { type: AuthAction.LOGOUT 
export const authenticateUser = <T>(credentials: ValidatedCreds<T>): AuthActionType => {
  return {
    type: AuthAction.LOGIN,
    payload: credentials as UserCredentials,
  }
}

Answer ā„–3

While utilizing Prisma, I encountered a similar issue related to object literals and the inference of return types based on their structure. If excess properties are passed in these objects, it can lead to runtime errors. To avoid the hassle of duplicating the entire object literal in every query, I needed a way to pass an object with a known shape that could also be validated against another type.

The generated type from Prisma, representing a config object for data retrieval, plays a crucial role in determining the return type inferred by Typescript. Hence, directly declaring the object using this type was not feasible.

  export type memberInclude = {
    account?: boolean | accountArgs
    faculty?: boolean | facultyArgs
    member_type?: boolean | member_typeArgs
    has_keyword?: boolean | has_keywordFindManyArgs
    insight?: boolean | insightFindManyArgs
    ...
  }

To address this scenario, a modified version of the suggested solution was employed to ensure that only valid keys are present in the object before exporting it.

type CheckKeysAreValid<T, ValidProps> = Exclude<keyof T, keyof ValidProps> extends never
  ? T
  : "Invalid keys" | Exclude<keyof T, keyof ValidProps>;

const _includeMemberInfo = {
  account: true,
  member_type: true,
} as const;

// Excess property validation is performed before assigning the original object type upon successful completion
export const includeMemberInfo: CheckKeysAreValid<
  typeof _includeMemberInfo,
  Prisma.memberInclude
> = _includeMemberInfo;

This approach ensures that any modifications made to table names in the Prisma schema are caught during compilation rather than at runtime, enhancing development efficiency. By specifying the exact object shape, Typescript accurately infers the return type from Prisma when executing database queries.

  const member = await db.member.findUnique({
    where: { id },
    include: includeMemberInfo,
  });
  
  // The return type is member & {account: account} & {member_type: member_type}

Answer ā„–4

This may seem confusing and surprising, but I have found a solution by including type annotations directly on the variable declaration.

-----------------šŸ‘‡
let strangeCreature: Creature = {
    legCount: 65,
    speciesName: "strange 65-legged creature",
    specialPower: "Eats plastic"
};
serializeCreatureData(strangeCreature);

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

Using GSAP in an Ionic application

What is the best way to add the GSAP library to an Ionic project? Simply running npm install gsap doesn't seem to work when I try to import it using: import { TweenMax, TimelineMax} from "gsap"; I am currently using TypeScript. Thank you. ...

having difficulty sending a post request with Angular

Submitting form data via HTTP post will look like this: saveDataFile(mutlidata,id,value): Observable<Response> { var _url = 'http://xxxx.xxx.xxx'; var saveDataURL = _url + '/' + id; var _this = this; ...

Allow Visual Studio Code to create a constructor for Typescript class

When developing Angular 2 apps in Typescript using Visual Studio Code, one common task is writing constructors with their parameter list. Is there a way to save time and effort on this? It would be really helpful if the IDE could automatically generate th ...

Continue looping in Javascript until an empty array is identified

Currently, I am in search of a solution to create a loop in Javascript that continues until the array of objects is empty. The object I am working with looks like this: "chain": { "evolves_to": [{ "evolves_to": [{ ...

Creating a TypeScript array of objects that aligns with a specific interface: A step-by-step guide

In the code snippet below, there is a Typescript interface called Product. The goal is to ensure that every object in the products array follows this interface. However, the implementation process has been challenging so far. Various attempts like products ...

Analyze two sets of JSON data and compile a new JSON containing only the shared values

I am trying to generate two JSON arrays based on a shared property value from comparing two sets of JSON data. this.linkedParticipants =[ { "id": 3, "name": "Participant 2", "email": "<a href="/ ...

Nexus and GraphQL: The root typing path for the "context" type is not found

Iā€™m currently working on integrating GraphQL into Next.js API routes. For writing the GraphQL schema, Iā€™m utilizing Nexus. Here are the two essential files: context.ts and schema.ts, that help in setting up Nexus development mode. // context.ts import ...

Activate the drop-down menu in Angular 6 by hovering over it with your mouse

I am just beginning my journey with Angular 6 and Bootstrap. Currently, I am working on a Navigation bar that consists of 3 navigation items. One of the nav items is called "Store", and when a user hovers their mouse over it, I want to display a mega menu ...

Tally up identical words without considering differences in capitalization or extra spaces

Let's take an example with different variations of the word "themselves" like "themselves", "Themselves", or " THEMSelveS " (notice the leading and trailing spaces), all should be considered as one count for themselves: 3 ...

Make sure to include a property that is indexed when typing

I am currently working on defining a type to represent a list (hash) of HTTP headers. This type is supposed to be a hash that only contains key / string pairs: type TStringStringHash = { [key: string]: string } However, the issue I am facing is that i ...

An error has been detected: An unexpected directive was found. Kindly include a @NgModule annotation

I am encountering an issue while trying to import a class into a module in my Ionic/Angular app. Upon attempting to release, the following error message appears: ERROR in : Unexpected directive 'SeedModalPage in /home/robson/Lunes/repositories/bolunes ...

What is the best way to make an attribute in an interface mandatory only when another attribute is set to true

How can a relative be made required only when another attribute is true? For Example: interface ITesteProps { required: boolean content{!required && '?'}: string } I understand that this code is not valid. Is it possible to make the ...

transmit information from the current state to a fresh task within an rxjs effect

My goal is to access state data and use it as properties for a new action. I successfully extracted the necessary data from the state after triggering the effect, but I am facing an issue when dispatching the new action with the data. It seems that I am un ...

The validation for decimal numbers fails to function when considering the length

I've been struggling to come up with a regular expression for validating decimal numbers of a specific length. So far, I've tried using pattern="[0-9]){1,2}(\.){1}([0-9]){2}", but this only works for numbers like 12.12. What I'm aimin ...

ReactNative: When attempting to navigate, a TypeError occurred - this.props.navigation.navigate is not a function

It's confusing to see how this issue is occurring, even though all props have been properly typed. The goal is to pass the navigator to the bottom bar component in order to navigate onPress. Initially, I define the props interface: export interface B ...

Encountering the issue of receiving "undefined" when utilizing the http .get() method for the first time in Angular 2

When working in Angular, I am attempting to extract data from an endpoint. Below is the service code: export class VideosService { result; constructor(private http: Http, public router: Router) { } getVideos() { this.http.get('http://local ...

Angular Pipe: Working with Data Structures in Angular With Nested Arrays and Objects

After struggling to customize answers for similar questions, I find myself in need of some assistance. My current challenge involves using an angular pipe to filter the following data structure: subjects = [ { name: "subject1", keywords:[& ...

Testing a reusable component in Angular using unit testing techniques

Currently, I am creating a test for an AppleComponent which has the following type: <T,U extends BananaComponent<T>>. This component also contains BananaComponent<T>. Target Component export class AppleComponent<T,U extends BananaCom ...

Utilizing Next.js App Router to Enable Static Site Generation for Dynamic Routes in Live Environments

For my portfolio, I am utilizing Next.js's new App Router to highlight projects with dynamic routing (src/app/projects/[id]/page.tsx). During development, useSearchParams is able to correctly retrieve the project ID. However, in production, it returns ...

The issue with Angular 2's router.navigate not functioning as expected within a nested JavaScript function

Consider the app module: import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { RouterModule } from '@angul ...