Generic Object remapping function with proper return type

Viewed 44

So basically I have data coming in from another via a post request in a large object. I need to pull out certain fields and put them in more specific objects with some of their fields remapped to then be inserted into a database.

I have this working in a non generic way, but I would like to have a more generic, but still completely type safe function.

This is a pared down version of the code as far as I have it right now.

import type { O } from 'ts-toolbelt';

const userFields = {
  accountFirstName: 'firstName',
  accountLastName: 'lastName',
  email: 'email',
  cellphone: 'cellPhone',
  city: 'city',
  state: 'state',
  street: 'street',
  zip: 'zip',
} as const;


const playerFields = {
  playerId: 'sc_id',
  playerFirstName: 'firstName',
  playerLastName: 'lastName',
  sizingJersey: 'jerseySize',
  sizingShorts: 'shortSize',
} as const;

const allFields = {
    ...userFields,
    ...playerFields
}

type EventData = typeof allFields;
type InvertedRecord<R extends { [P in keyof R]: R[P]; }> = Record<keyof O.Invert<R>, string>;

type UserRecord = InvertedRecord<typeof userFields>

function objectKeys<Obj> (obj: Obj): (keyof Obj)[] {
  return Object.keys(obj) as (keyof Obj)[];
};

// take just the user fields from the full data object and put them into a new object 
const extractUser = (data: EventData) => {
  const user: Partial<UserRecord> = {};
  objectKeys(userFields).forEach((field) => {
    user[allFields[field]] = data[field];
  });
  return user as UserRecord;
};

// A more generic extract function that takes the list of fields and the data object and extracts the fields into a new object
function genericExtract <T>(fields: T, data: EventData): T {
  const obj: Partial<T> = {};
  objectKeys(userFields).forEach((field) => {
    obj[allFields[field]] = data[field]; // <--- obj's type doesn't get properly inferred so the assignment is not compatible
  });
  return obj as T;
}

TS Playground link for more context on the issue hat hand..

Second Playground link

1 Answers

First, a simple type to get the values of an object type:

type Values<T> = T[keyof T];

The problem is that T in genericExtract is not constrained. That means allFields[field] is not necessarily a key of T, which is why you get an error.

To solve this, add a constraint to T:

function genericExtract <T extends Record<Values<typeof allFields>, unknown>>(fields: T, data: EventData): T {

Playground

Related