ts-std-lib
Version:
A standard library for typescript
231 lines • 9.84 kB
JavaScript
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
;