Funkel
Funkel

Reputation: 183

Issues with Types when using reduce on an object

I have a function trying to return an object it receives with the keys all Pascal case instead of Camel case.

My Interfaces:

export interface INodeMailerResponseLower {
    accepted: string[];
    rejected: string[];
    envelopeTime: number;
    messageTime: number;
    messageSize: number;
    response: string;
    messageId: string;
}

export interface INodeMailerResponseUpper {
    Accepted: string[];
    Rejected: string[];
    EnvelopeTime: number;
    MessageTime: number;
    MessageSize: number;
    Response: string;
    MessageId: string;
}

My Code:

import { upperFirst } from 'lodash';

const response: INodeMailerResponseLower;   // << insert object values here
const formattedResponse: INodeMailerResponseUpper = Object.keys(response).reduce((acc, key) => {
     acc[upperFirst(key)] = response[key];
     return acc;
   }, {});

Typescript is giving me the following error message:

Type '{}' is missing the following properties from type 'INodeMailerResponseUpper': Accepted, Rejected, EnvelopeTime, MessageTime, and 3 more.ts(2740)

Question: How do I correctly type my accumulator when doing the reduce?

Upvotes: 1

Views: 1482

Answers (1)

Lauren Yim
Lauren Yim

Reputation: 14148

Use Partial<INodeMailerResponseUpper> for the reduce type parameter. This will mean acc has that type and will allow you to assign properties to it. However, you need to cast the result to INodeMailerResponseUpper because TypeScript doesn’t know that the final result will contain all the required properties.

You could do it like this, which uses a fair bit of type assertions:

const formattedResponse = Objectkeys(response)
    .reduce<Partial<INodeMailerResponseUpper>>((acc, key) => {
        (acc[upperFirst(key) as keyof INodeMailerResponseUpper] as unknown) =
            response[key as keyof INodeMailerResponseLower];
        return acc;
    }, {}) as INodeMailerResponseUpper;

Or you could also improve the type of lodash’s upperFirst (using TypeScript 4.1):

declare module 'lodash' {
    interface LoDashStatic {
        upperFirst<S extends string = ''>(string?: S): Capitalize<S>;
    }
}

const formattedResponse = (Object.keys(response) as (keyof INodeMailerResponseLower)[])
    .reduce<Partial<INodeMailerResponseUpper>>((acc, key) => {
        // Without the as unknown, TypeScript would complain that
        // string | number | string[] is not assignable to undefined
        (acc[upperFirst(key)] as unknown) = response[key];
        return acc;
    }, {}) as INodeMailerResponseUpper;

Unfortunately, there isn’t a way to completely avoid type assertions.

Upvotes: 2

Related Questions