Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: declare that ALL properties on an object must be of the same type

Tags:

In Typescript you can declare that all elements in an array are of the same type like this:

const theArray: MyInterface[]

Is there anything similar you can do that declares that ALL of an object's property values must be of the same type? (without specifying every property name)

For example, I'm currently doing this:

interface MyInterface {
    name:string;
}

const allTheThingsCurrently = {
    first: <MyInterface>{name: 'first thing name' },
    second: <MyInterface>{name: 'second thing name' },
    third: <MyInterface>{name: 'third thing name' },
    //...
};

...note how I have to specify <MyInterface> for every single property. Is there any kind of shortcut for this? i.e. I'm imagining something like this...

const allTheThingsWanted:MyInterface{} = {
    first: {name: 'first thing name' },
    second: {name: 'second thing name' },
    third: {name: 'third thing name' },
    //...
};

MyInterface{} is the part that's invalid code and I'm looking for a way to do with less redundancy, and optionally the extra strictness that prevents any other properties being adding to the object of a differing type.

like image 675
LaVache Avatar asked Jul 09 '18 02:07

LaVache


People also ask

How do you define object of objects type in TypeScript?

In TypeScript, object is the type of all non-primitive values (primitive values are undefined , null , booleans, numbers, bigints, strings). With this type, we can't access any properties of a value.

How do you access the properties of objects in TypeScript?

To dynamically access an object's property:Use keyof typeof obj as the type of the dynamic key, e.g. type ObjectKey = keyof typeof obj; . Use bracket notation to access the object's property, e.g. obj[myVar] .

What is type definition in TypeScript?

Built-in Type Definitions TypeScript includes declaration files for all of the standardized built-in APIs available in JavaScript runtimes. This includes things like methods and properties of built-in types like string or function , top-level names like Math and Object , and their associated types.

Is type a keyword in TypeScript?

In typescript the type keyword defines an alias to a type. We can also use the type keyword to define user defined types.


2 Answers

Solution 1: Indexable type

interface Thing {
  name: string
}

interface ThingMap {
  [thingName: string]: Thing
}

const allTheThings: ThingMap = {
  first: { name: "first thing name" },
  second: { name: "second thing name" },
  third: { name: "third thing name" },
}

The downside here is that you'd be able to access any property off of allTheThings without any error:

allTheThings.nonexistent // type is Thing

This can be made safer by defining ThingMap as [thingName: string]: Thing | void, but that would require null checks all over the place, even if you were accessing a property you know is there.

Solution 2: Generics with a no-op function

const createThings = <M extends ThingMap>(things: M) => things

const allTheThings = createThings({
  first: { name: "first thing name" },
  second: { name: "second thing name" },
  third: { name: "third thing name" },
  fourth: { oops: 'lol!' }, // error here
})

allTheThings.first
allTheThings.nonexistent // comment out "fourth" above, error here

The createThings function has a generic M, and M can be anything, as long as all of the values are Thing, then it returns M. When you pass in an object, it'll validate the object against the type after the extends, while returning the same shape of what you passed in.

This is the "smartest" solution, but uses a somewhat clever-looking hack to actually get it working. Regardless, until TS adds a better pattern to support cases like this, this would be my preferred route.

like image 79
kingdaro Avatar answered Sep 23 '22 13:09

kingdaro


Some alternatives for single level (flat) objects:

Alternative 1 (indexable type):

const exampleObj: { [k: string]: string } = {
  first: 'premier',
  second: 'deuxieme',
  third: 'troisieme',
}

Alternative 2 (Record):

const exampleObj: Record<string, string> = {
  first: 'premier',
  second: 'deuxieme',
  third: 'troisieme',
}

Alternative 3 (Record / Union):

const exampleObj: Record<'first' | 'second' | 'third', string> = {
  first: 'premier',
  second: 'deuxieme',
  third: 'troisieme',
}
like image 26
Fellow Stranger Avatar answered Sep 24 '22 13:09

Fellow Stranger