Armen Babakanian
Armen Babakanian

Reputation: 2345

typescript extending types vs interfaces

This might a be relatively noob question, I have an interface

interface Employee {
   name: string
}

and I would like to have an extended version of this after it being saved into the DB:

interface EmployeeDb {
   id: string,
   name: string
}

I would like to differentiate it when handling checks so after saving data in my storage, the type checker won't complain about not having id value. Meaning I want to avoid using this:

interface Employee {
   id?: string,
   name: string
}

so I don't have to check for id everywhere.

So I am trying to do it this way:

type Employee = {
   name: string
}

type IDatabaseObject<T> = {
  id: IDatabaseObjectId;
  [P in keyof T]: T[P];
};

type EmployeeDb = IDatabaseObject<Employee>

which the IDE gives an error with the top syntax

A computed property name must be of type 'string', 'number', 'symbol', or 'any'.ts(2464)

so I tried to use interface and extend it

interface IDatabaseObject { 
   id: string
}

interface EmployeeDb extends Employee, IDatabaseObject {}

but in the backend code when I try to use this setup I get an error from vscode eslint again. I have a small code here that adds the data to localstorage, generates a id and returns the data. see code:

class DbAsyncStorageTemplate<
    InputDataType,
    OutputDataType extends IDatabaseObject
> {

    async addEntry(object: InputDataType): Promise<OutputDataType> {

        const id: string = generateUuid()
        const dbObject = { id, ...object }
        dbObject.id = id

        // add the item to AsyncStorage directly
        await AsyncStorage.setItem(id, JSON.stringify(object))

        // ERROR HERE: return the new object
        return dbObject as OutputDataType
    } 
    }
}

but I get an error from the IDE (eslint) for the last line

Conversion of type '{ id: string; } & InputDataType' to type 'OutputDataType' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. '{ id: string; } & InputDataType' is assignable to the constraint of type 'OutputDataType', but 'OutputDataType' could be instantiated with a different subtype of constraint 'any'.

any recommendation on how to do this properly?

Upvotes: 8

Views: 16025

Answers (2)

MikeIsMike
MikeIsMike

Reputation: 31

I think you are looking for this: https://www.typescriptlang.org/docs/handbook/advanced-types.html#:~:text=an%20intersection%20type%3A-,//%20Use%20this%3A,%7D,-Try

You are trying to create a new type (IDatabaseObject) based on an old type (Employee, for instance; or T, in the generic case). This is a Mapped Type.

In your code,

[P in keyof T]: T[P]

returns your old type rather than members of that old type. So you need to close the curly brackets and intersect it with any other new members you want to add.

i.e. do the following for IDatabseObject

type IDatabaseObject<T> = {
    id: number;
} & {
    [P in keyof T]: T[P];
};

Upvotes: 3

Antonio Gargaro
Antonio Gargaro

Reputation: 106

I believe you're looking for intersections of types.

type Employee = {
   name: string
}

type EmployeeDb = {
  id: string;
} & Employee;

You could also define the raw DB interface and use Pick or Omit utilities as needed.

Pick Utility

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, "title" | "completed">;

const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};

Upvotes: 7

Related Questions