0

First look, below function addPropertyIfValueIsNotUndefined works:

function addPropertyIfValueIsNotUndefined<Value>(key: string, value: Value): { key?: Value; } {
    return isNotUndefined(value) ? { [key]: value } : {};
}

function isNotUndefined<TargetValue>(targetValue: TargetValue | undefined): targetValue is TargetValue {
  return typeof targetValue !== "undefined";
}


type Test = {
    alpha: string;
    bravo?: string; 
}

const test1: Test = { 
    alpha: "ALPHA1" ,
    // bravo: "BRAVO1"
};

const test2: Test = {
    alpha: "ALPHA2",
    ...addPropertyIfValueIsNotUndefined("bravo", test1.bravo)
};

console.log(test2)

// Neither TypeScript compiler nor JavaScript VM errors until here

But what if try to add some property that does not declare on type Test? I am expecting that TypeScript emit the error TS2322 Type { gibberish: string; bravo: string | undefined; alpha: string; }' is not assignable to type 'Test', but TypeScript will not emit error or warning!

const test2: Type = {
    alpha: "ALPHA2",
    ...addPropertyIfValueIsNotUndefined("gibberish", test1.bravo)
};

Seems like I understood why. But how to annotate the returning value of addPropertyIfValueIsNotUndefined? We don't know the key at advance but need refer to it.

Fiddle

Takeshi Tokugawa YD
  • 670
  • 5
  • 40
  • 124
  • What exatcly you're trying to achieve? Are you trying to copy some property from one object to another? Is it correct that first parameter is a key that should be added to object and the second parameter is value that should be copied? What kind of type check do you expect? – Николай Гольцев Nov 03 '20 at 07:11
  • @НиколайГольцев, I'm sorry about unclear question. "What exatcly you're trying to achieve?" - I want TypeScript emit the error `TS2322 Type { gibberish: string; bravo: string | undefined; alpha: string; }' is not assignable to type 'Test'`. – Takeshi Tokugawa YD Nov 03 '20 at 07:32
  • 2
    @TakesiTokugawaYD I got your question now. Sadly it's a known case in typescript. You can check this so [question](https://stackoverflow.com/questions/59318739/is-there-an-option-to-make-spreading-an-object-strict) for further info. – Eldar Nov 03 '20 at 08:01
  • @Eldar, thank you for reading my comments. So the verdict of this question is "No simple way to reach desired effect in TypeScript <=4.1"? – Takeshi Tokugawa YD Nov 04 '20 at 02:12
  • 1
    Yes, and it's a feature of typescript rather than a lack of a feature. And it seems it will stay as-is for a while. – Eldar Nov 04 '20 at 06:58
  • @Eldar, Got it. Thanks to you, this question is is settled. – Takeshi Tokugawa YD Nov 05 '20 at 02:13

1 Answers1

0

Here's a slightly modified approach which I think accomplishes your goal. Instead of having a separate function to create the {key: value} object, we pass the existing object into the function.

function addPropertyIfValueIsNotUndefined<T extends {}, K extends keyof T>(
   object: T, key: K, value: Required<T>[K]
): T & Required<Pick<T, K>> {
    return propertyIsNotUndefined(object, key) ? object : { ...object, [key]: value } as T & Required<Pick<T, K>>
}

Note that we have to use as to return the correct type because of how typescript treats dynamic keys and because it is theoretically possibly for the generic K to be wider than just the single key, in which case our return type could be incorrect.

The boolean checker now looks at the property on the object:

function propertyIsNotUndefined<T extends {}, K extends keyof T>(object: T, key: K): object is T & Required<Pick<T, K>> {
  return object[key] !== undefined;
}

The usage looks like this:

const test2 = addPropertyIfValueIsNotUndefined(test1, "bravo", "value") // ok

const test3 = addPropertyIfValueIsNotUndefined(test1, "bravo", undefined) 
// error: Argument of type 'undefined' is not assignable to parameter of type 'string'

const test4 = addPropertyIfValueIsNotUndefined(test1, "wrongKey", "value") 
// error: Argument of type '"wrongKey"' is not assignable to parameter of type '"bravo" | "alpha"'

Typescript Playground Link

Linda Paiste
  • 38,446
  • 6
  • 64
  • 102