Vamsi
Vamsi

Reputation: 9780

Typescript replace generic for object keys and value

I have several code blocks that look like this. Is there any better way to do this with generics or some other typescript feature?

    const { authors } = book
    
    if (authors?.author) {
      if (Array.isArray(authors.author)) {
        book.authors = authors.author
      } else {
        book.authors = [authors.author]
      }
    }

and

    const { bookLinks } = book
    
    if (bookLinks?.bookLink) {
      if (Array.isArray(bookLinks.bookLink)) {
        book.bookLinks = bookLinks.bookLink
      } else {
        book.bookLinks = [bookLinks.bookLink]
      }
    }

I want to create a function that takes two parameters, say ('authors', 'author') or ('bookLinks', 'bookLink') and replace the variables, in the above code.

Upvotes: 0

Views: 664

Answers (1)

jcalz
jcalz

Reputation: 329418

I'm not 100% sure I'm following the use case, but maybe your function could look like this:

function fixBook<K extends PropertyKey>(
    book: any, key: K, subKey: PropertyKey
): asserts book is { [P in K]?: unknown[] } {
    if (!book) throw new Error("This is not an object"); 
    const { [key]: prop } = book;
    if (prop?.[subKey]) {
        if (Array.isArray(prop[subKey])) {
            book[key] = prop[subKey];
        } else {
            book[key] = [prop[subKey]];
        }
    } 
}

This should behave similarly to your above code. It is an assertion function, which means that after you call it, it will narrow the type of the input object so you can access its properties.

Example:

const book: unknown = {
    authors: { author: "A" },
    bookLinks: { bookLink: ["b", "c"] }
}

Here we have book of type unknown... the annotated unknown makes the compiler forget the actual type, so this should replicate the situation in which you get book from an API and don't know what type it is:

book.authors.author; // error! object is of type unknown

Now we call fixBook() twice. First:

fixBook(book, "authors", "author");

After that statement, book has been narrowed from unknown to {authors?: unknown[]}. (Note that it's not {authors: string[]}, since the compiler has no idea what type book?.authors?.author will be. Following the different code paths, I think after you run the function, the particular property will either be undefined or some array of unknown type.) And then:

fixBook(book, "bookLinks", "bookLink");

After that statement, book has been narrowed further to {authors?: unknown[]} & {bookLinks?: unknown[]}. We can verify by accessing the authors and bookLinks properties:

console.log(book.authors?.join(",")); // A
console.log(book.bookLinks?.join(",")); // b,c

Looks reasonable.


Okay, hope that helps give you some direction; good luck!

Playground link to code

Upvotes: 1

Related Questions