Typescript, serialize class objects - javascript

How do I serialize these classes to JSON?
As you can see from the example below JSON.stringify() does not serialize the list of Cache_Backend_LocalStorage_Tag inside the Cache_Backend_LocalStorage_TagThree object.
What am I missing?
interface Cache_Backend_LocalStorage_Tag_Interface {
_tag : string;
_keys : string[];
}
class Cache_Backend_LocalStorage_Tag implements Cache_Backend_LocalStorage_Tag_Interface {
public _tag : string;
public _keys : string[];
constructor(tag : string) {
this._tag = tag;
this._keys = [];
}
public add(key : string) : void {
this._keys.push(key);
}
public remove(key : string): boolean {
// Get the index of the key
var index = this._keys.indexOf(key, 0);
// Check if we found the keys index
if (index != undefined) {
this._keys.splice(index, 1);
return true;
}
return false;
}
public get tag(): string {
return this._tag;
}
public get keys(): string[] {
return this._keys;
}
}
interface Cache_Backend_LocalStorage_TagThree_Interface {
_tags : Cache_Backend_LocalStorage_Tag[];
}
class Cache_Backend_LocalStorage_TagThree implements Cache_Backend_LocalStorage_TagThree_Interface {
public _tags : Cache_Backend_LocalStorage_Tag[];
constructor(tags : Cache_Backend_LocalStorage_Tag[] = []) {
this._tags = tags;
}
public add(tag : Cache_Backend_LocalStorage_Tag) : void {
this.tags[tag.tag] = tag;
}
public get tags(): Cache_Backend_LocalStorage_Tag[] {
return this._tags;
}
public get(tagKey : string): Cache_Backend_LocalStorage_Tag {
return this.tags[tagKey];
}
public addKeyToTag(tagKey, key) {
this.tags[tagKey].add(key);
}
public removeKeyFromTag(tagKey, key) {
// Get the tag
var tag = this._tags[tagKey];
// Check if we found the tag index
if (tag != undefined) {
return tag.remove(key);
}
return false;
}
public clear(tagKey : string): void {
delete this._tags[tagKey];
}
public static fromObject(object): Cache_Backend_LocalStorage_TagThree {
return new Cache_Backend_LocalStorage_TagThree(object._tags);
}
}
Issue:
var tagThree = new Cache_Backend_LocalStorage_TagThree();
tagThree.add(new Cache_Backend_LocalStorage_Tag("stores"));
tagThree.addKeyToTag("stores", "store5");
tagThree.removeKeyFromTag("stores", "store5");
// {"_tags":[]}
console.log(JSON.stringify(tagThree));
// { _tags: [ stores: { _tag: 'stores', _keys: [Object] } ] }
console.log(tagThree);

Reason
It's because you're assigning properties to an array and array properties won't be included in JSON serialization. For example:
var a = [];
a["test"] = "some value";
JSON.stringify(a); // returns: []
You need to use a plain object:
var o = {};
o["test"] = "some value";
JSON.stringify(o); // returns: {"test":"some value"}
Solution
Change your Cache_Backend_LocalStorage_TagThree_Interface interface to use a dictionary like object:
interface Cache_Backend_LocalStorage_TagThree_Interface {
_tags : { [tag: string] : Cache_Backend_LocalStorage_Tag; };
}
Then update all areas of the code that will now have a compile error to use the same type. For example, change:
constructor(tags : Cache_Backend_LocalStorage_Tag[] = []) {
To:
constructor(tags : { [tag: string] : Cache_Backend_LocalStorage_Tag; } = {}) {
Just for fun - Changing default serialization behaviour (Not recommended)
If you really want to make this work with an array with your current setup (I'm not sure why), you can override how serialization is done. To do this, add a toJSON method to the _tags array in Cache_Backend_LocalStorage_TagThree. This allows you to control how the object is serialized when JSON.stringify is called on it. For example:
this._tags.toJSON = function() {
var values = [];
for (var v in this) {
if (this[v] instanceof Cache_Backend_LocalStorage_Tag) {
values.push(this[v]);
}
}
return JSON.stringify(values);
};

Related

NestJs: How can I add options for my Custom Validation Pipe?

I created a custom validation pipe where I want to add some custom logic.I want to know how can I extend it so that I can call it with the below options
Edited:
I have referred the below link of the github file and have developed my own pipe but there still seems to be something missing as its not validating as per the DTO
https://github.com/nestjs/nest/blob/1f6fca5f55e9e51705fa326654760736b254f4e5/packages/common/pipes/validation.pipe.ts#L48
I was able to solve it by creating my own pipe.
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException, Logger, Optional } from '#nestjs/common';
import { ValidatorOptions, ValidationError, isObject, validate } from 'class-validator';
import { plainToClass, classToPlain, ClassTransformOptions } from 'class-transformer';
import { iterate } from 'iterare';
const isUndefined = (obj: any): obj is undefined =>
typeof obj === 'undefined';
const isNil = (val: any): val is null | undefined =>
isUndefined(val) || val === null;
interface ValidationPipeOptions extends ValidatorOptions {
transform?: boolean;
transformOptions?: ClassTransformOptions;
validateCustomDecorators?: boolean;
}
#Injectable()
export class ValidationPipe implements PipeTransform<any> {
private readonly logger = new Logger(ValidationPipe.name);
protected isTransformEnabled: boolean;
protected transformOptions: ClassTransformOptions;
protected validatorOptions: ValidatorOptions;
protected validateCustomDecorators: boolean;
constructor(#Optional() options?: ValidationPipeOptions) {
options = options || {};
const {
transform,
transformOptions,
validateCustomDecorators,
...validatorOptions
} = options;
this.isTransformEnabled = !!transform;
this.transformOptions = transformOptions;
this.validatorOptions = validatorOptions;
this.validateCustomDecorators = validateCustomDecorators || false;
}
async transform(value: any, metadata: ArgumentMetadata) {
const metatype = metadata.metatype;
if (!metatype || !this.toValidate(metadata)) {
return this.isTransformEnabled
? this.transformPrimitive(value, metadata)
: value;
}
const originalValue = value;
value = this.toEmptyIfNil(value);
const isNil = value !== originalValue;
const isPrimitive = this.isPrimitive(value);
this.stripProtoKeys(value);
let object = plainToClass(metatype, value, this.transformOptions);
const originalEntity = object;
const isCtorNotEqual = object.constructor !== metatype;
if (isCtorNotEqual && !isPrimitive) {
object.constructor = metatype;
} else if (isCtorNotEqual) {
// when "entity" is a primitive value, we have to temporarily
// replace the entity to perform the validation against the original
// metatype defined inside the handler
object = { constructor: metatype };
}
const errors = await this.validate(object, this.validatorOptions);
if (errors.length > 0) {
for (let error of errors) {
for (let key in error.constraints) {
this.logger.error(`${error.target.constructor.name}:${error.constraints[key]}`);
}
}
throw new BadRequestException('Invalid Input Parameters');
}
if (isPrimitive) {
// if the value is a primitive value and the validation process has been successfully completed
// we have to revert the original value passed through the pipe
object = originalEntity;
}
if (this.isTransformEnabled) {
return object;
}
if (isNil) {
// if the value was originally undefined or null, revert it back
return originalValue;
}
return Object.keys(this.validatorOptions).length > 0
? classToPlain(object, this.transformOptions)
: value;
}
protected stripProtoKeys(value: Record<string, any>) {
delete value.__proto__;
const keys = Object.keys(value);
iterate(keys)
.filter(key => isObject(value[key]) && value[key])
.forEach(key => this.stripProtoKeys(value[key]));
}
protected isPrimitive(value: unknown): boolean {
return ['number', 'boolean', 'string'].includes(typeof value);
}
protected transformPrimitive(value: any, metadata: ArgumentMetadata) {
if (!metadata.data) {
// leave top-level query/param objects unmodified
return value;
}
const { type, metatype } = metadata;
if (type !== 'param' && type !== 'query') {
return value;
}
if (metatype === Boolean) {
return value === true || value === 'true';
}
if (metatype === Number) {
return +value;
}
return value;
}
protected toEmptyIfNil<T = any, R = any>(value: T): R | {} {
return isNil(value) ? {} : value;
}
private toValidate(metadata: ArgumentMetadata): boolean {
const { metatype, type } = metadata;
if (type === 'custom' && !this.validateCustomDecorators) {
return false;
}
const types = [String, Boolean, Number, Array, Object, Buffer];
return !types.some(t => metatype === t) && !isNil(metatype);
}
protected validate(
object: object,
validatorOptions?: ValidatorOptions,
): Promise<ValidationError[]> | ValidationError[] {
return validate(object, validatorOptions);
}
}

Error trying to create a Dictionary object in Typescript

I need to create a dictionary object in typescript, something that has the following function:
obj.add(key, value);
obj.remove(key);
obj.contains(key); // returns true/false
obj.get(key); // returns value
This was the model I created to accommodate:
export class Dictionary<T, F> {
items = {};
constructor() {
this.items = {};
}
public contains(key: T): boolean {
return key in this.items;
}
public add(key: T, value: F): void {
this.items[key] = value;
}
public get(key: T): F {
return this.items[key];
}
public remove(key: T): boolean {
if (this.contains(key) ){
delete this.items[key];
return true;
}
return false;
}
}
However, I keep getting this error:
Error: src/app/models/dictionary.model.ts:10:7 - error TS2536: Type 'T' cannot be used to index type '{}'.
10 this.items[key] = value;
Has anyone come across this before and can recommend the most appropriate fix?
Thanks in advance for any pointers (examples welcome :-))!

Deserialize a JSON object into its original class

I am trying to serialize/deserialize an object. I am thinking that the best way would be to save the path to the file that called as part of the json, but I am unsure of how to get said path.
Can getting this path to the file (A.ts/B.ts) be done when called within the parent (Base.ts)?
Is there maybe a better approach to doing this? I am trying to take a class created in the main node process, and and pass it to a worker process, the only why to do this that I can see is to serialize/deserialize the class somehow.
// src/Base.ts
export abstract class Base {
public serialize() {
return JSON.stringify({path: '', obj: this})
}
public static deserialize(json: string) {
let { path, obj } = JSON.parse(json) as { path: string, obj: { [key: string]: any } }
let newable = require(path)
let o = new newable
return Object.assign(o, obj)
}
}
// src/filter/A.ts
export class A extends Base {
public cat: string = 'meow'
public sayHi() { return this.cat }
}
// src/filter/B.ts
export class B extends Base {
public dog: string = 'woof'
public sayHi() { return this.dog }
}
// test.ts
let serializedA = new A().serialize()
let serializedB = new B().serialize()
// Create child...
let worker = cp.fork(path.join(__dirname, './worker'), [], { silent: true })
worker.send({ serializedA, serializedB })
// worker.ts
process.on('message', msg => {
let classA = Base.deserialize(msg.serializedA)
let classB = Base.deserialize(msg.serializedB)
})
The simplest way that comes to mind would be to have a set of class names associated with callbacks that would require the appropriate classes.
// src/JsonIO.ts
export class JsonIO {
private _classes: { name: string, callback: () => { new(): any } }[] = []
public serialize(obj: any): string {
return JSON.stringify({ class: obj.constructor.name, value: obj })
}
public deserialize(json: string) {
const obj = JSON.parse(json) as { class: string, value: any }
const clazz = this._classes.find(c => c.name == obj.class)
if(!clazz) return obj.value
return Object.assign(new (clazz.callback()), obj.value)
}
public registerClass(name: string, callback: () => { new(): any }) {
this._classes.push({ name, callback })
}
}
// src/Base.ts
export abstract class Base { /* ... */ }
// src/filter/A.ts
export class A {
public cat: string = 'meow'
}
// src/filter/B.ts
export class B {
public dog: string = 'woof'
}
// test.ts
const io = new JsonIO()
io.registerClass('A', () => A /* require('filter/A.ts') */)
io.registerClass('B', () => B /* require('filter/B.ts') */)
const serializedA = io.serialize(new A)
const serializedB = io.serialize(new B)
const a = io.deserialize(serializedA)
const b = io.deserialize(serializedB)

Storing interfaces in object

Imagine I have the following interfaces
interface IMarket {
ID: number,
Name: string,
MarketDescription: string
}
interface IDepartment {
ID: number,
Name: string,
DepartmentDescription: string
}
Is there a way to store the interfaces in an object like this?
var typeMap = { Markets: IMarket, Departments: IDepartment }
I'd like to do something like this. I'd like to dynamically set the generic type for "getQueryResults" based on a string value I pass into the constructor.
export class Service {
protected baseURL = "";
protected typeName = "";
private typeMap = { Markets: IMarket, Departments: IDepartment }
constructor(typeName) {
this.baseURL = 'http://localhost/API/odata/' + typeName;
this.currentType = typeMap[typeName];
}
getQueryResults(): Promise<this.currentType> {
return new Promise<this.currentType>((resolve, reject) => {
$.getJSON(this.baseURL, function (returnValue) {
resolve(returnValue.value);
});
})
}
}
var marketService = new Service("Markets");
var topMarket = marketService.getQueryResults();
//topMarket is an instance(?) of IMarket
var departmentService = new Service("Departments");
var topDepartment = departmentServicegetQueryResults();
//topDepartment is an instance(?) of IDepartment
That can be simply solved using generics, it's exactly what it's for:
export class Service<T> {
protected baseURL = "";
constructor() {
this.baseURL = 'http://localhost/API/odata/' + typeName;
}
getQueryResults(): Promise<T> {
return new Promise<T>((resolve, reject) => {
$.getJSON(this.baseURL, function (returnValue) {
resolve(returnValue.value);
});
})
}
}
var marketService = new Service<IMarket>();
var topMarket: Promise<IMarket> = marketService.getQueryResults();
var departmentService = new Service<IDepartment>();
var topDepartment: Promise<IDepartment> = departmentService.getQueryResults();
Edit
You can use 2 more classes to "get rid" of the need to have Service<TYPE> more than once (per TYPE):
export abstract class Service<T> {
protected baseURL = "";
constructor() {
this.baseURL = 'http://localhost/API/odata/' + this.getTypeName();
}
getQueryResults(): Promise<T> {
return new Promise<T>((resolve, reject) => {
$.getJSON(this.baseURL, function (returnValue) {
resolve(returnValue.value);
});
})
}
protected abstract getTypeName(): string;
}
export class MarketsService extends Service<IMarket> {
protected getTypeName(): string {
return "Markets";
}
}
export class DepartmentsService extends Service<IDepartment> {
protected getTypeName(): string {
return "Departments";
}
}
var marketService = new MarketsService();
var topMarket: Promise<IMarket> = marketService.getQueryResults();
var departmentService = new DepartmentsService();
var topDepartment: Promise<IDepartment> = departmentService.getQueryResults();
But unlike the need to specify the type every time you use Service, these extra classes will be part of the compiled js, so it's a question of what's more important to you.
Taking a note from the TypeScript docs:
http://www.typescriptlang.org/docs/handbook/namespaces.html#namespaced-validators
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
It appears you would want:
namespace YourNamespace {
export interface IMarket {
ID: number,
Name: string,
MarketDescription: string
}
export interface IDepartment {
ID: number,
Name: string,
DepartmentDescription: string
}
}

Returning a function from Javascript into TypeScript

I have just started out with Typescript and want to convert the following Javascript into Typescript.
The first code block is the actual JS and the second is the TypeScript which I have so far.
I think it's mostly right but the bit I am having trouble working out is the return function at the end of the Javascript. How would I write this bit in my TypeScript code?
var ABC = ABC || {};
ABC.navigationService = function () {
var navigate = {};
function navigateChart(e, query) {
}
function drillChart(direction, type, query) {
}
function canDrillUp(series, query) {
}
function drillUp(series, query) {
}
return {
navigate: navigateChart,
drill: drillChart,
canDrillUp: canDrillUp,
drillUp: drillUp
}
}
angular.module("MyApp").service("navigationService", [ABC.navigationService]);
and with the typescript, this is what I have so far, including only the functions for which I want to return the results as in the Javascript:-
module ABC.Visualisation.Services {
'use strict';
var xPos = 0;
var yPos = 0;
var canDrilldownPoint = false;
var canDrillupPoint = false;
var canDrilldownSeries = false;
var canDrillupSeries = false;
var navigate = {
loading: false,
showGui: false,
canDrilldownPoint: canDrilldownPoint,
canDrillupPoint: canDrillupPoint,
canDrilldownSeries: canDrilldownSeries,
canDrillupSeries: canDrillupSeries,
x: xPos,
y: yPos
};
export class NavigationService implements INavigationService {
public navigateChart(e: any, query: any) {
}
public drillChart(direction: string, type: string, query: any): void {
}
public canDrillUp(series: any, query: any): boolean {
}
public drillUp(series: any, query: any): void {
}
}
}
angular.module("MyApp").service("NavigationService", [ABC.Visualisation.Services.NavigationService]);
1) I believe that
angular.module("MyApp").service("NavigationService", [ABC.Visualisation.Services.NavigationService]);
should be
angular.module("MyApp").service("NavigationService", ABC.Visualisation.Services.NavigationService);
2) This is how you can put variables to a module: Creating a variable with get/set inside a module in Typescript

Categories