Skip to content

Commit

Permalink
feat: Add support for mapping JSON to class properties with same name
Browse files Browse the repository at this point in the history
- Added `mapClassProperties` option to `IJsonClassOptions`
- When enabled, JSON properties with the same name as class properties will be automatically mapped
  • Loading branch information
Unnoen committed Nov 23, 2023
1 parent cb6c71f commit 015e636
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 7 deletions.
39 changes: 32 additions & 7 deletions src/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ const verifyType = (propertyKey: string, properties: IJsonPropertyMetadata, json
throw new TypeError(`Property ${propertyKey} does not have a type and cannot be inferred from the default value.`);
}

if (type === JsonType.ANY && inferredType === undefined) {
return;
}

if (inferredType !== undefined && type === undefined) {
type = inferredType as DeserializeType;
}
Expand Down Expand Up @@ -225,15 +229,36 @@ export const DeserializeObject = <T>(json: object | string, classReference: new(
mapObjectProperty(propertyKey, classMetadata.properties[propertyKey], jsonObject, instance, false, options);
}

if (options?.passUnknownProperties && classConstructor?.prototype !== undefined) {
const unknownProperties = Object.keys(jsonObject).filter((key) => {
return !Object.values(classMetadata.properties).some((property) => {
return property.jsonProperty === key;
if (classConstructor?.prototype !== undefined) {
if (classMetadata.options?.mapClassProperties || options?.mapClassProperties) {
const classProperties = Object.keys(instance);

for (const classProperty of classProperties) {
if (!propertyKeys.includes(classProperty)) {
const propertyMetadata: IJsonPropertyMetadata = {
array: Array.isArray(instance[classProperty]),
jsonProperty: classProperty,
nested: false,
nullabilityMode: classMetadata.options?.defaultNullabilityMode ?? PropertyNullability.MAP,
type: JsonType.ANY,
};

verifyNullability(classProperty, propertyMetadata, jsonObject);
mapObjectProperty(classProperty, propertyMetadata, jsonObject, instance, false, options);
}
}
}

if (classMetadata.options?.passUnknownProperties || options?.passUnknownProperties) {
const unknownProperties = Object.keys(jsonObject).filter((key) => {
return !Object.values(classMetadata.properties).some((property) => {
return property.jsonProperty === key;
});
});
});

for (const unknownProperty of unknownProperties) {
instance[unknownProperty] = jsonObject[unknownProperty];
for (const unknownProperty of unknownProperties) {
instance[unknownProperty] = jsonObject[unknownProperty];
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import {

/**
* Represents the options for deserializing an object.
* @property {boolean} [mapClassProperties=false] - Whether to try and map JSON properties to class names by default.
* This means you don't need to use @JsonProperty if the property name is the same as the class name.
* @property {boolean} [passUnknownProperties=false] - Determines whether unknown properties should be passed or ignored during deserialization from JSON.
*/
export type DeserializeOptions = {
mapClassProperties?: boolean,
passUnknownProperties?: boolean,
};

Expand Down Expand Up @@ -48,8 +51,16 @@ export type IJsonPropertyMetadata = {
type?: DeserializeType,
};

/**
* Interface for defining options for JSON class.
* @property {PropertyNullability} defaultNullabilityMode - The default nullability mode for properties.
* @property {boolean} mapClassProperties - Whether to try and map JSON properties to class names by default.
* This means you don't need to use @JsonProperty if the property name is the same as the class name.
*/
export type IJsonClassOptions = {
defaultNullabilityMode?: PropertyNullability,
mapClassProperties?: boolean,
passUnknownProperties?: boolean,
};

/**
Expand Down
59 changes: 59 additions & 0 deletions test/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,36 @@ describe('JsonOptions Tests', () => {
}, Child);
}).toThrow('Property t2 is not a string. It is null.');
});

it('should map JSON properties to class properties of the same name if mapClassNames is true', () => {
@JsonOptions({
mapClassProperties: true,
})
class TestClass {
@JsonProperty('t', JsonType.STRING)
public test: string;

public test2?: string = undefined;

public test3?: string[] = undefined;

public test4: number = 1;
}

const testJson = DeserializeObject({
t: 'test',
test2: 'test2',
test3: ['test3'],
test4: 2,
}, TestClass);

expect(testJson).toEqual({
test: 'test',
test2: 'test2',
test3: ['test3'],
test4: 2,
});
});
});

describe('DeserializeOptions Tests', () => {
Expand All @@ -595,6 +625,35 @@ describe('DeserializeOptions Tests', () => {
});
});

it('should map JSON properties to class properties of the same name if mapClassNames is true', () => {
class TestClass {
@JsonProperty('t', JsonType.STRING)
public test: string;

public test2?: string = undefined;

public test3?: string[] = undefined;

public test4: number = 1;
}

const testJson = DeserializeObject({
t: 'test',
test2: 'test2',
test3: ['test3'],
test4: 2,
}, TestClass, {
mapClassProperties: true,
});

expect(testJson).toEqual({
test: 'test',
test2: 'test2',
test3: ['test3'],
test4: 2,
});
});

it('should pass unknown properties of all child classes if passUnknownProperties is true', () => {
class Child {
@JsonProperty('t', JsonType.STRING)
Expand Down

0 comments on commit 015e636

Please sign in to comment.