3

I'm not sure why there is a problem if i use this line as array and how to fix it:

Pick<Author, PickedAuthorFields>[]

Sandbox.

If i use never instead of conditional type, than i have another problem with required author field.

Sanbox 2

Sandbox 3 with optional ? author had another problems...

Sanbox 3

This ApiResBook generic type is needed for many pages that will make requests to api with different expected output fields based on request.

Maybe there is alternative approach, i can change shape of object if needed.

// Main types as in database - should't be changed
type Book = {
  id: string
  title: string
  visible: boolean
  author: string
}

type Author = {
  id: string
  name: string
}

// Inhereted from types from main
type BookFields = keyof Book
type AuthorFields = keyof Author

// type for generating expected fetch response from API
type ApiResBook<
  PickedBookFields extends BookFields,
  PickedAuthorFields extends AuthorFields | undefined = undefined,
> = {
  book: Pick<Book, PickedBookFields> & {
    author: PickedAuthorFields extends AuthorFields ? Pick<Author, PickedAuthorFields>[] : undefined
  }
}

// tests
type BookWithAuthor = ApiResBook<'id', 'name' | 'id'>

// should be ok
const bookWithAuthor1: BookWithAuthor = { book: { id: '1', author: [{ id: '1' }] } }
const bookWithAuthor2: BookWithAuthor = { book: { id: '1', author: [{ name: 'Max' }] } }
const bookWithAuthor3: BookWithAuthor = { book: { id: '1', author: [{ name: 'Max', id: '1' }] } } // why error?
10
  • 2
    That's because conditional type is distributed over union type. You get author: Pick<Author, "id">[] | Pick<Author, "name">[] so only one of props picked. Do you really need to allow undefined as a PickedAuthorFields ? Commented Feb 20, 2020 at 11:26
  • If there is a way to do it like this: type BookWithAuthor = ApiResBook<'id'> // only book, without author prop, than no. Commented Feb 20, 2020 at 11:28
  • 1
    @AlekseyL. is right but if we remove the condition we will get author: Pick<Author, PickedAuthorFields>[] what will allow only objects with all fields, so partial objects like in two first examples will be failing. Only third would be ok Commented Feb 20, 2020 at 11:30
  • It's fine, but there is another problem: added another link in example. Commented Feb 20, 2020 at 11:31
  • 2
    @RTW I'd split utility types into two. Playground Commented Feb 20, 2020 at 11:45

1 Answer 1

3

It was a while to figurate out but I think this is the solution:

type Book = {
  id: string
  title: string
  visible: boolean
  author: string
}

type Author = {
  id: string
  name: string
}

// Inhereted from types from main
type BookFields = keyof Book
type AuthorFields = keyof Author

// type for generating expected fetch response from API
type ApiResBook<
  PickedBookFields extends BookFields,
  PickedAuthorFields extends AuthorFields | null = null,
  AuthorObj = {author: Pick<Author, Exclude<PickedAuthorFields, null>>[]}
> = {
    book: Pick<Book, PickedBookFields> & (PickedAuthorFields extends null ? {} : AuthorObj)
}


// tests
type BookWithAuthor = ApiResBook<'id', 'name' | 'id'>
type BookWithoutAuthor = ApiResBook<'id'>

// should be ok
const bookWithAuthor0: BookWithoutAuthor = { book: { id: '1' } }
const bookWithAuthor3: BookWithAuthor = { book: { id: '1', author: [{ id: '1', name: 'Max' }] } }

PS. null can be replaced by undefined or any other unit type

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

Comments

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.