M -
M -

Reputation: 28462

Typescript - declaring object that only has properties of a single type?

I'm trying to create an object where all values are of type SFXElem. Is it possible to tell TS that every value needs to be of type SFXElem? Just like Array<string> knows that every value in the array will be a string?

interface SFXElem {
    path: string,
    audio?: Audio
}

// This doesn't work: Error "Type 'Object' is not generic
const sfxDir1: Object<SFXElem> = {
    sound1: {path: "something.mp3"},
    sound2: {path: "other.mp3"},
}

// This could work, but I can sneak other types if I don't use <SFXElem> on every line
const sfxDir2 = {
    sound1: <SFXElem>{path: "hi"},
    sound2: <SFXElem>{path: "ho"},
    sound3: {other: "hello"}      // Allowed
};

Is there a way to tell TypeScript that only SFXElem types are allowed as object values?

Attempt 1:

I tried using Record<string, SFXElem>, as suggested by CRice, but that lets me access properties that don't exist. In the demo below, accessing the doesntExist property is allowed, when it should yield an error.

enter image description here

Upvotes: 4

Views: 1250

Answers (2)

Nenad
Nenad

Reputation: 26607

You can use Record by listing names of properties that you want to allow:

const sfxDir3: Record<"sound1" | "sound2", SFXElem> = {
    sound1: {path: "something.mp3"},
    sound2: {path: "other.mp3"},
};

It seems you are creating chicken-egg problem here. If you want to allow "any" property name on the type and only to constrain property type, then your 2nd example Record<string, SFXelem> is valid.

This would be valid usage for that case, to add extra property after initialization:

const sfxList2: Record<string, SFXElem> = {
    sound1: {path: "something.mp3"},
    sound2: {path: "other.mp3"},
};

sfxList2.doesntExistsYet = {path: "yet-another.mp3"};

Upvotes: 3

Erik
Erik

Reputation: 867

You can use Index Signatures.

interface SFXObject: {
    [index: string]: SFXElement
}

This way you can make an object of type SFXObject and every string property will need to be an SFXElement.

So in your case something like this:

const sfxDir1: SFXObject = {
    sound1: {path: "something.mp3"},
    sound2: {path: "other.mp3"},
    sound3: {other: "hello"} // Not allowed
}

Documentation here: https://www.typescriptlang.org/docs/handbook/2/objects.html

Upvotes: 3

Related Questions