0

How to write definition of type in function argument which is one of object properties with same type?

For example I have object:

type Article = {
  name: string;
  quantity: number;
  priceNet: number;
  priceGross: number;
};

and want to write function which summarise price depending on price type property:

function summarise(article: Article, priceTypeProperty: NeededProperty) {
  return article[priceTypeProperty] * article.quantity;
}

How definition of NeededProperty should look like to ensure typescript that it's one of Article properties priceNet or priceGross and it is number type?

Piotr Witkoś
  • 334
  • 3
  • 10
  • 2
    Does [this approach](https://www.typescriptlang.org/play?ts=5.0.4#code/C4TwDgpgBAggTsAlgYwDbQLxQN4CgpQB2AhgLYQBcUAzsHIoQOYDc+UAjgK7GFKhWFOpAEYQ4rAmHrIIAOQjABQ0eLZSUEAOJwA9tWpKRY1gF9WuUJCgAFaRFs7ICRBGpQsAawggdAMxxsANoA0lAMUF4+-vBIaNDEbqEQAB7AEIQAJm4ABuoyACTYtPRMJtlsBAD8sM5xIQC6UClpmW6CRnAVBFDVwV0EAhAAbmJdgyNw9VQxKOgNpubIOoS0HNy8iKDuUACM5r6chMhIyzRCpMT01BAAFJex6NO16AA0UHkQACrg9rpO-DY7A5-i5qABKAIEOAKThwQhQe6zCCBD7fSDAsSgRoAKjWPD4IFMuCAA) work for you? If yes, I'll write an answer explaining; If not, what am I missing? – wonderflame Aug 11 '23 at 08:44
  • Yes, it works but it's very complicated :/ See my answer which is much simpler: [my answer](https://stackoverflow.com/a/76882361/7008104) – Piotr Witkoś Aug 11 '23 at 09:49
  • Just spell out `priceTypeProperty: 'priceNet' | 'priceGross'`, am I missing something? – Bergi Aug 11 '23 at 11:09
  • @Bergi your solution doesn't give typescript information about type of `'priceNet' | 'priceGross'` and also not ensures that Article object contains that properties. – Piotr Witkoś Aug 11 '23 at 11:26
  • @PiotrWitkoś Not sure what you mean by "*doesn't give typescript information*"? And it certainly would complain about the expression `article[priceTypeProperty]` if `priceTypeProperty` was not a property of `Article`, or if that property was not numeric. – Bergi Aug 11 '23 at 12:21
  • @Bergi I mean that `priceTypeProperty: 'priceNet' | 'priceGross'` specifies only that `priceTypeProperty` can be string with value `'priceNet'` or `'priceGross'`, but it doesn't keep information what type will have `article[priceTypeProperty]`. My [solution](https://stackoverflow.com/a/76882361/7008104) ensures typescript that `article[priceTypeProperty]` is `number` so it not shows error – Piotr Witkoś Aug 13 '23 at 08:41
  • @PiotrWitkoś No. `article[priceTypeProperty]` is properly inferred to type `number`, regardless how you do it. Using `… & keyof Article` (or the same thing with `Pick`) does not guarantee that the properties are numbers either, it would not compile if you were to change the `Article` property types - just the same as the simple solution. However, if you were the `Article` property *names*, your solution would continue to compile (but complain only at the call sites) while mine would complain at the property access. – Bergi Aug 13 '23 at 11:53

2 Answers2

1

To achieve this you could use keyof and a type constraint to make sure that the parameter is one of the valid key names in the Article type and must be (priceNet or priceGross) and its a number type

type Article = {
  name: string;
  quantity: number;
  priceNet: number;
  priceGross: number;
};

function summarise(article: Article, priceTypeProperty: keyof Article & ("priceNet" | "priceGross")) {
  return article[priceTypeProperty] * article.quantity;
}

// Usage
const myArticle: Article = {
  name: "Example Article",
  quantity: 5,
  priceNet: 10,
  priceGross: 12
};

const totalPriceNet = summarise(myArticle, "priceNet"); // Works
const totalPriceGross = summarise(myArticle, "priceGross"); // Also works

const totalPriceGross = summarise(myArticle, "otherPrice"); // does not work
Yaman Abd
  • 116
  • 1
  • 4
0

Previous answer by Yaman Abd is correct, but I found little more pretty solution:

Solution

type NeededType = keyof Pick<Article, 'priceNet' | 'priceGross'>;

Explanation

  • keyof makes string type from one of given object properties
  • Pick is utility type that helps to select properties from Article type. So we are ensured that property is included in Article object type.

This solution suggest properties which you can use while defining type NeededType.

Example

So proper example look like this:

type Article = {
  name: string;
  quantity: number;
  priceNet: number;
  priceGross: number;
};
    
type NeededType = keyof Pick<Article, 'priceNet' | 'priceGross'>;

function summarise(article: Article, priceTypeProperty: NeededType) {
  return article[priceTypeProperty] * article.quantity;
}
    
// Usage
const myArticle: Article = {
  name: "Example Article",
  quantity: 5,
  priceNet: 10,
  priceGross: 12
};
    
const totalPriceNet = summarise(myArticle, "priceNet"); // Works
const totalPriceGross = summarise(myArticle, "priceGross"); // Also works
    
const totalPriceGross = summarise(myArticle, "otherPrice"); // does not work
Piotr Witkoś
  • 334
  • 3
  • 10