Set every attribute inside a Typescript interface as non-mandatory

I have defined an interface within my software:

interface Asset {
  id: string;
  internal_id: string;
  usage: number;
}

This interface is a component of another interface named Post:

interface Post {
  asset: Asset;
}

In addition, there is an interface designed for a post draft, where the asset object can be incompletely constructed:

interface PostDraft {
  asset: Asset;
}

My goal is to allow a PostDraft object to contain a partially complete asset object while still enforcing type checks on the available properties (rather than using any).

Essentially, I am seeking a method to generate the following structure:

interface AssetDraft {
  id?: string;
  internal_id?: string;
  usage?: number;
}

without fully redefining the original Asset interface. Is there a technique to achieve this? If not, what would be the most efficient approach to organizing my types in this scenario?

Answer №1

In TypeScript versions prior to 2.1, achieving this isn't possible without creating an additional interface featuring optional properties. However, the use of mapped types in TypeScript 2.1 and above makes this task achievable.

To accomplish this, leverage the inherent Partial<T> type provided by TypeScript.

interface PostDraft {
    asset: Partial<Asset>;
}

Consequently, all attributes within the asset object become optional, enabling actions like:

const postDraft: PostDraft = {
    asset: {
        id: "some-id"
    }
};

Understanding Partial<T>

The Partial<T> concept is a mapped type that renders each property within the specified type as optional (via the ? symbol). Its definition can be found here.

type Partial<T> = {
    [P in keyof T]?: T[P];
};

For further insights on mapped types, refer to resources available here and within the TypeScript handbook here.

Enhancing with Deep Partiality

If a deeply partial implementation working recursively on objects is desired, TS version 4.1 and beyond offer the following type structure:

type DeepPartial<T> = {
    [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

Answer №2

In order to create a specific AssetDraft interface, I can utilize both the extends and Partial keywords:

interface Asset {
  id: string;
  internal_id: string;
  usage: number;
}

interface AssetDraft extends Partial<Asset> {}

Answer №3

In the interface, properties can be either optional or mandatory. It is not possible to mix and match the same property as both optional and mandatory within the same interface.

One solution is to create an interface with optional properties for AssetDraft, and then a class with mandatory properties for Asset:

interface AssetDraft {
    id?: string;
    internal_id?: string;
    usage?: number;
}

class Asset {
    static DEFAULT_ID = "id";
    static DEFAULT_INTERNAL_ID = "internalid";
    static DEFAULT_USAGE = 0;

    id: string;
    internal_id: string;
    usage: number;

    constructor(draft: AssetDraft) {
        this.id = draft.id || Asset.DEFAULT_ID;
        this.internal_id = draft.internal_id || Asset.DEFAULT_INTERNAL_ID;
        this.usage = draft.usage || Asset.DEFAULT_USAGE;
    }
}

The default values in this example are set as static members, but you could obtain them differently or handle missing values by throwing an error.

Using this method makes it easier to work with JSON data received from servers or similar sources. The interfaces represent the data structure, while the classes act as models constructed using the JSON data as initial values.

Answer №4

Aside from David Sherret's response, I would like to provide my own insights on how the implementation can be done directly without using the Partial<T> type, in order to enhance clarity on the topic.

interface IAsset {
  id: string;
  internal_id: string;
  usage: number;
}

interface IPost {
  asset: IAsset;
}

interface IPostDraft {
  asset: { [K in keyof IAsset]?: IAsset[K] };
}

const postDraft: IPostDraft = {
  asset: {
    usage: 123
  }
};

Answer №5

Why not consider utilizing

interface AssetDraft = { id?: string, internal_id?: string; usage?: number; }

and then expanding it for the Asset object as follows:

interface Asset extends Required<AssetDraft> {}

?

While unconventional, is there a discernible distinction in this alternate approach?

Answer №6

Have you ever considered forcefully instantiating an empty object?

const newDraft = <PostDraft>{}
newDraft.id = 123
newDraft.internal_id = 456
newDraft.usage = 789

If this is a necessity for you, one potential solution could be to create a d.ts interface based on a predefined template that encompasses both optional and typed properties.

As previously mentioned by Nitzan, in Typescript interfaces, properties can either be optional or mandatory.

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

Interacting with a form input by triggering the onChange event

I am encountering a problem where I need to be able to select a radio button both onChange via keydown and mouse click. However, I am struggling with accessing both event parameters of the on keydown and on mouse click within the same function. As a result ...

An HTML table featuring rows of input boxes that collapse when the default value is not filled in

My table is populated with dynamic rows of input boxes, some of which may have a default value while others return an empty string ''. This causes the table to collapse on those inputs. <tr *ngFor="let d of displayData"> < ...

Angular 16 SSR encounters a TypeError when the 'instanceof' operator is used on a value that is not an object

I have been facing an issue while updating my Angular application from version 15 to 16. Everything seems to work fine with Angular, including building for Angular Universal without any errors. However, when I attempt to serve using npm run serve:ssr, it t ...

Create the accurate data format rather than a combination in GraphQL code generation

In the process of migrating a setup that mirrors all the types exactly as on the server to one based solely on the document nodes we've written. Currently, the configuration is in .graphqlrc.js /** @type {import('graphql-config').IGraphQLCo ...

Developing a bespoke React Typescript button with a custom design and implementing an onClick event function

Currently, I am in the process of developing a custom button component for a React Typescript project utilizing React Hooks and Styled components. // Button.tsx import React, { MouseEvent } from "react"; import styled from "styled-components"; export int ...

A step-by-step guide on importing stompjs with rollup

My ng2 app with TypeScript utilizes stompjs successfully, but encounters issues when rollup is implemented. The import statement used is: import {Stomp} from "stompjs" However, upon running rollup, the error "EXCEPTION: Stomp is not defined" is thrown. ...

RXJS - Hold off until the shop is fully stocked

Here's a function I have that needs some adjustment: combineLatest([this.contact$, this.account$]).pipe( map((res) => {contacts = res[0], account = res[1]})).subscribe() I am facing an issue where the contact$ selector is sometimes empty. If it ...

Changing the order of a list in TypeScript according to a property called 'rank'

I am currently working on a function to rearrange a list based on their rank property. Let's consider the following example: (my object also contains other properties) var array=[ {id:1,rank:2}, {id:18,rank:1}, {id:53,rank:3}, {id:3,rank:5}, {id:19,r ...

Enhance your Next.js routing by appending to a slug/url using the <Link> component

In my Next.js project, I have organized my files in a folder-based structure like this: /dashboard/[appid]/users/[userid]/user_info.tsx When using href={"user_info"} with the built-in Next.js component on a user page, I expect the URL to dynamic ...

Having trouble with the Ng multiselect dropdown displaying empty options?

I'm currently facing a challenge in adding a multiselect dropdown feature to my project. Below is the code I have been working on: HTML <ng-multiselect-dropdown [settings]="searchSettings" [data]="dummyList" multiple> </n ...

Sharing the input value with a service in Angular 4

I am a beginner when it comes to Angular 4. I currently have a variable named "md_id" which is connected to the view in the following way. HTML: <tr *ngFor="let item of driverData"> <td class="align-ri ...

Is there a way to use Jest spyOn to monitor a function that is returned by another function?

I'm curious about why the final assertion (expect(msgSpy).toBeCalled()) in this code snippet is failing. What adjustments should be made to ensure it passes? it('spyOn test', () => { const newClient = () => { const getMsg = ...

What is the best way to transmit two distinct sets of data from a child component to the v-model of a parent component?

Currently, I am working on a project using vuejs 2 and typescript. In this project, I need to pass two different sets of data - data and attachments - within the parent component. I am utilizing vue-property-decorator for this purpose. However, I am facing ...

What steps need to be taken in VSCode to import React using IntelliSense?

When I press Enter in that image, nothing seems to occur. I believed IntelliSense would automatically insert import React from 'react'; at the beginning of the file. https://i.stack.imgur.com/7HxAf.png ...

Referencing other styled-components in Typescript and React code bases

I'm attempting to replicate this code snippet found on https://styled-components.com/docs/advanced using TypeScript. const Link = styled.a` display: flex; align-items: center; padding: 5px 10px; background: papayawhip; color: palevioletred; ...

React component showing historical highchart data when navigating through previous and next periods

I developed this element to showcase a Highchart. It's utilized within a dashboard element that I access from an item in a list. It mostly works as intended, but not entirely. When I move to the dashboard for item A, everything functions correctly. H ...

Firebase console does not show any console.log output for TypeScript cloud functions

I encountered an issue while using Typescript to write some Firebase cloud functions. Here is a snippet of my code: index.ts export * from "./Module1"; Module1.ts import * as functions from "firebase-functions"; export const test = functions.https.onR ...

Discover the power of catching Custom DOM Events in Angular

When working with an Angular library, I encountered a situation where a component within the library dispatches CustomEvents using code like the following: const domEvent = new CustomEvent('unselect', { bubbles: true }); this.elementRef.nati ...

Is it possible to evaluate a conditional in Angular after retrieving values from a subscription in an observable?

Objective: Verify conditional statement after retrieving data from an array Attempts Made: I explored various articles on SO with similar queries, but they didn't quite match my situation. I need to ensure that the entire Array is populated before ev ...

Determine parameter types and return values by analyzing the generic interface

I am currently working on a feature where I need to create a function that takes an interface as input and automatically determines the return types based on the 'key' provided in the options object passed to the function. Here is an example of ...