204

I have an interface in my application:

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

that is part of a post interface:

interface Post {
  asset: Asset;
}

I also have an interface that is for a post draft, where the asset object might only be partially constructed

interface PostDraft {
  asset: Asset;
}

I want to allow a PostDraft object to have a partial asset object while still checking types on the properties that are there (so I don't want to just swap it out with any).

I basically want a way to be able to generate the following:

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

without entirely re-defining the Asset interface. Is there a way to do this? If not, what would the smart way to arrange my types in this situation be?

jkjustjoshing
  • 3,370
  • 4
  • 21
  • 22
  • Today you need to make that second interface manually, though this could change in the near future: check out the [partial types](https://github.com/Microsoft/TypeScript/issues/4889) issue on the ts repo if you're interested. – Alex Guerra Sep 26 '16 at 22:59

6 Answers6

410

This isn't possible in TypeScript < 2.1 without creating an additional interface with optional properties; however, this is possible by using mapped types in TypeScript 2.1+.

To do this, use the Partial<T> type which TypeScript provides by default.

interface PostDraft {
    asset: Partial<Asset>;
}

Now all the properties on asset are optional, which will allow you to do the following:

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

About Partial<T>

Partial<T> is defined as a mapped type that makes every property in the provided type optional (using the ? token).

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

Read more about mapped types here and in the handbook.

Deep Partial

If you want a partial implementation that works recursively on objects then you can use the following type in TS 4.1+:

type DeepPartial<T> = {
    [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
David Sherret
  • 101,669
  • 28
  • 188
  • 178
  • @david is it possible to go the other way? To mark fields that were previously partial as required? And also is it possible to do it on a per-field basis? – Tony Mar 26 '18 at 15:34
  • 2
    @Tony yes, it will be possible with conditional types in [TS 2.8](https://blogs.msdn.microsoft.com/typescript/2018/03/15/announcing-typescript-2-8-rc/) (see `Required`). – David Sherret Mar 26 '18 at 15:35
  • 2
    omg this is such a clean solution. Wish I knew this a bit earlier – CiriousJoker Oct 07 '18 at 16:04
  • 3
    Continued to be amazed at the thought that went into TS...from Microsoft no less. Wish the same intentionality went into their web browser development (-_-) – greaterKing Feb 06 '19 at 16:05
23

If I want to have an explicit AssetDraft interface, I would use a combination of extends and Partial:

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

interface AssetDraft extends Partial<Asset> {}
dhilt
  • 18,707
  • 8
  • 70
  • 85
  • this is really straightforward and worked perfectly well for me. I was trying to define an interface for an object returned by a function that needs to have all defined properties, as well as an options object passed to the function that could optionally specify any of those properties. – Nevermore Jul 20 '22 at 22:29
8

The properties in the interface are either optional are not, you can not use the same interface once as optional and once as must.

What you can do is to have an interface with optional properties for the AssetDraft and then a class with mandatory properties for the 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 here are static members, but you can get those in other ways or throw an error in case they are missing.

I find this way very comfortable when working with jsons that are received from the server (or something similar), the interfaces represent the json data and the classes are the actual models that are constructed using the jsons as initial values.

Nitzan Tomer
  • 155,636
  • 47
  • 315
  • 299
0

In addition to David Sherret answer just a few lines from my side how it can be implemented directly without Partial<T> type for better understanding of the subject.

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
  }
};
WebBrother
  • 1,447
  • 20
  • 31
0

Wouldn't it be better to use

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

and then extend it for Asset as

interface Asset extends Required<AssetDraft> {}

?

I understand this is reverse approach, but is there a difference?

Reabon
  • 1
  • 1
-2

How about force casting an empty object e.g

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

If you really really need this then you can always generate a d.ts interface from a template that makes both the optional and typed properties.

As Nitzan pointed out, either Typescript interface properties are optional or not

Paul Rooney
  • 20,879
  • 9
  • 40
  • 61
user3893988
  • 330
  • 3
  • 6