Ray Booysen
Ray Booysen

Reputation: 30001

Mapping Keys between Types

Consider the Typescript below. We have two interfaces, both with the same fields that we want to map between. I.e. I want to take an instance of SerialisedModel and convert it to a Model. In the example below if I rename fields on SerialisedModel or Model, the compiler will not complain and we'll have runtime problems.

What I really want to do is declare Model to have the same keys as SerialisedModel, BUT allow different values. E.g. age is a string in SerialisedModel but is a number in Model.

I can't seem to figure out how to do this with any Typescript syntax. We've managed to get it somewhat working with Mapped types, but it always ends up with values being any. Any ideas?

interface SerialisedModel {
  name: string;
  age: string;
  dateOfBirth: string;
}

interface Model {
  name: string;
  age: number;
  dateOfBirth: Date
}

const f: SerialisedModel = // instanciate SerialisedModel;
let b = {} as Model;
Object.keys(f).forEach(key => {
  const mapped = f[key] // then do some mapping to convert values
  b[key] = mapped;
})

Upvotes: 0

Views: 498

Answers (1)

jcalz
jcalz

Reputation: 327744

I'm confused about what you're really trying to achieve here. Where are you seeing "it always ends up with values being any"? I don't see that in my or your code.

If all you are trying to do is have the compiler yell at you if two types have different keys, you can do something like this:

type RequireSameKeys<T extends Record<keyof U, any>, U extends Record<keyof T, any>> = true

type VerifyModelTypes = RequireSameKeys<SerialisedModel, Model>; // okay?

If VerifyModelTypes has no error, then SerialisedModel and Model have exactly the same keys. Let's see:

interface Model {
  name: string;
  age: number;
  dateOfBirth: Date
}

interface SerialisedModel {
  name: string;
  age: string;
  dateOfBrith: string; // Hppay Brithday To Yuo
}

type VerifyModelTypes = RequireSameKeys<SerialisedModel, Model>; // error!
//  Property 'dateOfBirth' is missing in type 'SerialisedModel'.

Whoops, let's fix that:

interface SerialisedModel {
  name: string;
  age: string;
  dateOfBirth: string; // fixed
}

type VerifyModelTypes = RequireSameKeys<SerialisedModel, Model>; // okay

It's happy now.


But since SerialisedModel seems to be a straightforward mapping of Model, you should indeed be able to use mapped types to define it:

type _SM = { [K in keyof Model]: string };
interface SerialisedModel extends _SM {};  // same as before

and then make a generic processor function to convert between them:

function makeProcessor<T extends Record<keyof T & keyof U, any>, 
  U extends Record<keyof T & keyof U, any>>(
  processors: { [K in keyof T & keyof U]: (x: T[K]) => U[K] }
): (t: T) => U {
  return (t: T) => {
    const ret = {} as U;
    // note Object.keys() returns string[], not (keyof T)[], so we assert
    Object.keys(processors).forEach((k: keyof T) => {
      ret[k] = processors[k](t[k]);
    });
    return ret;
  }
}

Here's how you'd use it:

const deserialise = makeProcessor<SerialisedModel, Model>({
  age: a => +a,
  name: n => n,
  dateOfBirth: d => new Date(+d)
});

const serialise = makeProcessor<Model, SerialisedModel>({
  age: a => "" + a,
  name: n => n,
  dateOfBirth: d => "" + d.getTime()
})

const model: Model = {
  name: "Gabriel Jarret",
  age: 48,
  dateOfBirth: new Date(0)
}

console.log(model.name); // "Gabriel Jarret"
console.log(model.age); // 48 
console.log(model.dateOfBirth); // Date 1970-01-01T00:00:00.000Z

const serialisedModel = serialise(model);

console.log(serialisedModel.name); // "Gabriel Jarret"
console.log(serialisedModel.age); // "48"
console.log(serialisedModel.dateOfBirth); // "0"

const deserialisedModel = deserialise(serialisedModel);

console.log(deserialisedModel.name === model.name); // true
console.log(deserialisedModel.age === model.age); // true
console.log(deserialisedModel.dateOfBirth.getTime() === 
  model.dateOfBirth.getTime()); // true

That all looks good to me. Does that help? If you are continuing to see a problem, please include specific code where errors are occurring. Otherwise I'm just making guesses about what you need. Anyway, good luck!

Upvotes: 1

Related Questions