Skip to content

Commit

Permalink
feat(json-serializer): add possibility to define data structure for a…
Browse files Browse the repository at this point in the history
… property #185

reflection cannot understand properties possibly undefined
to avoid this problem for array/dictionary/map/set set manually the data structure
  • Loading branch information
GillianPerard committed Oct 5, 2022
1 parent ff83e2a commit ace015f
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 32 deletions.
4 changes: 2 additions & 2 deletions examples/models/organization.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { JsonProperty, JsonObject } from '../../src';
import { JsonObject, JsonProperty } from '../../src';
import { Human } from './human';
import { Society } from './society';
import { Zoo } from './zoo';

@JsonObject()
export class Organization extends Society {
@JsonProperty({ name: 'zoos', type: Zoo }) zoos: Array<Zoo>;
@JsonProperty({ isDictionary: true })
@JsonProperty({ dataStructure: 'dictionary' })
zoosName: { [id: string]: string };
@JsonProperty({
name: ['mainShareholder', 'secondaryShareholder', 'thirdShareholder'],
Expand Down
6 changes: 3 additions & 3 deletions examples/models/zoo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ export class Zoo {
coordinates: { x: number; y: number; z: number };
@JsonProperty()
description: string;
@JsonProperty({ type: Employee })
employees: Set<Employee>;
@JsonProperty({ type: Employee, dataStructure: 'set' })
employees: Set<Employee> | undefined;
@JsonProperty()
id: number;
@JsonProperty()
Expand All @@ -51,7 +51,7 @@ export class Zoo {
@JsonProperty({ type: UnknownAnimal })
unknownAnimals: Map<string, UnknownAnimal>;
@JsonProperty({
isDictionary: true,
dataStructure: 'dictionary',
type: phoneNumberType
})
phoneBook: { [id: string]: PhoneNumber | string };
Expand Down
41 changes: 26 additions & 15 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,46 @@ import { Reflection } from './reflection';

export type Type<T> = new (...args: Array<any>) => T;

export const hasConstructor = <T = any>(f: unknown): f is Type<T> => {
if (typeof f !== 'function') {
return false;
}

try {
Reflect.construct(String, [], f);
} catch (e) {
return false;
}

return true;
};

export const isString = (value: unknown): value is string => typeof value === 'string';

export const isNumber = (value: any): value is number => typeof value === 'number';

export const isBoolean = (value: any): value is number => typeof value === 'boolean';

export const isObject = (value: unknown): value is object =>
value !== null && typeof value === 'object' && !isArray(value);

export const isArray = (value: unknown): value is Array<any> => Array.isArray(value);

export const isDateObject = (value: any): value is Date => toString.call(value) === '[object Date]';

export const isDateValue = (value: any): value is string | number => {
if (isDateObject(value) || isArray(value)) {
return false;
}

return !isNaN(Date.parse(value));
};
export const isJsonObject = (propertyType: any) => Reflection.isJsonObject(propertyType);

export const isMap = (value: unknown): value is Map<any, any> => value instanceof Map;

export const isNullish = (value: any): value is null | undefined =>
[null, undefined].includes(value);
export const isJsonObject = (propertyType: any) => Reflection.isJsonObject(propertyType);

export const isPredicate = (value: any): value is PredicateProto => {
if (!value) {
Expand All @@ -32,6 +55,8 @@ export const isPredicate = (value: any): value is PredicateProto => {
return (valueLength === 1 || valueLength === 2) && !paramTypes;
};

export const isSet = (value: unknown): value is Set<any> => value instanceof Set;

export const tryParse = (value: any) => {
try {
const parsed = JSON.parse(value);
Expand All @@ -40,17 +65,3 @@ export const tryParse = (value: any) => {
return value;
}
};

export const hasConstructor = <T = any>(f: unknown): f is Type<T> => {
if (typeof f !== 'function') {
return false;
}

try {
Reflect.construct(String, [], f);
} catch (e) {
return false;
}

return true;
};
3 changes: 2 additions & 1 deletion src/json-property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { Reflection } from './reflection';

export type IOProto = (property: any, currentInstance?: any) => any;
export type PredicateProto = (property: any, parentProperty?: any) => any;
export type DataStructure = 'array' | 'dictionary' | 'map' | 'set';

export interface JsonPropertyBaseMetadata {
isDictionary?: boolean;
dataStructure?: DataStructure;
required?: boolean;
beforeSerialize?: IOProto;
afterSerialize?: IOProto;
Expand Down
60 changes: 49 additions & 11 deletions src/json-serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {
isDateObject,
isDateValue,
isJsonObject,
isMap,
isNullish,
isNumber,
isObject,
isSet,
isString,
tryParse,
Type
Expand Down Expand Up @@ -297,10 +299,8 @@ export class JsonSerializer {
}

const type = Reflection.getType(instance, propertyKey);
const typeName = type?.name?.toLowerCase();
const isArrayProperty = typeName === 'array';
const isSetProperty = typeName === 'set';
const isMapProperty = typeName === 'map';
const { isArrayProperty, isSetProperty, isMapProperty, isDictionaryProperty } =
this.getDataStructureInformation(type, instance[propertyKey], metadata);
let propertyType = metadata.type || type;

if (metadata.beforeDeserialize) {
Expand All @@ -310,7 +310,7 @@ export class JsonSerializer {
let property: any;
const predicate = metadata.predicate;

if (metadata.isDictionary || isMapProperty) {
if (isDictionaryProperty || isMapProperty) {
property = this.deserializeDictionary(dataSource, propertyType, predicate);

if (isMapProperty) {
Expand Down Expand Up @@ -489,6 +489,46 @@ export class JsonSerializer {
return json[name];
}

private getDataStructureInformation(
type: any,
property: any,
metadata: JsonPropertyMetadata
): {
isArrayProperty: boolean;
isDictionaryProperty: boolean;
isMapProperty: boolean;
isSetProperty: boolean;
} {
if (metadata.dataStructure) {
return {
isArrayProperty: metadata.dataStructure === 'array' ?? false,
isDictionaryProperty: metadata.dataStructure === 'dictionary' ?? false,
isMapProperty: metadata.dataStructure === 'map' ?? false,
isSetProperty: metadata.dataStructure === 'set' ?? false
};
}

const typeName = type?.name?.toLowerCase();

/**
* When a property is set as possibly undefined the type change
* to object even if it is an array, a set or a map.
*/
return typeName === 'object'
? {
isArrayProperty: isArray(property),
isDictionaryProperty: false,
isMapProperty: isMap(property),
isSetProperty: isSet(property)
}
: {
isArrayProperty: typeName === 'array',
isDictionaryProperty: false,
isMapProperty: typeName === 'map',
isSetProperty: typeName === 'set'
};
}

private getJsonPropertiesMetadata(instance: any): JsonPropertiesMetadata | undefined {
const { baseClassNames } = Reflection.getJsonObjectMetadata(instance.constructor) ?? {};
const instanceMap = Reflection.getJsonPropertiesMetadata(instance);
Expand Down Expand Up @@ -561,10 +601,9 @@ export class JsonSerializer {
private serializeProperty(instance: object, key: string, metadata: JsonPropertyMetadata): any {
const property = instance[key];
const type = Reflection.getType(instance, key);
const typeName = type?.name?.toLowerCase();
const isArrayProperty = typeName === 'array';
const isSetProperty = typeName === 'set';
const isMapProperty = typeName === 'map';
const { isArrayProperty, isDictionaryProperty, isMapProperty, isSetProperty } =
this.getDataStructureInformation(type, property, metadata);

const predicate = metadata.predicate;
const propertyType = metadata.type || type;
const isJsonObjectProperty = isJsonObject(propertyType);
Expand All @@ -575,7 +614,7 @@ export class JsonSerializer {
return this.serializeObjectArray(array);
}

if (metadata.isDictionary || isMapProperty) {
if (isDictionaryProperty || isMapProperty) {
if (!isMapProperty) {
return this.serializeDictionary(property);
}
Expand All @@ -593,7 +632,6 @@ export class JsonSerializer {
}
});

console.log(obj);
return this.serializeDictionary(obj);
}

Expand Down

0 comments on commit ace015f

Please sign in to comment.