Skip to content

Commit

Permalink
feat(json-serializer-options): add additionalPropertiesPolicy option #…
Browse files Browse the repository at this point in the history
  • Loading branch information
GillianPerard committed Oct 25, 2022
1 parent 7d97e42 commit 07284ef
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 4 deletions.
91 changes: 89 additions & 2 deletions spec/json-serializer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import 'reflect-metadata';
import { JsonSerializer, JsonSerializerOptions, logError, throwError } from '../src';
import { Animal, data, deserializedData, Employee, Gender, Organization, Zoo } from '../examples';
import {
JsonObject,
JsonProperty,
JsonSerializer,
JsonSerializerOptions,
logError,
throwError
} from '../src';

@JsonObject()
class WithAdditionalProperties {
@JsonProperty() id: string;
name: string;
}

describe('constructor', () => {
it('should have default options', () => {
Expand All @@ -10,14 +23,16 @@ describe('constructor', () => {
nullishPolicy: {
undefined: 'remove',
null: 'allow'
}
},
additionalPropertiesPolicy: 'remove'
};
expect(jsonSerializer.options).toStrictEqual(options);
});

it('should override default options when pass new in param', () => {
const options: Partial<JsonSerializerOptions> = {
errorCallback: undefined,
additionalPropertiesPolicy: 'disallow',
formatPropertyName: (name: string) => `_${name}`
};
const overriddenOptions: JsonSerializerOptions = {
Expand All @@ -26,6 +41,7 @@ describe('constructor', () => {
undefined: 'remove',
null: 'allow'
},
additionalPropertiesPolicy: 'disallow',
formatPropertyName: (name: string) => `_${name}`
};
const jsonSerializer = new JsonSerializer(options);
Expand Down Expand Up @@ -396,3 +412,74 @@ describe('formatPropertyName', () => {
expect(jsonSerializer.serializeObject(employee)).toStrictEqual(jsonEmployee);
});
});

describe('allowedAdditionalProperties', () => {
let jsonSerializer: JsonSerializer;
let obj: any;
let instance: WithAdditionalProperties;

beforeEach(() => {
jsonSerializer = new JsonSerializer({ errorCallback: throwError });
obj = {
id: 'bb34c00b-af82-4c27-98c2-766c738796b8'
};
instance = new WithAdditionalProperties();
instance.id = 'bb34c00b-af82-4c27-98c2-766c738796b8';
});

describe('deserialize', () => {
beforeEach(() => {
obj.name = 'test';
obj.age = 20;
});

it('should remove additional properties', () => {
expect(jsonSerializer.deserialize(obj, WithAdditionalProperties)).toStrictEqual(
instance
);
});

it('should keep additional properties', () => {
jsonSerializer.options.additionalPropertiesPolicy = 'allow';
instance.name = 'test';
(instance as any).age = 20;
expect(jsonSerializer.deserialize(obj, WithAdditionalProperties)).toStrictEqual(
instance
);
});

it('should throw an error additional properties', () => {
jsonSerializer.options.additionalPropertiesPolicy = 'disallow';
const fn = () => jsonSerializer.deserialize(obj, WithAdditionalProperties);
expect(fn).toThrowError(
'Additional properties detected in {"id":"bb34c00b-af82-4c27-98c2-766c738796b8","name":"test","age":20}: name,age.'
);
});
});

describe('serialize', () => {
beforeEach(() => {
instance.name = 'test';
(instance as any).age = 20;
});

it('should remove additional properties', () => {
expect(jsonSerializer.serialize(instance)).toStrictEqual(obj);
});

it('should keep additional properties', () => {
jsonSerializer.options.additionalPropertiesPolicy = 'allow';
obj.name = 'test';
obj.age = 20;
expect(jsonSerializer.serialize(instance)).toStrictEqual(obj);
});

it('should throw an error additional properties', () => {
jsonSerializer.options.additionalPropertiesPolicy = 'disallow';
const fn = () => jsonSerializer.serialize(instance);
expect(fn).toThrowError(
'Additional properties detected in {"id":"bb34c00b-af82-4c27-98c2-766c738796b8","name":"test","age":20}: name,age.'
);
});
});
});
1 change: 1 addition & 0 deletions src/json-serializer-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export class JsonSerializerOptions {
undefined: 'remove',
null: 'allow'
};
additionalPropertiesPolicy: Policy = 'remove';
formatPropertyName?: FormatPropertyNameProto;
}

Expand Down
46 changes: 44 additions & 2 deletions src/json-serializer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
difference,
hasConstructor,
isArray,
isDateObject,
Expand Down Expand Up @@ -87,7 +88,9 @@ export class JsonSerializer {
return instance;
}

Object.keys(jsonPropertiesMetadata).forEach(key => {
const jsonPropertiesMetadataKeys = Object.keys(jsonPropertiesMetadata);

jsonPropertiesMetadataKeys.forEach(key => {
const metadata = jsonPropertiesMetadata[key];
const property = this.deserializeProperty(instance, key, obj as object, metadata);
this.checkRequiredProperty(metadata, instance, key, property, obj, false);
Expand All @@ -97,6 +100,24 @@ export class JsonSerializer {
}
});

if (this.options.additionalPropertiesPolicy === 'remove') {
return instance;
}

const additionalProperties = difference(Object.keys(obj), jsonPropertiesMetadataKeys);

if (!additionalProperties.length) {
return instance;
}

if (this.options.additionalPropertiesPolicy === 'disallow') {
this.error(
`Additional properties detected in ${JSON.stringify(obj)}: ${additionalProperties}.`
);
} else if (this.options.additionalPropertiesPolicy === 'allow') {
additionalProperties.forEach(key => (instance[key] = obj[key]));
}

return instance;
}

Expand Down Expand Up @@ -190,8 +211,9 @@ export class JsonSerializer {

const json = {};
const instanceKeys = Object.keys(instance);
const jsonPropertiesMetadataKeys = Object.keys(jsonPropertiesMetadata);

Object.keys(jsonPropertiesMetadata).forEach(key => {
jsonPropertiesMetadataKeys.forEach(key => {
const metadata = jsonPropertiesMetadata[key];

if (instanceKeys.includes(key)) {
Expand Down Expand Up @@ -248,6 +270,26 @@ export class JsonSerializer {
}
});

if (this.options.additionalPropertiesPolicy === 'remove') {
return json;
}

const additionalProperties = difference(instanceKeys, jsonPropertiesMetadataKeys);

if (!additionalProperties.length) {
return json;
}

if (this.options.additionalPropertiesPolicy === 'disallow') {
this.error(
`Additional properties detected in ${JSON.stringify(
instance
)}: ${additionalProperties}.`
);
} else if (this.options.additionalPropertiesPolicy === 'allow') {
additionalProperties.forEach(key => (json[key] = instance[key]));
}

return json;
}

Expand Down

0 comments on commit 07284ef

Please sign in to comment.