Skip to content

Commit

Permalink
feat: add polymprphic serializer
Browse files Browse the repository at this point in the history
Use default serializer when none defined for child type
  • Loading branch information
egmacke committed Sep 4, 2023
1 parent 33065b3 commit 5ac5b2d
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 1 deletion.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"lint:eslint": "eslint .",
"playground": "ts-node ./benchmarks/playground.benchmark",
"prepare": "husky install",
"test": "jest --runInBand --verbose --coverage"
"test": "jest --runInBand --verbose --coverage",
"test:watch": "jest --runInBand --verbose --watch"
},
"publishConfig": {
"access": "public",
Expand Down
84 changes: 84 additions & 0 deletions src/classes/polymorphic-serialiser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { DataDocument } from '../interfaces/json-api.interface';
import { SerializerOptions } from '../interfaces/serializer.interface';
import ResourceIdentifier from '../models/resource-identifier.model';
import Resource from '../models/resource.model';
import { Dictionary, nullish, SingleOrArray } from '../types/global.types';
import { Helpers } from '../utils/serializer.utils';
import Relator from './relator';
import Serializer from './serializer';

export default class PolymorphicSerializer<
PrimaryType extends Dictionary<any>
> extends Serializer<PrimaryType> {
private serialisers: Record<string, Serializer>;

private key: keyof PrimaryType;

public constructor(
commonName: string,
key: keyof PrimaryType,
serializers: Record<string, Serializer>
) {
super(commonName);
this.serialisers = serializers;
this.key = key;
}

public async serialize(
data: SingleOrArray<PrimaryType> | nullish,
options?: Partial<SerializerOptions<PrimaryType>>
): Promise<Partial<DataDocument<PrimaryType>>> {
if (Array.isArray(data)) {
data.map((d) => {
return this.serializeSingle(d, options);
});
} else if (data) {
return this.serializeSingle(data, options);
}

return Object.values(this.serialisers)[0].serialize(data, options);
}

public createIdentifier(
data: PrimaryType,
options?: SerializerOptions<PrimaryType>
): ResourceIdentifier {
const serializer = this.getSerializerForData(data);
if (serializer) {
return serializer.createIdentifier(data, options);
}
return super.createIdentifier(data, options);
}

public async createResource(
data: PrimaryType,
options?: Partial<SerializerOptions<PrimaryType>>,
helpers?: Helpers<PrimaryType>,
relatorDataCache?: Map<Relator<any>, Dictionary<any>[]>
): Promise<Resource<PrimaryType>> {
const serializer = this.getSerializerForData(data);
if (serializer) {
return serializer.createResource(data, options, helpers, relatorDataCache);
}
return super.createResource(data, options, helpers, relatorDataCache);
}

private async serializeSingle(
data: PrimaryType,
options?: Partial<SerializerOptions<PrimaryType>>
) {
const serializer = this.getSerializerForData(data);
if (serializer) {
return serializer.serialize(data, options);
}
return super.serialize(data, options);
}

private getSerializerForData(data: PrimaryType): Serializer | null {
if (this.serialisers[data[this.key]]) {
return this.serialisers[data[this.key]];
}

return null;
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export { default as Relator } from './classes/relator';
export { default as JapiError } from './models/error.model';
export { default as ErrorSerializer } from './classes/error-serializer';
export { default as Serializer } from './classes/serializer';
export { default as PolymorphicSerializer } from './classes/polymorphic-serialiser';
export * from './interfaces/cache.interface';
export * from './interfaces/error-serializer.interface';
export * from './interfaces/error.interface';
Expand Down
113 changes: 113 additions & 0 deletions test/issue-65.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { PolymorphicSerializer, Relator, Serializer } from '../lib';
import ResourceIdentifier from '../lib/models/resource-identifier.model';
import Resource from '../lib/models/resource.model';

describe('Issue #65 - Polymorphic relator', () => {
class Model {
id: string;

children: Child[];
}

abstract class Child {
public type: string;

constructor(public id: string) {}
}

class Child1 extends Child {
constructor(id: string, public child1: string) {
super(id);
this.type = 'type:Child1';
}
}

class Child2 extends Child {
constructor(id: string, public child2: string) {
super(id);
this.type = 'type:Child2';
}
}

class Child3 extends Child {
constructor(id: string, public child2: string) {
super(id);
this.type = 'type:Child3';
}
}

it('should work non-polymorphicly', async () => {
const model: Model = new Model();
const child1: Child1 = new Child1('1', 'child1');
const child2: Child2 = new Child2('2', 'child2');

model.id = '1';
model.children = [child1, child2];

const Child1Serializer = new Serializer<Child>('Child');

const relator = new Relator<Model, Child>(
async (obj) => obj.children,
() => Child1Serializer,
{ relatedName: 'children' }
);

const ModelSerializer = new Serializer<Model>('Model', {
relators: [relator],
projection: {
children: 0,
},
});

const data = (await ModelSerializer.serialize(model)) as { data: Resource<Model> };

expect(data.data).toBeInstanceOf(Resource);
expect(data.data.relationships?.children.data).toHaveLength(2);
expect(data.data.relationships?.children.data?.[0].id).toEqual('1');
expect(data.data.relationships?.children.data?.[1].id).toEqual('2');

expect(data.data.relationships?.children.data?.[0].type).toEqual('Child');
expect(data.data.relationships?.children.data?.[1].type).toEqual('Child');
});

it('should work polymorphicly', async () => {
const model: Model = new Model();
const child1: Child1 = new Child1('1', 'child1');
const child2: Child2 = new Child2('2', 'child2');
const child3: Child2 = new Child3('3', 'child3');

model.id = '1';
model.children = [child1, child2, child3];

const Child1Serializer = new Serializer<Child1>('Child1');
const Child2Serializer = new Serializer<Child2>('Child2');

const PolySerializer = new PolymorphicSerializer<Child>('Child', 'type', {
'type:Child1': Child1Serializer,
'type:Child2': Child2Serializer,
});

const relator = new Relator<Model, Child>(async (obj) => obj.children, PolySerializer, {
relatedName: 'children',
});

const ModelSerializer = new Serializer<Model>('Model', {
relators: [relator],
projection: {
children: 0,
},
});

const data = (await ModelSerializer.serialize(model)) as { data: Resource<Model> };

expect(data.data).toBeInstanceOf(Resource);
expect(data.data.relationships?.children.data).toHaveLength(3);
expect(data.data.relationships?.children.data?.[0].id).toEqual('1');
expect(data.data.relationships?.children.data?.[1].id).toEqual('2');
expect(data.data.relationships?.children.data?.[2].id).toEqual('3');

expect(data.data.relationships?.children.data?.[0].type).toEqual('Child1');
expect(data.data.relationships?.children.data?.[1].type).toEqual('Child2');
expect(data.data.relationships?.children.data?.[2].type).toEqual('Child');
});
});

0 comments on commit 5ac5b2d

Please sign in to comment.