Typescript — make sure object prop key is same as prop value

Viewed 303

I need to type the following object structure:

const entities = {
  abc: {
    id: 'abc'
  },
  def: {
    id: 'def'
  }
}

Each object prop key needs to match its corresponding id.


I tried doing this:

interface EntitiesMap<E extends Entity> {
  [K in E['id']]: E;
}

interface Entity {
  id: string;
}

But this does not ensure that the prop key and id value match. For example:

const entities = {
  ghi: {
    id: 'aaaaa' // should throw an error as ghi doesn't match aaaaa
  }
}

Any ideas how I could make this work?

4 Answers

The way I know is to define a type. But you need always to care about keys.

type Entity<T extends keyof any> = {
  [K in T]: {
    id: K;
  };
};

const entities: Entity<'abc' | 'def' | 'def2' | 'def3'> = {
  abc: {
    id: 'abc'
  },
  def: {
    id: 'def'
  },
  def2: {
    id: 'abc' // <- fails
  },
  def3: {
    id: 'def4' // <- fails
  },
}

For keys you can have a helper function, not sure if it's acceptable

const validateEntities = <K extends keyof any>(value: Entity<K>): Entity<K> => {
  return value;
};

const entities = validateEntities({
  abc: {
    id: 'abc'
  },
  def: {
    id: 'def'
  },
  def2: {
    id: 'abc' // <- fails
  },
  def3: {
    id: 'def4' // <- fails
  },
});

That's because you defined id as string and 'aaaaa' is a string. you can use something like this for id which is one step more specific than string:

type IdType = keyof typeof entities // "abc" | "def"

// and use it as id type

interface Entity {
  id: IdType
}

It's maybe not the exact answer you're looking for , but it's a good start. hope it would be helpfull

If you're OK to use identity function to create the entities, you can use generics to infer the root keys and mapped type to verify the values of the inner objects:

const createEntities = <TKeys extends string>(entities: { [K in TKeys]: { id: K } }) => entities;

// OK
const entities = createEntities({
  abc: {
    id: 'abc'
  },
  def: {
    id: 'def'
  }
})

// Error: type '"aaaaa"' is not assignable to type '"ghi"'
createEntities({
  ghi: {
    id: 'aaaaa'
  }
})

Playground

I don't think it's possible with a simple type signature, but if you declare entities with as const, it is possible to do this entirely at the type level, without any JavaScript functions:

type ValidEntities<E> = {[K in keyof E]: {id: K}}
type IsValid<E extends ValidEntities<E>> = true // Can be any type

// OK
const entities = {
  abc: {
    fine: 3,
    id: 'abc'
  },
  def: {
    id: 'def'
  }
} as const

type Validate = IsValid<typeof entities>

If entities is invalid, you will get a type error at IsValid<typeof entities>.

TypeScript playground

Related