I want to define the object type that I download from my database.
type ActiveOrders = {[orderId: string]: {name: string; price: number}}
const activeOrders: ActiveOrders = {
'orderId1': {name: 'apple', price: 123},
'orderId2': {name: 'banana', price: 123},
'orderId3': {name: 'tesla', price: 99999999},
}
Following code is fine, and my orderData is guaranteed to exist.
for(const orderId in activeOrders) {
const orderData = activeOrders[orderId]
// This is fine, orderData is guaranteed to exist
const {name, price} = orderData
}
This is NOT fine, but typescript is not giving me any error. someRandomId can come from anywhere such as user entered value.
const orderData2 = activeOrders['someRandomId']
// This is NOT fine, orderData2 is possibly undefined, but typescript says it is guaranteed to exist.
const {name, price} = orderData2
I can change my type to following but I want to avoid as it will mess up my for-in loop.
type ActiveOrders = {[orderId: string]: {name: string; price: number} | undefined}
Is there more elegant solution to this?
CodePudding user response:
By definition TypeScript will assume that any key you use to access this type of object is valid because you basically said it would be.
You can use noUncheckedIndexedAccess to force typescript to assume that there is always a chance the result is undefined when using any arbitrary string to index the object.
You can use a conditional type to specify when the object is assumed to always be defined and when it could be undefined (or anything really):
type ActiveOrders = {
[orderId in string|symbol]: orderId extends string ? ({
name: string;
price: number
}|undefined) : {
name: string;
price: number
}
}
const keys : symbol[] = [
Symbol('a'),
Symbol('b'),
Symbol('c')
];
const activeOrders: ActiveOrders = {
[keys[0]]: {
name: 'apple',
price: 123
},
[keys[0]]: {
name: 'banana',
price: 123
},
[keys[0]]: {
name: 'tesla',
price: 99999999
},
}
for(const symbolKey of keys) {
const orderData = activeOrders[symbolKey]
// This is fine, indexed with a symbol the data is assumed to be there
const {name, price} = orderData
}
CodePudding user response:
The ActiveOrders type you've defined reads as follows:
// An ActiveOrders is a map...
type ActiveOrders = {
// with arbitrary string keys in it that correspond to map values...
[orderId: string]: {
// that must contain a `name` key whose value is a string
name: string;
// and a `price` key whose value is a number
price: number;
}
};
By this definition, activeOrders['someRandomId'] is valid. The ActiveOrders type allows for any string key to exist within its map, and the type does not know what those keys will be.
With TypeScript 4.1 , you do have some freedom over defining what constitutes an acceptable key, but remember that, while you may be coding with TypeScript, your code is being transpiled to JavaScript where your type is no longer upheld.
So it really comes down to you to safety check each datum; the types are just there as guard rails for developers. If you want some sort of runtime verification of the data's shape, you could use a schema validator like yup, but this seems weirdly placed next to TypeScript.
