Having trouble getting Typescript's Pick Array to work properly?

There seems to be an issue when using this line as an array, and I'm unsure how to resolve it:

Pick<Author, PickedAuthorFields>[]

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?

After some time spent puzzling it out, I have come to believe that this is the solution:

interface BookDetails {
  id: string
  title: string
  visible: boolean
  author: string
}

interface AuthorDetails {
  id: string
  name: string
}

// Inherited from main types
type BookFields = keyof BookDetails
type AuthorFields = keyof AuthorDetails

// Type for generating expected fetch response from API
type ApiResponseBook<
  PickedBookFields extends BookFields,
  PickedAuthorFields extends AuthorFields | null = null,
  AuthorObject = {author: Pick<AuthorDetails, Exclude<PickedAuthorFields, null>>[]}
> = {
    book: Pick<BookDetails, PickedBookFields> & (PickedAuthorFields extends null ? {} : AuthorObject)
}


// Tests
type BookWithAuthor = ApiResponseBook<'id', 'name' | 'id'>
type BookWithoutAuthor = ApiResponseBook<'id'>

// Should work correctly
const bookWithAuthorExample1: BookWithoutAuthor = { book: { id: '1' } }
const bookWithAuthorExample2: BookWithAuthor = { book: { id: '1', author: [{ id: '1', name: 'Max' }] } }

Note: The use of null can be interchanged with undefined or any other unit type.

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

Answer №1

After some time spent puzzling it out, I have come to believe that this is the solution:

interface BookDetails {
  id: string
  title: string
  visible: boolean
  author: string
}

interface AuthorDetails {
  id: string
  name: string
}

// Inherited from main types
type BookFields = keyof BookDetails
type AuthorFields = keyof AuthorDetails

// Type for generating expected fetch response from API
type ApiResponseBook<
  PickedBookFields extends BookFields,
  PickedAuthorFields extends AuthorFields | null = null,
  AuthorObject = {author: Pick<AuthorDetails, Exclude<PickedAuthorFields, null>>[]}
> = {
    book: Pick<BookDetails, PickedBookFields> & (PickedAuthorFields extends null ? {} : AuthorObject)
}


// Tests
type BookWithAuthor = ApiResponseBook<'id', 'name' | 'id'>
type BookWithoutAuthor = ApiResponseBook<'id'>

// Should work correctly
const bookWithAuthorExample1: BookWithoutAuthor = { book: { id: '1' } }
const bookWithAuthorExample2: BookWithAuthor = { book: { id: '1', author: [{ id: '1', name: 'Max' }] } }

Note: The use of null can be interchanged with undefined or any other unit type.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Switching from JavaScript to TypeScript resulted in React context not being located in its respective file

I previously had my context and context provider set up in a file, and everything was working perfectly. However, I recently decided to convert all of my files to TypeScript, including this one. Unfortunately, I've encountered a strange issue that I c ...

Issues with sending emails through Nodemailer in a Next.js project using Typescript

I'm currently working on a personal project using Nodemailer along with Next.js and Typescript. This is my first time incorporating Nodemailer into my project, and I've encountered some issues while trying to make it work. I've been followin ...

Guide on using automapper in typescript to map a complex object to a "Map" or "Record" interface

I have been utilizing the automapper-ts with typescript plugin for automatic mapping. Check it out here While it works smoothly for simple objects, I encountered issues when dealing with complex ones like: Record<string, any> or Map<string, Anoth ...

What is the best way to incorporate modules into the client side of TypeScript projects?

I'm currently developing a TypeScript project for client-side JavaScript code. Prior to using TypeScript, I used to import a module in vanilla ES6 JavaScript like this: import * as THREE from 'https://threejs.org/build/three.module.js'; H ...

Unlocking the power of global JavaScript variables within an Angular 2 component

Below, you will find a global JavaScript variable that is defined. Note that @Url is an ASP.Net MVC html helper and it will be converted to a string value: <script> var rootVar = '@Url.Action("Index","Home",new { Area = ""}, null)'; Sy ...

Adjust the key values within an array of objects in TypeScript

I am looking to update the keys' values of the 1st object within an array of objects. Here is what I have attempted so far: The array of objects: const objArray: FoodItems[] = [ { apple: 4, banana: 7, 'mango & grapes': ...

What sets TypeScript apart is the distinction between types like `InstanceType<typeof MyClass>` and `: MyClass`

I have been pondering whether there is a distinction between using InstanceType to declare a type, or simply using the Class name. For instance, considering the following class: MyClass { public static foo: string = 'abc' public makeFoo() ...

Error in typography - createStyles - 'Style<Theme, StyleProps, "root"

I'm encountering an error in a small React app. Here is a screenshot of the issue: https://i.sstatic.net/ilXOT.png The project uses "@material-ui/core": "4.11.3". In the codebase, there is a component named Text.tsx with its corresponding styles defi ...

How can I enhance the mongoose Query class using Typescript?

I'm in the process of setting up caching using Mongoose, Redis, and Typescript. Here's a snippet from my cache.ts file : import mongoose, { model, Query } from "mongoose"; import redis from "redis"; //import { CacheOptions } f ...

Utilizing Express-WS app and TypeScript to manage sessions

I am currently working on setting up a node server using Typescript with the help of express and express-ws. My goal is to include Sessions in my application, so I have implemented express-session. Below you can find some pseudo code: import * as session ...

Error in Typescript: 'SyncClient' not found in Twilio

While working on my Ionic app, I encountered an issue every time I attempted to use the twilio-chat library in my project through npm install. The error consistently appeared in the .d.ts files. Here is how I imported it in my provider : import { Client ...

Piping in Angular 2 with injected dependencies

Is it possible to inject dependencies such as a service into Angular 2 pipes? import {Pipe, PipeTransform} from 'angular2/core'; import {MyService} from './service'; //How can I inject MyService into the pipe? @Pipe({name: 'expo ...

Is there a way for me to determine the value that has been assigned to a <li> key attribute in React using Jest and testing-library/react?

In my current project, I am using a combination of React with TypeScript and Jest along with Testing Library for testing purposes. I have a specific requirement to unit test some code where I need to ensure that the person.id is correctly set as the key at ...

Reacting to Appwrite events in a React Native environment

My React Native application encounters an error when subscribing to realtime events. The error message reads as follows: ERROR Error: URLSearchParams.set is not implemented, js engine: hermes. appwriteClient .subscribe( `databases.${APPWRITE_DATAB ...

Using lambda expressions to sort through an array of objects in React

My goal is to create a delete button that removes items from a list and updates the state variable accordingly. public OnDeleteClick = (): void => { const selectionCount = this._selection.getSelectedCount(); let newArray = this.state.items; for ...

Having trouble with building an Ionic3 project, getting the error message: "Execution failed for task ':app:processDebugResources'. > Failed to execute aapt"

My attempt to develop an android app in ionic 3 hit a roadblock when running 'ionic cordova build android' resulted in the error: Execution failed for task ':app:processDebugResources'. > Failed to execute aapt I have integrated plug ...

After upgrading from Angular 7 to 12, the module './rest.service.interface' does not export 'RestService' (imported as 'RestService'), causing it to not be found

Hey everyone, I've been struggling with a problem for hours now and I can't seem to fix it. Here is the interface I'm working with: import { HttpClient } from '@angular/common/http'; import { Response } from '@angular/http&apo ...

What should be included in the types field of package.json for TypeScript libraries?

I'm finding it challenging to efficiently develop multiple typescript modules simultaneously with code navigation while ensuring the correct publishing method. What should I include in the "types" field of my package.json? Referring to: Typescriptlan ...

Mastering the art of Typescript typing

I am attempting to start the REST server for an Aries agent as outlined here: import { startServer } from '@aries-framework/rest' import { Agent } from '@aries-framework/core' import { agentDependencies } from '@aries-framework/nod ...

Invoking a nested class while declaring types in TypeScript

This is the specific format of data that I am in need of this.structure=[ { id: 1, name: 'root1', children: [ { id: 2, name: 'child1' }, { id: 3, name: 'child2' } ] }, { ...