Comments (6)
Thank you for your response!
Indeed, I followed your feedback and I have just created a separate library called Jsonthis that can be very easily connected with Sequelize!
Here's a quick example on how to use it:
function maskEmail(value: string): string {
return value.replace(/(?<=.).(?=[^@]*?.@)/g, "*");
}
class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
@Attribute(DataTypes.INTEGER)
@PrimaryKey
declare id: number;
@Attribute(DataTypes.STRING)
@NotNull
@JsonField({serializer: maskEmail})
declare email: string;
@Attribute(DataTypes.STRING)
@NotNull
@JsonField(false)
declare password: string;
}
const jsonthis = new Jsonthis({sequelize}); // Here's where all the magic happens!
const user = await User.create({
id: 1,
email: "[email protected]",
password: "s3cret"
});
console.log(user.toJSON());
// {
// id: 1,
// email: 'j******[email protected]',
// updatedAt: 2024-04-13T18:00:20.909Z,
// createdAt: 2024-04-13T18:00:20.909Z
// }
from sequelize.
I have already implemented a Proof-of-concept that can be integrated with a PR, however I'm very new to the project and I don't know where to start and the best place to inject this logic.
In the meantime, this is the additional module I created in my project (a test script can be found at the bottom as well):
import {InferAttributes, InferCreationAttributes, Model} from "@sequelize/core";
import {camelCase, snakeCase} from "case-anything";
export type ToJsonOptions = {
keepNulls?: boolean; // Whether to keep null values or not (default is false).
case?: "camel" | "snake"; // The case to use for field names, default is to keep field name as is.
}
export type JsonFieldOptions = {
visible?: boolean; // Whether the column is visible or not (default is true).
serializer?: (value: any) => any; // The custom serializer function for the column.
}
class JsonSchema {
hiddenFields: Set<string> = new Set();
serializers: Map<string, (value: any) => any> = new Map();
static getOrCreate(target: Function): JsonSchema {
return target["__json_schema"] = target["__json_schema"] || new JsonSchema();
}
static get(target: unknown): JsonSchema | undefined {
return (target instanceof Function) ? target["__json_schema"] : undefined;
}
}
export const JsonField = function (visible?: boolean | JsonFieldOptions, options?: JsonFieldOptions): Function {
return function JsonField(target: Object, propertyName: PropertyKey): void {
options = options || {};
if (typeof visible === "object") {
options = visible;
} else if (typeof visible === "boolean") {
options.visible = visible;
}
const key = propertyName.toString();
const schema = JsonSchema.getOrCreate(target.constructor);
if (options.visible === false) schema.hiddenFields.add(key);
if (options.serializer) schema.serializers.set(key, options.serializer);
}
}
export default abstract class JsonModel<M extends Model> extends Model<InferAttributes<M>, InferCreationAttributes<M>> {
toJSON(options?: ToJsonOptions): object {
return toJSON(this.get(), this.constructor, options);
}
}
function isNull(value: any): boolean {
return value === null || value === undefined;
}
function toJSON(modelData: object, modelClass: Function, options?: ToJsonOptions): { [key: string]: any } {
const schema = JsonSchema.get(modelClass);
const json: { [key: string]: any } = {};
for (const name in modelData) {
if (schema?.hiddenFields.has(name)) continue;
let value: any = modelData[name];
let key = name;
switch (options?.case) {
case "camel":
key = camelCase(name);
break;
case "snake":
key = snakeCase(name);
break;
}
if (isNull(value)) {
if (!options?.keepNulls) continue;
json[key] = null;
} else {
const serializer = schema?.serializers.get(name);
if (serializer) {
json[key] = serializer(value);
} else if (Array.isArray(value)) {
json[key] = value.map(obj => toJSON(obj, obj?.constructor, options));
} else if (value instanceof Model) {
json[key] = toJSON(value.get(), value.constructor, options);
} else {
json[key] = value;
}
}
}
return json;
}
Here's how you can use it currently:
class User extends JsonModel<User> {
@Attribute(DataTypes.STRING)
@JsonField({serializer: (value: string) => value.substring(0, 9) + "********"})
declare email: string;
@Attribute(DataTypes.STRING)
@JsonField(false)
declare password: string;
}
[...]
const user = await User.create({
email: "[email protected]",
password: "top-secret-password"
});
console.log(user.get({plain: true}));
// {
// id: 1,
// email: '[email protected]',
// password: 'top-secret-password',
// updatedAt: 2024-04-12T15:38:49.740Z,
// createdAt: 2024-04-12T15:38:49.740Z
// }
console.log(user.toJSON());
// {
// id: 1,
// email: 'john.doe@********',
// updatedAt: 2024-04-12T15:38:49.740Z,
// createdAt: 2024-04-12T15:38:49.740Z
// }
from sequelize.
Hi! Thank you for the proof of concept
Unfortunately I still don't think JSON serialization of classes is an ORM concern, and as such does not belong in Sequelize.
This is the sort of feature that should be implemented as its own generic library that works for any class.
If it's necessary to override toJSON
, users can easily create a base class, or a plugin system, similar to what is being considered for #15497, could be implemented
from sequelize.
Hi @ephys !
Sure, i see your point about not having to include a JSON serialization logic to Sequelize too..
However.. 😄 Still a Model
class has the toJSON()
method, which must indicate that the JSON serialization feature is very interesting to the common user of this library, and for sure it was it for me!
While I was going through the documentation, it felt to me just like the ability to customize serialization was there somewhere, yet just out of reach, so close yet needing custom implementation to make it work properly.
With that said, I totally understand if you don't want to integrate such feature, and I find the suggestion to make it a plugin very interesting. Do you have any practical hints for where to "inject" my logic without requiring the user to extend a custom class?
I think it should be as simple as just adding the decorators and that's it! The serialization magically works, like this:
class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
@Attribute(DataTypes.STRING)
@JsonField({serializer: (value: string) => value.substring(0, 9) + "********"})
declare email: string;
@Attribute(DataTypes.STRING)
@JsonField(false)
declare password: string;
}
However in order to do this I need to be able to modify the Model
class to intercept the toJSON()
method, and add an option parameter to it (ToJsonOptions
in my example implementation).
Do you see an easy way to do it I'm missing? Is there a plugin architecture in Sequelize that allows to customize its internals from outside?
Thanks!
from sequelize.
I agree that serialization is important, what we do not want is to have a sequelize-specific solution that doesn't work for anything else. There are also multiple possible approaches: toJSON is simple but fully synchronous. Other approaches that don't use toJSON
could benefit from being asynchronous. In a way, graphql's resolvers are also a way to serialize
If there are libraries that do this, we're happy to link to them from our documentation
There is no plugin system yet, but you could provide a install(sequelize)
function that your users need to call once after having initialized Sequelize
In that function, you can hook the afterDefine
(or beforeDefine
) event in which you replace the toJSON method of the model you reveive
from sequelize.
Hello!
I'm closing this issue as I solved the original problem with the Jsonthis! library!
from sequelize.
Related Issues (20)
- Can't make table queries. TypeError: _.includes is not a function HOT 1
- DATE parse function crashes in SQLite HOT 1
- PostgreSQL. Error in getQueryInterface().describeTable() with column comments and schemas
- Unable to find dialect at HOT 2
- Cyclic sequelize sync doesn't handle several cases in the non-cyclic version HOT 1
- Introducing @EncryptedAttribute() decorator
- Add `Model.exists()` to allow efficient checking of at least one matching row
- Filter As on Properties of Associated Bs But Still Return all Bs Associated to the Resulting A's
- Feature Request: support 'schema' for MySQL HOT 1
- Merge Failure When Using Raw Where Query With Scope HOT 2
- Recieving sqlite related errors, but dialect used is postgres
- An In-Class @Scope Decorator HOT 1
- Add method for accessing #transactionCls Sequelize v7
- 使用webpack5 打包 后报错 TypeError: i is not a function HOT 2
- `unique: true` does not generate same index name and constraint in model and migrations
- Please include src/ in public package if you're going to publish source maps
- Node.js start fails on Linux platform due to invalid ELF header error HOT 1
- Weird Failure When Using this.reload(); "Cannot use offset or limit without a model or order being set" HOT 2
- Raw Queries with multiple Bind Parameters and in wrong order not working properly - (tested on Oracle)
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from sequelize.