protoobject
Version:
A universal class for creating any JSON objects and simple manipulations with them.
264 lines (263 loc) • 9.25 kB
JavaScript
/**
* A universal class for creating any JSON objects and simple manipulations with them.
*/
export class ProtoObject {
/**
*
* @param data - ProtoObject or its heir properties
* @returns - the ProtoObject or its heir
*/
constructor(data) {
return ProtoObject.deepAssign(this, data ?? {});
}
/**
* Get all properties of an object and its prototypes
*
* @param data - any non-null object, such as an instance of the ProtoObject class or its heir
* @returns - an array of the properties information
*/
static getProperties(data) {
const props = [];
// eslint-disable-next-line guard-for-in
for (const key in data) {
const prop = Object.getOwnPropertyDescriptor(data, key);
if (prop)
props.push({ key, value: prop?.value, descriptor: prop });
}
return props;
}
/**
* Get all enumerable properties of an object and its prototypes
*
* @param data - any non-null object, such as an instance of the ProtoObject class or its heir
* @param options - options for the returned array of properties
* @param options.onlyWritable - return only writable properties
* @returns - an array of the properties information
*/
static getEnumerableProperties(data, options = { onlyWritable: false }) {
const classNode = this;
return classNode
.getProperties(data)
.filter((prop) => !prop.descriptor.get &&
!prop.descriptor.set &&
prop.descriptor.enumerable)
.filter((prop) => options?.onlyWritable ? prop.descriptor.writable : true);
}
/**
* A recursive function for assigning properties to an object or returning a property
* if it is not interchangeable
*
* WARN: The first transferred object will be changed.
*
* @param obj - an object such as a ProtoObject or its heir or an object property
* @param data - an object such as a ProtoObject or its heir or an object property
* @returns - an object such as a ProtoObject or its heir or an object property
*/
static recursiveAssign(obj, data) {
const classNode = this;
if (typeof obj === "undefined" ||
obj === null ||
(typeof data !== "undefined" && typeof obj !== typeof data))
return data;
if (data === null)
return data;
switch (typeof data) {
case "undefined":
return obj;
case "bigint":
case "boolean":
case "function":
case "number":
case "string":
case "symbol":
return data;
case "object": {
if (Array.isArray(data)) {
return data;
}
if (data instanceof ProtoObject &&
data?.constructor?.name !== obj?.constructor?.name) {
return data.copy();
}
if (!(data instanceof ProtoObject) &&
typeof data.constructor === "function" &&
data.constructor?.name !== "Object") {
return data;
}
for (const prop of classNode.getEnumerableProperties(data)) {
if (typeof prop.key !== "string")
continue;
const origProp = Object.getOwnPropertyDescriptor(obj, prop.key);
if (!origProp ||
(!origProp.get &&
!origProp.set &&
origProp.enumerable &&
origProp.writable)) {
obj[prop.key] = classNode.recursiveAssign(obj[prop.key], data[prop.key]);
}
else {
continue;
}
}
return obj;
}
}
return obj;
}
/**
* Deep assign data to an instance of the ProtoObject class or its heir
*
* @param obj - a ProtoObject class or its heir
* @param data - a ProtoObject class or its heir or any other object
* @returns - a assigned ProtoObject class or its heir
*/
static deepAssign(obj, data) {
const classNode = this;
return classNode.recursiveAssign(obj, data ?? {});
}
/**
* The converter of values into simple types
*
* @param data - a value to convert to a simple type
* @returns - a simple type
*/
static valueToJSON(data) {
switch (typeof data) {
case "boolean":
case "number":
case "string":
return data;
default:
return undefined;
}
}
/**
* The converter of simple types into values
*
* @param data - a simple type to convert to a value
* @returns - a value
*/
static valueFromJSON(data) {
switch (typeof data) {
case "boolean":
case "number":
case "string":
return data;
default:
return undefined;
}
}
/**
* A method for converting a simple json to ProtoObject class or its heir
*
* @param data - a simple json data
* @returns - a ProtoObject class or its heir
*/
static fromJSON(data) {
const classNode = this;
const json = {};
// eslint-disable-next-line guard-for-in
for (const key in data) {
const value = classNode.valueFromJSON(data[key]);
if (value)
json[key] = value;
}
return new classNode(json);
}
/**
* A method for converting a ProtoObject class or its heir to simple json
*
* @returns - a simple json
*/
toJSON() {
const classNode = this
.constructor;
const json = {};
const props = ProtoObject.getEnumerableProperties(this);
for (const prop of props) {
const value = classNode.valueToJSON(prop.value);
if (value)
json[prop.key] = value;
}
return json;
}
/**
* A method for converting a ProtoObject class or its heir to a string
*
* @returns - string
*/
toString() {
return JSON.stringify(this.toJSON());
}
/**
* Copying a ProtoObject class or its heirs
*
* @returns - a deep copy of the ProtoObject object or its heir
*/
copy() {
const classNode = this.constructor;
return classNode.fromJSON(this.toJSON());
}
/**
* Deep assign data to an instance of the ProtoObject class or its heir
*
* @param data - a ProtoObject class or its heir or any other object
* @returns - a assigned ProtoObject class or its heir
*/
assign(data) {
const classNode = this.constructor;
return classNode.deepAssign(this, data);
}
/**
* Factory for creating a data transformer for the ProtoObject class or its heir
*
* @param param0 - data validators
* @param param0.validatorTo - data validator when converting to a simple JSON object
* @param param0.validatorFrom - data validator when converting to a class
* @returns data transformer for the ProtoObject class or its heir
*/
static recordTransformer({ validatorTo, validatorFrom, }) {
const classNode = this;
return {
to(obj) {
if (!obj || (typeof validatorTo === "function" && !validatorTo(obj)))
return undefined;
return obj.toJSON();
},
from(json) {
if (!json ||
(typeof validatorFrom === "function" && !validatorFrom(json)))
return undefined;
return classNode.fromJSON(json);
},
};
}
/**
* Factory for creating a data transformer for the array of ProtoObject classes or its heirs
*
* @param param0 - data validators
* @param param0.validatorTo - data validator when converting to a simple JSON object
* @param param0.validatorFrom - data validator when converting to a class
* @returns data transformer for the array of the ProtoObject classes or its heirs
*/
static collectionTransformer({ validatorTo, validatorFrom, }) {
const classNode = this;
return {
to(objArr) {
if (!Array.isArray(objArr))
return undefined;
return objArr
.filter((obj) => !!obj && (typeof validatorTo !== "function" || !!validatorTo(obj)))
.map((obj) => obj.toJSON());
},
from(jsonArr) {
if (!Array.isArray(jsonArr))
return undefined;
return jsonArr
.filter((json) => !!json &&
(typeof validatorFrom !== "function" || !!validatorFrom(json)))
.map((json) => classNode.fromJSON(json));
},
};
}
}