Evanss
Evanss

Reputation: 23142

Type safe way of finding differences between 2 objects in TypeScript?

I have 2 objects of the same type. I need to find all of the values from one object that either don't exist in the other, or have a different value.

This code works but has TypeScript errors:

export type Thing = {
  name?: string;
  age?: number;
  location?: {
    x: number,
    y: number
  }
};

const things: Thing = { name: "Jim", age: 32, location: {x:1, y:2} };
const things2: Thing = { name: "Jim" };

const things3: Thing = {};

Object.entries(things).forEach(([key, value]) => {
  if (things[key] !== things2[key]) {
    things3[key] = value;
  }
});

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Thing'. No index signature with a parameter of type 'string' was found on type 'Thing'.ts(7053)

Ive reduced the number of errors by using the ts-extras library:

objectEntries(things).forEach(([key, value]) => {
  if (things[key] !== things2[key]) {
    things3[key] = value;
  }
});

But I still get this error by things3[key] = value;:

(parameter) key: keyof Thing Type 'string | number | { x: number; y: number; } | undefined' is not assignable to type 'undefined'. Type 'string' is not assignable to type 'undefined'.ts(2322)

Upvotes: 3

Views: 730

Answers (2)

fourjuaneight
fourjuaneight

Reputation: 147

You could make use of Partial Types:

type Thing = {
  name?: string;
  age?: number;
  location?: {
    x: number;
    y: number;
  };
};

const things: Thing = { name: 'Jim', age: 32, location: { x: 1, y: 2 } };
const things2: Thing = { name: 'Jim' };

const things3 = {} as Partial<Record<keyof Thing, any>>;

Object.entries(things).forEach(([key, value]) => {
  const thingKey = key as keyof Thing;

  if (things[thingKey] !== things2[thingKey]) {
    things3[thingKey] = value as Thing[keyof Thing];
  }
});

Partial<Thing> makes all properties optional while keeping type safety. You can then cast key as keyof Thing to ensure validity. As well as value as Thing[keyof Thing] so TS knows it exists in Thing.

Your result should be this:

{ age: 32, location: { x: 1, y: 2 } }

Upvotes: 0

twharmon
twharmon

Reputation: 4282

Add an intersection to explicitly require keys of Thing to be string:

type Thing = {
  name?: string;
  age?: number;
  location?: {
    x: number,
    y: number
  }
} & { [key: string]: any };

Upvotes: -1

Related Questions