fix: better mergeDeep implementation and tests#5097
fix: better mergeDeep implementation and tests#5097ferk6a wants to merge 4 commits intotypeorm:masterfrom ferk6a:merge-deep
Conversation
|
I guess the tests pass, but I'm a bit worried about edge cases, maybe something like passing a class instance to an |
|
Tests can easily pass because we don't have a large test coverage for monogdb driver. I think your changes won't work properly because in the cases when embedded objects (nested objects) are used, we actually need class instances, we can't skip them. Solution to this issue is to rewrite current way of "copying object" - traverse over defined columns in the class and all its embeddeds and copy only object properties that are defined in these columns. Columns definition tells us what schema should be used for saving and this schema can't be recursive. |
|
@pleerock Ok, thanks! I will work some more on this then. I will add more tests regarding having nested embedded objects and objects with more information than the schema allows (should we throw an error or something if we detect that, or just ignore?). I will work on making a new function to transform nested entities into plain objects using schema (probably using |
@pleerock I was looking into this again, and it looks like there is a test for this, and it passed: So I don't know exactly how I could break this, since I saw that it passed that, it looks to me to be pretty solid (even though it really doesn't look like it). |
|
@pleerock is this ever going to get merged any time soon? |
imnotjames
left a comment
There was a problem hiding this comment.
Can you rebase & fix the conflict?
| // for mongodb we have a bit different updation logic | ||
| if (this.queryRunner instanceof MongoQueryRunner) { | ||
| const partialEntity = OrmUtils.mergeDeep({}, subject.entity!); | ||
| const partialEntity = OrmUtils.mergeDeep({}, { ...subject.entity! }); |
There was a problem hiding this comment.
Why shallow cloning the object here? Doesn't this code that already?
| static isObject(item: any) { | ||
| return (item && typeof item === "object" && !Array.isArray(item)); | ||
| // Checks if it's an object made by Object.create(null), {} or new Object() | ||
| static isPlainObject(item: any) { |
There was a problem hiding this comment.
If you're dropping isObject can you make this private? To reduce the public API footprint.
| @Column({ type: String, transformer: new ComplexTransformer(), nullable: true }) | ||
| complex: Complex | null; |
There was a problem hiding this comment.
Is it possible to add this to a different entity in a different entity file?
That way we're not muddying up the original tests
| describe("OrmUtils.isPlainObject", () => { | ||
| const isPlainObject = OrmUtils.isPlainObject.bind(OrmUtils); | ||
|
|
||
| it("should return `true` if the Object is made by Object.create(null), {} or new Object().", () => { | ||
| class Foo {} | ||
|
|
||
| expect(isPlainObject(new Object())).to.be.true; | ||
| expect(isPlainObject(Object.create(null))).to.be.true; | ||
| expect(isPlainObject(Object.create(Object.prototype))).to.be.true; | ||
| expect(isPlainObject({ foo: "bar" })).to.be.true; | ||
| expect(isPlainObject({ constructor: "foo" })).to.be.true; | ||
| expect(isPlainObject({ constructor: Foo })).to.be.true; | ||
| expect(isPlainObject({})); | ||
| }); | ||
|
|
||
| it("should return `false` if the Object is not made by Object.create(null), {} or new Object().", () => { | ||
| class Foo {} | ||
|
|
||
| expect(isPlainObject(Object.create({}))).to.be.false; | ||
| expect(isPlainObject(Object.create(Object))).to.be.false; | ||
| expect(isPlainObject(/foo/)).to.be.false; | ||
| expect(isPlainObject(function() {})).to.be.false; | ||
| expect(isPlainObject(() => {})).to.be.false; | ||
| expect(isPlainObject(["foo", "bar"])).to.be.false; | ||
| expect(isPlainObject([])).to.be.false; | ||
| expect(isPlainObject(new Foo())).to.be.false; | ||
| expect(isPlainObject(null)).to.be.false; | ||
| expect(isPlainObject(Math)).to.be.false; | ||
| expect(isPlainObject(Error)).to.be.false; | ||
| expect(isPlainObject(0)).to.be.false; | ||
| expect(isPlainObject(false)).to.be.false; | ||
| expect(isPlainObject(NaN)).to.be.false; | ||
| }); | ||
| }); |
There was a problem hiding this comment.
since we're doing all the tests below - do we need to test isPlainObject if it's just an internal helper? Do the below tests not go through all the possible permutations?
The current mergeDeep implementation tries to recursively copy complex objects like class instances. So, a new implementation of
mergeDeepandisPlainObject(previouslyisObject) is given. This fixes #5096, which demonstrates the problem.As a bonus, this creates tests for these methods which weren't tested before.