Sequelize-typescript: Type toJSON's return type

Created on 10 May 2019  路  6Comments  路  Source: RobinBuschmann/sequelize-typescript

Versions

  • sequelize: ^4.37.6
  • sequelize-typescript: ^0.6.10
  • typescript^3.4.3

I'm submitting a ...
[ ] bug report
[x] feature request

Content
Model.toJSON() is set to return any.
Would there be a way to make it return an interface consisting of the specified raw attributes?
I noticed the typings of sequelize make it return TAttributes. Can we do some decorator magic there?

All 6 comments

Hey @Telokis, thanks for bringing this up. Since [email protected] won't be responsible anymore for most of the typings (since version 5 sequelize provides their own typings), those changes won't be implemented anymore. So this should be fixed in sequelize directly.

But for now you could implement your own BaseModel where you could override toJSON and use a mapped type like so:

type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];

class BaseModel<T extends Model<T>> extends Model<T> {
  toJSON(): NonFunctionPropertyNames<this>;
}

Hope this helps!

Thanks for the suggestion. I ended up adding a custom typings that globally overrides your definition of toJSON() for every model automatically. It works like a charm!

I don't have the code with me but it looked something like this:

// File typings/extendedSequelizeTypescript.d.ts
import * as ST from "sequelize-typescript";

type MagicToExtractRealType<T extends Model<T>> = ...;

declare module "sequelize-typescript" {
    class Model<T extends Model<T>> {
       toJSON() : MagicToExtractRealType<T>;
    }
}

FYI, since there are a lot of attributes which are not functions, I made a "static" utility type to remove all sequelize & sequelize-typescript attributes from the model:

export type ModelAttributes<T extends Model<T>> = Omit<
  T,
  | 'isInitialized'
  | 'init'
  | '$add'
  | '$set'
  | '$get'
  | '$count'
  | '$create'
  | '$has'
  | '$remove'
  | 'addHook'
  | 'changed'
  | 'decrement'
  | 'deletedAt'
  | 'destroy'
  | 'equals'
  | 'equalsOneOf'
  | 'get'
  | 'getDataValue'
  | 'hasHook'
  | 'hasHooks'
  | 'id'
  | 'increment'
  | 'isNewRecord'
  | 'isSoftDeleted'
  | 'previous'
  | 'reload'
  | 'removeHook'
  | 'restore'
  | 'save'
  | 'sequelize'
  | 'set'
  | 'setAttributes'
  | 'setDataValue'
  | 'toJSON'
  | 'update'
  | 'validate'
  | 'version'
  | 'where'
>;

And use a BasicModel with its own toJSON() method:

  toJSON(): ModelData<this> {
    return <ModelData<this>>super.toJSON();
  }

Thanks for the suggestion. I ended up adding a custom typings that globally overrides your definition of toJSON() for every model automatically. It works like a charm!

I don't have the code with me but it looked something like this:

// File typings/extendedSequelizeTypescript.d.ts
import * as ST from "sequelize-typescript";

type MagicToExtractRealType<T extends Model<T>> = ...;

declare module "sequelize-typescript" {
    class Model<T extends Model<T>> {
       toJSON() : MagicToExtractRealType<T>;
    }
}

@Telokis Hey man do you have the full code? would be really helpful :)

FYI, since there are a lot of attributes which are not functions, I made a "static" utility type to remove all sequelize & sequelize-typescript attributes from the model:

export type ModelAttributes<T extends Model<T>> = Omit<
  T,
  | 'isInitialized'
  | 'init'
  | '$add'
  | '$set'
  | '$get'
  | '$count'
  | '$create'
  | '$has'
  | '$remove'
  | 'addHook'
  | 'changed'
  | 'decrement'
  | 'deletedAt'
  | 'destroy'
  | 'equals'
  | 'equalsOneOf'
  | 'get'
  | 'getDataValue'
  | 'hasHook'
  | 'hasHooks'
  | 'id'
  | 'increment'
  | 'isNewRecord'
  | 'isSoftDeleted'
  | 'previous'
  | 'reload'
  | 'removeHook'
  | 'restore'
  | 'save'
  | 'sequelize'
  | 'set'
  | 'setAttributes'
  | 'setDataValue'
  | 'toJSON'
  | 'update'
  | 'validate'
  | 'version'
  | 'where'
>;

And use a BasicModel with its own toJSON() method:

  toJSON(): ModelData<this> {
    return <ModelData<this>>super.toJSON();
  }

@francois-gsk do you have the full code with files?

Sure @talkl, it's someting like:

/**
 * Abstract Model class which adds fields common to
 * all entities, such as IDs and creation/update date.
 */
export default abstract class Node<T extends Node<T>> extends Model<T> {
  @Column({ primaryKey: true, autoIncrement: true })
  internalId: number;

  @Column({
    allowNull: false,
    defaultValue: () => nanoid(),
    type: DataType.TEXT,
    unique: true,
  })
  externalId: string;

  @CreatedAt
  createdAt: Date;

  @UpdatedAt
  updatedAt: Date;

  /**
   * Override toJSON method to provide a better typing
   */
  toJSON(): ModelData<this> {
    return <ModelData<this>>super.toJSON();
  }
}

export type ModelStatic<T extends Model<T>> = typeof Node &
  (new (values?: Record<string, unknown>, options?: BuildOptions) => T);

export type ModelData<T extends Model<T>> = Omit<
  T,
  | 'isInitialized'
  | 'init'
  | '$add'
  | '$set'
  | '$get'
  | '$count'
  | '$create'
  | '$has'
  | '$remove'
  | 'addHook'
  | 'changed'
  | 'decrement'
  | 'deletedAt'
  | 'destroy'
  | 'equals'
  | 'equalsOneOf'
  | 'get'
  | 'getDataValue'
  | 'hasHook'
  | 'hasHooks'
  | 'id'
  | 'increment'
  | 'isNewRecord'
  | 'isSoftDeleted'
  | 'previous'
  | 'reload'
  | 'removeHook'
  | 'restore'
  | 'save'
  | 'sequelize'
  | 'set'
  | 'setAttributes'
  | 'setDataValue'
  | 'toJSON'
  | 'update'
  | 'validate'
  | 'version'
  | 'where'
>;

Then you create your model like this:

@Table({ tableName: 'cats' })
export default class Cat extends Node<Cat> {
  @Column({ type: DataType.STRING, allowNull: false })
  breed: string;

  @Column({ type: DataType.STRING, allowNull: false })
  name: string;
}

// I export the data version for each model but not mandatory
export type CatData = ModelData<Cat>;

Now if you call toJSON() on an instance like this cat.toJSON() you will receive a clean type.

Be aware that the ModelData generic must be updated if sequelize or sequelize-typescript change its Model signature.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nandox5 picture nandox5  路  3Comments

KAMAELUA picture KAMAELUA  路  4Comments

oscarcalvo picture oscarcalvo  路  3Comments

bschveitzer picture bschveitzer  路  5Comments

mikew picture mikew  路  4Comments