Skip to content
This repository has been archived by the owner on Mar 20, 2022. It is now read-only.

Added recursive dependencies support for denormalize method #220

Merged
merged 3 commits into from
Jan 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/__tests__/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,39 @@ Object {
}
`;

exports[`denormalize denormalizes recursive dependencies 1`] = `
Object {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My expected output would be:

{
  title: "Weekly report",
  user: {
    role: 'manager',
    reports: [ 1 ]
  }
}

This is going too many levels deep and isn't properly keeping track of the fact that we've already come across report 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's reasonable. I changed denormalization logic to confirm described expectations. Please, review last commit.

"id": 1,
"title": "Weekly report",
"user": Object {
"id": 1,
"reports": Array [
1,
],
"role": "manager",
},
}
`;

exports[`denormalize denormalizes recursive dependencies with custom idAttribute 1`] = `
Object {
"title": "Weekly report",
"user": Object {
"id": 1,
"reports": Array [
1,
Object {
"title": "Monthly report",
"user": 1,
"uuid": 2,
},
],
"role": "manager",
},
"uuid": 1,
}
`;

exports[`normalize can use fully custom entity classes 1`] = `
Object {
"entities": Object {
Expand Down
65 changes: 65 additions & 0 deletions src/__tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,69 @@ describe('denormalize', () => {
});
expect(() => denormalize('123', article, entities)).not.toThrow();
});

it('denormalizes recursive dependencies', () => {
const user = new schema.Entity('users');
const report = new schema.Entity('reports');

user.define({
reports: [ report ]
});
report.define({
user: user
});

const entities = {
reports: {
1: {
id: 1,
title: 'Weekly report',
user: 1
}
},
users: {
1: {
id: 1,
role: 'manager',
reports: [ 1 ]
}
}
};
expect(denormalize('1', report, entities)).toMatchSnapshot();
});

it('denormalizes recursive dependencies with custom idAttribute', () => {
const user = new schema.Entity('users');
const report = new schema.Entity('reports', {}, { idAttribute: 'uuid' });

user.define({
reports: [ report ]
});
report.define({
user: user
});

const entities = {
reports: {
1: {
uuid: 1,
title: 'Weekly report',
user: 1
},
2: {
uuid: 2,
title: 'Monthly report',
user: 1
}
},
users: {
1: {
id: 1,
role: 'manager',
reports: [ 1, 2 ]
}
}
};
expect(denormalize('1', report, entities)).toMatchSnapshot();
});
});
11 changes: 6 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,23 @@ export const normalize = (input, schema) => {
return { entities, result };
};

const unvisit = (input, schema, entities) => {
if (typeof schema === 'object' && (!schema.normalize || typeof schema.normalize !== 'function')) {
const unvisit = (input, schema, entities, visitedEntities) => {
if (typeof schema === 'object' && (!schema.denormalize || typeof schema.denormalize !== 'function')) {
let method = ObjectUtils.denormalize;
if (Array.isArray(schema)) {
method = ArrayUtils.denormalize;
}
return method(schema, input, unvisit, entities);
return method(schema, input, unvisit, entities, visitedEntities);
}

return schema.denormalize(input, unvisit, entities);
return schema.denormalize(input, unvisit, entities, visitedEntities);
};

export const denormalize = (input, schema, entities) => {
if (!input) {
return input;
}

return unvisit(input, schema, entities);
const visitedEntities = {};
return unvisit(input, schema, entities, visitedEntities);
};
8 changes: 4 additions & 4 deletions src/schemas/Array.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ export const normalize = (schema, input, parent, key, visit, addEntity) => {
return values.map((value, index) => visit(value, parent, key, schema, addEntity));
};

export const denormalize = (schema, input, unvisit, entities) => {
export const denormalize = (schema, input, unvisit, entities, visitedEntities) => {
schema = validateSchema(schema);
return input.map((entityOrId) => unvisit(entityOrId, schema, entities));
return input.map((entityOrId) => unvisit(entityOrId, schema, entities, visitedEntities));
};

export default class ArraySchema extends PolymorphicSchema {
Expand All @@ -34,7 +34,7 @@ export default class ArraySchema extends PolymorphicSchema {
.filter((value) => value !== undefined && value !== null);
}

denormalize(input, unvisit, entities) {
return input.map((value) => this.denormalizeValue(value, unvisit, entities));
denormalize(input, unvisit, entities, visitedEntities) {
return input.map((value) => this.denormalizeValue(value, unvisit, entities, visitedEntities));
}
}
23 changes: 14 additions & 9 deletions src/schemas/Entity.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { denormalize } from './Object';

export default class EntitySchema {
constructor(key, definition = {}, options = {}) {
if (!key || typeof key !== 'string') {
Expand Down Expand Up @@ -51,16 +53,19 @@ export default class EntitySchema {
return this.getId(input, parent, key);
}

denormalize(entityOrId, unvisit, entities) {
denormalize(entityOrId, unvisit, entities, visitedEntities) {
const entity = typeof entityOrId === 'object' ? entityOrId : entities[this.key][entityOrId];
const entityCopy = { ...entity };
Object.keys(this.schema).forEach((key) => {
if (entityCopy.hasOwnProperty(key)) {
const schema = this.schema[key];
entityCopy[key] = unvisit(entityCopy[key], schema, entities);
}
});

return entityCopy;
if (!visitedEntities[this.key]) {
visitedEntities[this.key] = {};
}

const id = this.getId(entity);
if (visitedEntities[this.key][id]) {
return id;
}
visitedEntities[this.key][id] = true;

return denormalize(this.schema, entity, unvisit, entities, visitedEntities);
}
}
5 changes: 2 additions & 3 deletions src/schemas/Object.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ export const normalize = (schema, input, parent, key, visit, addEntity) => {
return object;
};

export const denormalize = (schema, input, unvisit, entities) => {
export const denormalize = (schema, input, unvisit, entities, visitedEntities) => {
const object = { ...input };
Object.keys(schema).forEach((key) => {
const localSchema = schema[key];
if (object[key]) {
object[key] = unvisit(object[key], localSchema, entities);
object[key] = unvisit(object[key], schema[key], entities, visitedEntities);
}
});
return object;
Expand Down
4 changes: 2 additions & 2 deletions src/schemas/Polymorphic.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ export default class PolymorphicSchema {
{ id: normalizedValue, schema: this.getSchemaAttribute(value, parent, key) };
}

denormalizeValue(value, unvisit, entities) {
denormalizeValue(value, unvisit, entities, visitedEntities) {
if (!this.isSingleSchema && !value.schema) {
return value;
}
const schema = this.isSingleSchema ? this.schema : this.schema[value.schema];
return unvisit(value.id || value, schema, entities);
return unvisit(value.id || value, schema, entities, visitedEntities);
}
}
4 changes: 2 additions & 2 deletions src/schemas/Union.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default class UnionSchema extends PolymorphicSchema {
return this.normalizeValue(input, parent, key, visit, addEntity);
}

denormalize(input, unvisit, entities) {
return this.denormalizeValue(input, unvisit, entities);
denormalize(input, unvisit, entities, visitedEntities) {
return this.denormalizeValue(input, unvisit, entities, visitedEntities);
}
}
4 changes: 2 additions & 2 deletions src/schemas/Values.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ export default class ValuesSchema extends PolymorphicSchema {
}, {});
}

denormalize(input, unvisit, entities) {
denormalize(input, unvisit, entities, visitedEntities) {
return Object.keys(input).reduce((output, key) => {
const entityOrId = input[key];
return {
...output,
[key]: this.denormalizeValue(entityOrId, unvisit, entities)
[key]: this.denormalizeValue(entityOrId, unvisit, entities, visitedEntities)
};
}, {});
}
Expand Down