0

I have an interface for a book with an optional id (for case a new book which isn't saved yet to server):

interface Book {
   id?: string
}

When I fetch all books from backend - I use to create an object byId:

allIds: payload.map(book => book.id!),

where allIds declaration is:

allIds: string[]

as you can see, I added the ! after the book.id in order to specify each book has an Id. as if I won't do that, Typescript will not compile with:

TS2322: Type '(string | undefined)[]' is not assignable to type 'string[]'.

Type 'string | undefined' is not assignable to type 'string'.

 Type 'undefined' is not assignable to type 'string'.

Now, It's working with the !, and I don't expect any book to missing its id, but I would like to find an elegant way to validate it.. just in case.

I can do something like:

allIds: payload.map(book => {
   if (!book.id) {
      throw new Error("invalid book id")
   }
   return book.id
}),

but I would like to know if you have any idea for a nicer one-liner elegant solution to validate the items while mapping it.

2
  • So what should happen when id doesn't exist in the Book element? Commented Feb 9, 2020 at 19:56
  • raising Error is good enough for me for now @Buszmen Commented Feb 10, 2020 at 9:34

2 Answers 2

3

I don't expect any book to missing its id

Then remove ? from the interface.

If you want to filter out elements without the id so allIds will be all strings and if you insist on one-liner:

const allStrings: string[] = payload.map(book => book.id).filter(<T>(v: T): v is Exclude<T, undefined> => typeof v !== 'undefined');

If for some reason you don't have access to Book interface, you can use this type for incoming data:

Required<Book>

which will make id no longer optional.

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

1 Comment

I can't remove the ? as the same model is used to create new book.. and in this case the id is undefined (yet.. until it will be sent to server)
2

How about encapsulating the assertion logic in a separate function?

function assertDefined<T>(t: T | undefined | null): T {
    if (t == undefined) throw new Error("undefined or null item.")
    return t
}

const res = payload.map(book => assertDefined(book.id)) // string[]

You could define a propOrThrow function to make it a bit shorter:

function propOrThrow<T, U>(mapper: (t: T) => U | undefined) {
    return (t: T): U => assertDefined(mapper(t))
}

const res2 = payload.map(propOrThrow(b => b.id)) // string[]

Code sample

3 Comments

Thank you! Both comments are great. I wish I could've approved both, but I was looking for one liner in the question.
Pick what you need :-). Be aware though, that a filter operation is different from a throw like in your question. Depends on, what you understand by validation
agree :) After thought about that we decided to go with filter to ignore such cases instead of breaking the app until the database will be investigated.

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.