2

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?

8
  • do you want to make sure that the keys always start with orderId? Commented Jan 25, 2022 at 6:18
  • 2
    You need to use the noUncheckedIndexedAccess option in your tsconfig but that would make both cases throw errors. As far as I know there's no way to get the first to work and the second to fail Commented Jan 25, 2022 at 6:23
  • It isn't Typescript's job to validate your orderIds - you've told Typescript to expect a string [orderId] key in an object, and that's the only thing it will know/care about. It's your job to then validate the orderId; if you're concerned that it's possible that invalid orderIds will be used to access the object, you should type it accordingly (as you've done using undefined). Commented Jan 25, 2022 at 6:25
  • @RameshReddy no, my orderIds are randomly generated Commented Jan 25, 2022 at 6:28
  • 1
    yes typescript will try to determine which type something is when there's a type union involved. Check my answer for a way to do this with a conditional type Commented Jan 25, 2022 at 7:00

2 Answers 2

3

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[1]]: {
    name: 'banana', 
    price: 123
  },
  [keys[2]]: {
    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
}

Playground link

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for the answer. I don't think I can use your symbol as a solution since for-in or Object.keys forces my keys to be string (which is fine probably JS restriction). I will still take this as a solution since you gave possible workaround noUncheckedIndexedAccess.
Object.getOwnPropertySymbols might work for iterating over symbol keys
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.