moleculer
Version:
Fast & powerful microservices framework for Node.JS
171 lines (154 loc) • 4.29 kB
JavaScript
/*
* moleculer
* Copyright (c) 2023 MoleculerJS (https://github.com/moleculerjs/moleculer)
* MIT Licensed
*/
"use strict";
const BaseSerializer = require("./base");
//const { isDate } = require("../utils");
const { isDate, isRegExp, isMap, isSet } = require("util").types;
const PREFIX_BIGINT = "[[BI]]";
const PREFIX_MAP = "[[MP]]";
const PREFIX_SET = "[[ST]]";
const PREFIX_DATE = "[[DT]]";
const PREFIX_BUFFER = "[[BF]]";
const PREFIX_REGEXP = "[[RE]]";
const PREFIX_ESCAPED_STRING = "[[ES]]";
/**
* Import types
*
* @typedef {import("../service-broker")} ServiceBroker
* @typedef {import("./json-extended")} JSONExtSerializerClass
* @typedef {import("./json-extended").JSONExtSerializerOptions} JSONExtSerializerOptions
* @typedef {import("./json-extended").JSONExtSerializerOptionsCustomType} JSONExtSerializerOptionsCustomType
*/
/**
* JSON Extended serializer for Moleculer
*
* @implements {JSONExtSerializerClass}
*/
class JSONExtSerializer extends BaseSerializer {
/**
* Creates an instance of JSONExtSerializer.
*
* @param {JSONExtSerializerOptions} opts
* @memberof JSONExtSerializer
*/
constructor(opts) {
super();
/** @type {JSONExtSerializerOptions} */
this.opts = opts || {};
/** @type {boolean} */
this.hasCustomTypes = this.opts?.customs?.length > 0;
}
/**
* JSON stringify replacer.
*
* @param {object} obj
* @param {String} key
* @param {any} value Already converted value
*/
replacer(obj, key, value) {
if (value == null) return value;
// Get the original value
const v = obj[key];
if (typeof v == "bigint") {
return PREFIX_BIGINT + v;
} else if (isDate(v)) {
return PREFIX_DATE + v.valueOf();
} else if (isMap(v)) {
return PREFIX_MAP + this.serialize(Object.fromEntries(v));
} else if (isSet(v)) {
return PREFIX_SET + this.serialize(Array.from(v));
} else if (isRegExp(v)) {
return PREFIX_REGEXP + v.flags + "|" + v.source;
} else if (Buffer.isBuffer(v)) {
return PREFIX_BUFFER + v.toString("base64");
} else if (this.hasCustomTypes) {
for (const custom of this.opts.customs) {
if (custom.check(v, key, obj)) {
return "[[" + custom.prefix + "]]" + custom.serialize(v, key, obj);
}
}
}
// Escape plain strings that start with "[[" to avoid false positives during deserialization
if (typeof value === "string" && value.charAt(0) === "[" && value.charAt(1) === "[") {
return PREFIX_ESCAPED_STRING + value;
}
return value;
}
/**
* JSON.parse reviver.
*
* @param {String} key
* @param {any} value
*/
reviver(key, value) {
if (typeof value === "string" && value.charAt(0) === "[" && value.charAt(1) === "[") {
switch (value.slice(0, 6)) {
case PREFIX_BIGINT:
return BigInt(value.slice(6));
case PREFIX_DATE:
return new Date(Number(value.slice(6)));
case PREFIX_MAP:
return new Map(Object.entries(this.deserialize(value.slice(6))));
case PREFIX_SET:
return new Set(this.deserialize(value.slice(6)));
case PREFIX_BUFFER:
return Buffer.from(value.slice(6), "base64");
case PREFIX_REGEXP: {
const p = value.slice(6).split("|");
const flags = p.shift();
// eslint-disable-next-line security/detect-non-literal-regexp
return new RegExp(p.join("|"), flags);
}
case PREFIX_ESCAPED_STRING:
return value.slice(6);
default: {
if (this.hasCustomTypes) {
for (const custom of this.opts.customs) {
if (value.startsWith("[[" + custom.prefix + "]]")) {
return custom.deserialize(
value.slice(custom.prefix.length + 4),
key
);
}
}
}
}
}
}
return value;
}
/**
* Serializer a JS object to Buffer
*
* @param {Object} obj
* @returns {Buffer}
*
* @memberof Serializer
*/
serialize(obj) {
const self = this;
return Buffer.from(
JSON.stringify(obj, function (key, value) {
return self.replacer.call(self, this, key, value);
})
);
}
/**
* Deserialize Buffer to JS object
*
* @param {any} buf
* @returns {Object}
*
* @memberof Serializer
*/
deserialize(buf) {
const self = this;
return JSON.parse(buf, function (key, value) {
return self.reviver.call(self, key, value);
});
}
}
module.exports = JSONExtSerializer;