Kousha
Kousha

Reputation: 36219

How to dynamically make an interface key required (or make it optional)

Let's say I have a type

interface Test {
    foo: number;
    bar?: {
        name: string;
    };
}

const obj: Test;
// obj.bar is optional right now

But let's say I have doSomething(obj) and this method will make bar required and set it. I want doSomething to then return a new type where bar is required now.

Of course, I want this to be dynamic - as in, I don't want to create interface TestRequired and manually return that type.

Is this possible in Typescript?

Upvotes: 1

Views: 618

Answers (2)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250016

The simplest way to change a property from optional to required is to intersect the original interface with a type where the containing only that property, but required. You can easily create such a type using Required and Pick:

declare const obj2: Test & Required<Pick<Test, 'bar'>>;
obj2.bar.name // bar is required so this is ok 

Playground Link

Also note that if your function changes an existing object, you can use a custom type assertion to change the original type:


const obj: Test = { foo: 0}
let a = obj.bar // a is  { name: string; } | undefined 

obj.bar.name // err
function doSomething(o: Test): asserts o is Test & Required<Pick<Test, 'bar'>> {
    o.bar = { name: ""}
}

doSomething(obj)

obj.bar.name // ok 

Playground Link

You could also make a function that adds bar but a type assertion is necessary:


function addBar(o: Test) {
    o.bar = { name: "" }
    return o as Test & Required<Pick<Test, 'bar'>> ;
}

let obj = addBar({ foo: 0 });
obj.bar.name;

Playground Link

Upvotes: 4

CRice
CRice

Reputation: 32186

Of course. You can add or remove the optional modifier by property name or generally:

// StdLib already has the the type Partial<T> and Required<T> to make all properties of T optional or required respectively.

// Makes the named properties of T optional:
type PartialSome<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>

// Makes the named properties of T required:
type RequiredSome<T, K extends keyof T> = T & Required<Pick<T, K>>

So your example might then look like this:

interface Test {
    foo: number;
    bar?: {
        name: string;
    };
}

declare function doSomething<T extends {bar?: any}>(obj: T): RequiredSome<T, "bar">;
const obj: Test;
const other = doSomething(obj);
other.bar // bar is required!

Playground Link.

Upvotes: 3

Related Questions