UNPKG

ts-std-lib

Version:
231 lines 9.84 kB
"use strict"; var _a; Object.defineProperty(exports, "__esModule", { value: true }); exports.jsonParameter = exports.jsonProperty = exports.DefaultSerializer = exports.JsonSerializable = void 0; require("reflect-metadata"); const IJsonSerializer_1 = require("./IJsonSerializer"); const Type_1 = require("../../Type"); const Optional_1 = require("../../Optional"); const IsJsonSerializable_1 = require("./IsJsonSerializable"); const IsJsonSerializer_1 = require("./IsJsonSerializer"); const DefaultInspector_1 = require("../../DefaultInspector"); /** * A base type to use for objects that can then be serialized to/from json using decorators */ class JsonSerializable { static fromJSON(json) { const constructor = this; //const parameterNames = getConstructorParameterNames(constructor); //? const parameterMetadatas = getJsonParameterMetadata(constructor); const parsedJson = parseJson(json); if (Type_1.Type.isArray(parsedJson)) { throw new Error('Array json is not yet supported.'); } const constructorArgs = !parameterMetadatas ? [] : Array.from({ length: constructor.length }) .map((_, index) => { const parameterMetadata = parameterMetadatas.find(p => p.index === index); if (!parameterMetadata) { return undefined; } // TODO: Here we assume we should use the same raw JSON for potentially multiple parameters... const value = Type_1.Type.isObject(parsedJson) ? parsedJson[parameterMetadata.name] : parsedJson; const serializer = (0, IsJsonSerializer_1.isJsonSerializer)(parameterMetadata.serializer) ? parameterMetadata.serializer : new DefaultSerializer(parameterMetadata.serializer); return serializer.deserialize(value); }); const instance = new constructor(...constructorArgs); for (const propertyKey of Reflect.ownKeys(instance)) { const propertyMetadata = getJsonPropertyMetadata(instance, propertyKey); if (!propertyMetadata) { continue; } if (!!parameterMetadatas && parameterMetadatas.find(p => p.name === propertyMetadata.name)) { continue; } // TODO: Here we assume we should use the same raw JSON for potentially multiple properties... const value = Type_1.Type.isObject(parsedJson) ? parsedJson[propertyMetadata.name] : parsedJson; const serializer = (0, IsJsonSerializer_1.isJsonSerializer)(propertyMetadata.serializer) ? propertyMetadata.serializer : new DefaultSerializer(propertyMetadata.serializer); instance[propertyKey] = serializer.deserialize(value); } return instance; } toJSON(_key) { const serialized = {}; for (const propertyKey of Reflect.ownKeys(this)) { const propertyValue = this[propertyKey]; const metadata = getJsonPropertyMetadata(this, propertyKey); if (metadata) { const serializer = (0, IsJsonSerializer_1.isJsonSerializer)(metadata.serializer) ? metadata.serializer : new DefaultSerializer(metadata.serializer); serialized[metadata.name] = serializer.serialize(propertyValue); } } return serialized; } } exports.JsonSerializable = JsonSerializable; /** * A default implementation of IJsonSerializer */ class DefaultSerializer { constructor(jsonSerializableConstructor) { this[_a] = true; this._jsonSerializableConstructor = new Optional_1.Optional(jsonSerializableConstructor); } serialize(object) { if (Type_1.Type.isNull(object)) { return object; } if (Type_1.Type.isUndefined(object)) { return object; } if (Type_1.Type.isBoolean(object)) { return object; } if (Type_1.Type.isNumber(object)) { return object; } if (Type_1.Type.isString(object)) { return object; } if (Type_1.Type.isFunction(object)) { throw new Error('Cannot serialize functions'); } if (Type_1.Type.isSymbol(object)) { throw new Error('Cannot serialize symbols'); } if (!Type_1.Type.isObject(object)) { throw new Error('Cannot serialize ' + typeof object); } if (Type_1.Type.isArray(object)) { return object.map((value) => this.serialize(value)); } if ((0, IsJsonSerializable_1.isJsonSerializable)(object)) { return object; } if (Type_1.Type.isClass(object)) { const defaultInspector = new DefaultInspector_1.DefaultInspector(); throw new Error('Cannot serialize class ' + defaultInspector.inspect(object)); } return Object.entries(object).reduce((obj, [key, value]) => { obj[key] = this.serialize(value); return obj; }, {}); } deserialize(json) { // if (json === null || json === undefined) { // return json; // } if (this._jsonSerializableConstructor.isPresent()) { const jsonSerializableConstructor = this._jsonSerializableConstructor.get(); return jsonSerializableConstructor.fromJSON(json); } return json; } } exports.DefaultSerializer = DefaultSerializer; _a = IJsonSerializer_1.jsonSerializer; function parseJson(json) { if ((0, IsJsonSerializable_1.isJsonSerializable)(json)) { return parseJson(json.toJSON()); } if (Type_1.Type.isArray(json)) { return json.map(parseJson); } if (Type_1.Type.isObject(json)) { const object = {}; Object.entries(json).forEach(([key, value]) => { object[key] = parseJson(value); }); return object; } return json; } function getJsonPropertyMetadata(constructor, propertyKey) { return Reflect.getMetadata(jsonPropertyMetadataKey, constructor, typeof propertyKey === 'number' ? propertyKey.toString() : propertyKey); } function getJsonParameterMetadata(constructor) { return Reflect.getMetadata(jsonParameterMetadataKey, constructor, constructorPropertyKey); } const jsonPropertyMetadataKey = Symbol('jsonProperty'); const jsonParameterMetadataKey = Symbol('jsonParameter'); const constructorPropertyKey = Symbol('constructor'); function jsonProperty(nameOrSerializer, serializer) { const name = typeof nameOrSerializer === 'string' ? nameOrSerializer : undefined; return (target, propertyKey) => { if (!target || !propertyKey) { return; } const metadataValue = { name: name || propertyKey.toString(), serializer: !!nameOrSerializer && typeof nameOrSerializer !== 'string' ? nameOrSerializer : serializer, }; Reflect.defineMetadata(jsonPropertyMetadataKey, metadataValue, target, propertyKey); }; } exports.jsonProperty = jsonProperty; const STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,)]*))/mg; const ARGUMENT_NAMES = /([^\s,]+)/g; // tslint:disable-next-line:ban-types function getParamNames(func) { const fnStr = func.toString().replace(STRIP_COMMENTS, ''); const result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(ARGUMENT_NAMES); return result || []; } function getConstructorParameterNames(func) { // https://gist.github.com/afaqurk/507a9954c47491ebbc3adfe12dc8fd28 try { // try to see if environment supports class syntax // tslint:disable-next-line:no-eval eval('"use strict"; class foo {}'); // thanks to: http://stackoverflow.com/a/30692705/1735884 } catch (e) { throw new Error('Expected native classes to be supported'); } const classString = func.toString(); const beginningOfConstructor = classString.indexOf('constructor('); const endOfConstructor = classString.indexOf(')', beginningOfConstructor); const length = endOfConstructor - beginningOfConstructor; const args = classString.substr(beginningOfConstructor, length).replace('constructor(', ''); // thanks to: https://davidwalsh.name/javascript-arguments const result = args .split(',') .map((arg) => arg.replace(/\/\*.*\*\//, '').trim()) .filter((arg) => !!arg); return result; } function jsonParameter(name, serializer) { return (target, propertyKey, parameterIndex) => { if (!target) { return; } const parameterNames = propertyKey ? getParamNames(target[propertyKey]) : getConstructorParameterNames(target); const methodName = propertyKey || constructorPropertyKey; const metadataValues = Reflect.getOwnMetadata(jsonParameterMetadataKey, target, methodName) || []; const metadataValue = { index: parameterIndex, name: name || parameterNames[parameterIndex], serializer, }; metadataValues.push(metadataValue); Reflect.defineMetadata(jsonParameterMetadataKey, metadataValues, target, methodName); }; } exports.jsonParameter = jsonParameter; //# sourceMappingURL=JsonSerializable.js.map