Home > Software engineering >  Generic Function with Omit gives could be instantiated with an arbitrary type error
Generic Function with Omit gives could be instantiated with an arbitrary type error

Time:12-07

I'm trying to setup a generic function that maybe removes a field in a generic type.

type MaybeID = {id?: string};

withoutID<K>(model: K & MaybeID): K {
    if (model.id) {
      const { id, ...data } = model;
      // Type of data is Omit<K & MaybeID, "id">
      return data; // Error here
    }
    return model;
  }

I get the error:

Type 'Omit<K & MaybeID, "id">' is not assignable to type 'K'. 'K' could be instantiated with an arbitrary type which could be unrelated to 'Omit<K & MaybeID, "id">'.

If I maybe add the id property but then remove it, do I not get K back? Thanks for your help.

CodePudding user response:

do I not get K back?

No, you don't — and here's why:

Note that I'll use a type assertion on the returned value in your function to override and suppress the compiler diagnostic — in order to illustrate why it's not what you expect:

TS Playground

type MaybeID = { id?: string };

type ModelConstraint = Record<PropertyKey, unknown> & MaybeID;

function withoutID <K>(model: K & MaybeID): K {
  if (model.id) {
    const { id, ...data } = model;
    return data as K;
  }
  return model;
}

const model = {
  id: '',
  strProp: 'hello world',
} satisfies ModelConstraint;

const result = withoutID(model);
    //^? const result: { id: string; strProp: string }

console.log('id' in result, result.id); // true ""

The example above uses your function's implementation code. You can see that, when an empty string is provided, the result of the conditional check if (model.id) is false (see runtime type coercion, if...else, truthy, and falsy).

This results in the effect that the code in the corresponding block is never executed, and the object is returned as-is: including the id property and empty string value.

Instead of using a conditional that coerces the type of the value, you can use the in operator to determine if the property exists in the object (regardless of the type of its value): this better represents the intent of your code.

It then becomes self-evident why Omit<K, 'id'> is suggested: the omission of the id property in the returned value is the type that is returned from the function (because the implementation code ensures that it isn't there — see Omit<Type, Keys> in the TS handbook):

TS Playground

type MaybeID = { id?: string };

type ModelConstraint = Record<PropertyKey, unknown> & MaybeID;

function withoutID <K extends ModelConstraint>(model: K): Omit<K, 'id'> {
  if ('id' in model) {
    const { id, ...data } = model;
    return data;
  }
  return model;
}

const model = {
  id: '',
  strProp: 'hello world',
} satisfies ModelConstraint;

const result = withoutID(model);
    //^? const result: { strProp: string }

console.log('id' in result, (result as any).id); // false undefined

  • Related