Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

152
Visualizações
Infer correct generic type in Typescript

I have the following method which serves to extend an existing key-value map — e.g. a result of a db query — with other object(s) of the same type.

export function extendWith<
  T extends { id: string | number },
  O =
    | (T["id"] extends string | number ? Record<T["id"], T> : never)
    | (T["id"] extends string | number ? Partial<Record<T["id"], T>> : never)
>(obj: O, vals: T | T[]): O {
  const extended = { ...obj };
  const values = Array.isArray(vals) ? vals : [vals];

  for (const val of values) {
    if (val !== undefined) {
      const prop = val["id"];

      if (
        typeof prop === "string" ||
        typeof prop === "number" ||
        typeof prop === "symbol"
      ) {
        (extended as any)[prop] =
          prop in obj ? { ...(obj as any)[prop], ...val } : val;
      }
    }
  }

  return extended;
}

When I call it as follows, all is well, i.e. I get a typescript error on the last line correctly stating that the type of name of the object I'm passing in is wrong.

interface Photo {
  id: number;
  name: string;
}
const photos: { [key: number]: Photo } = {
  1: { id: 1, name: "photo-1" },
  2: { id: 2, name: "photo-2" }
};
const extendedPhotos = extendWith<Photo>(photos, { id: 4, name: 3 });

Now, when I remove the explicit parameter <Photo> in the extendWith call on that last line, the typescript error disappears. I assume this has to do with typescript generic inference.

Does anyone know a way to achieve the inference being correct? Any tips to send me on the right path are much appreciated!

A sandbox to play with available here.

about 4 years ago · Juan Pablo Isaza
2 Respostas
Responde à pergunta

0

Firstly, It looks like your first overload is unnecessary, as you're saying that when the id is a string or a number, the returned object is either Partial<O>, or a full O. O will always be valid when mapped to a Partial<O> type, so you can just say its type is Partial<O>.

With regards to the inference, if you let TypeScript infer the type it will use your input to infer the output of the function. What it seems you are asking for is that the function's second argument MUST be of type Photo, which isn't inference, and can't be inferred unless you pass it a variable that is of type Photo already. For TS to infer, you'd need to replace your last line with something like:

const myPhoto: Photo { id: 4, name: 'my-photo' };
const extendedPhotos = extendWith<Photo>(photos, myPhoto);

So that TypeScript can use the information from the input values to infer the output value.

about 4 years ago · Juan Pablo Isaza Relatório

0

This can be done (though this example will need extending):

type Index = string | number;
type ObjectWithIdIndex = { id: Index };

function extendWith<O extends Record<Index, ObjectWithIdIndex>>(obj: O, v: O[keyof O]): O {
    (obj as Record<Index, ObjectWithIdIndex>)[v.id] = v;

    return obj;
}

The types of these parameters can be reduced to:

  • requiring that obj is an object with values that have id properties. We can constrain this by only accepting types that extend Record<Index, ObjectWithIdIndex>. So the following should give a type error:
extendWith(null, { id: 3, name: "photo-4" }); 
extendWith({ abc: 1 }, { id: 3, name: "photo-4" });
  • requiring that v is of the same type as of the values of obj. We can constrain this with O[keyof O]. As keyOf is a union of the properties of obj, O[keyof O] is a union of the values of these properties. The following should also give a type error:
interface Photo { id: number; name: string; }

const photos: Record<number, Photo> = {
  1: { id: 1, name: "photo-1" },
  2: { id: 2, name: "photo-2" }
};

extendWith(photos, { id: 4, name: "photo-4", abc: 2 });
extendWith(photos, { id: 4, name: 1 });
extendWith(photos, { name: "abc" });
extendWith(photos, null);

When calling extendWith, typescript will infer a more specific type for obj than Record<Index, ObjectWithIdIndex>. This has the consequence that:

  • We can use this type to infer the types of obj's values and then constrain v.
  • We can no longer assign new properties to obj as typescript doesn't know if obj is still an extensible Record type (eg. { a: 1 } is a subtype of Record<string, number>, but new properties can't be assigned to it). We can however cast obj back to its more generic type and then extend it.
about 4 years ago · Juan Pablo Isaza Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda