Home > Back-end >  How to write a function which takes a dynamic key and an item and pushes it to object[key]?
How to write a function which takes a dynamic key and an item and pushes it to object[key]?

Time:02-04

I have several objects and types:

interface Car {
  make: string;
  model: string;
  year: number;
}

const car: Car = {make: "Tesla", model: "Cybertruck", year: 2023 }

interface Book {
  title: string;
  author: string;
}

const book: Book = {title: "Cosmos", author: "Carl Sagan"}

interface Stuff {
  cars: Car[],
  books: Book[]
}

const stuff: Stuff = {
  cars: [car],
  books: [book]
}

I want to define a function that takes two arguments:

  • dynamic key ("cars" or "books"), and
  • an item (which will respectively be of ICar or IBook)

and pushes item to stuff[key].

Also when calling the function I want it to infer the type of an item based on the key and autosuggest keys.

What I've tried is this:

const addToStuff = ({key, item}: {
  key: keyof Stuff, 
  item: Stuff[typeof key][number] // infers item as Car | Book
}) => stuff[key].push(item)

// .push(item) errors and says 
// Argument of type 'Car | Book' is not assignable to parameter of type 'Car & Book'.
// Type 'Car' is not assignable to type 'Car & Book'.
// Type 'Car' is missing the following properties from type 'Book': title, author

here is the link of the ts playground

CodePudding user response:

In order to make it work, you need to create higher order function which is context agnostic. COnsider this example:

interface Car {
  make: string;
  model: string;
  year: number;
}

const car: Car = { make: "Tesla", model: "Cybertruck", year: 2023 }

interface Book {
  title: string;
  author: string;
}

const book: Book = { title: "Cosmos", author: "Carl Sagan" }

type Stuff = {
  cars: Car[],
  books: Book[]
}

const stuff: Stuff = {
  cars: [car],
  books: [book]
}

const withStuff = <
  Prop extends PropertyKey,
  Value, Data extends Record<Prop, Value[]>
>(data: Data) =>
  <Key extends keyof Data>({ key, item }: {
    key: Key,
    item: Data[Key][number]
  }) => {
    data[key].push(item)
  }

const addToStuff = withStuff(stuff)

addToStuff({ key: 'cars', item: { make: "Tesla", model: "Cybertruck", year: 2023 } }) // ok
addToStuff({ key: 'books', item: { make: "Tesla", model: "Cybertruck", year: 2023 } }) // expected error

As you might have noticed, I have added higher order function to addToStuff. Not it is withStuff which returns addTuStuff. You can also use any other data structure where each value is an array, not only Stuff data shape.

Playground

You can find more examples with currying in my article

  • Related