Frontmatter
| id | 8335 |
| title | Fix toJSON mixin shadowing and implement dynamic mixin serialization |
| state | Closed |
| labels | bugaicore |
| assignees | tobiu |
| createdAt | Jan 5, 2026, 2:26 PM |
| updatedAt | Jan 5, 2026, 2:34 PM |
| githubUrl | https://github.com/neomjs/neo/issues/8335 |
| author | tobiu |
| commentsCount | 1 |
| parentIssue | 8200 |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Jan 5, 2026, 2:34 PM |
Fix toJSON mixin shadowing and implement dynamic mixin serialization
tobiu assigned to @tobiu on Jan 5, 2026, 2:26 PM
tobiu added parent issue #8200 on Jan 5, 2026, 2:27 PM

tobiu
Jan 5, 2026, 2:34 PM
Input from Gemini 3 Pro:
✦ I have implemented the fix for the
toJSONmixin shadowing and recursion issue.Changes:
src/Neo.mjs: Added'toJSON'to theignoreMixinarray. This preventsNeo.setupClassfrom copying thetoJSONmethod from a mixin to the target class's prototype, ensuring the class's own inheritance chain (or own implementation) is preserved.src/core/Base.mjs:
- Implemented
getMixins()helper method to recursively extract mixin classes.- Updated
toJSON()to:
- Include a recursion guard (
me.__inToJSON) to prevent infinite loops when mixins callsuper.toJSON().- Dynamically iterate over all applied mixins using
getMixins().- Invoke
toJSONon each mixin (if present) usingcall(me)and merge the results into the serialized object.Verification: A new unit test
test/playwright/unit/core/MixinSerialization.spec.mjswas created and passed, confirming:
- Component's own
toJSONlogic is preserved.- Mixin's
toJSONlogic is correctly aggregated.- Base properties are present.
- No method shadowing occurs on the prototype.
The changes have been committed.
tobiu closed this issue on Jan 5, 2026, 2:34 PM
Problem: When
Neo.setupClassapplies mixins, it copies thetoJSONmethod from the mixin (e.g.,Neo.core.Observable) onto the target class's prototype. If the target class (e.g.,Neo.component.Abstract) relies on inheritance fortoJSONor implements its own, the mixin's version overwrites it. This results in missing data (e.g., component-specific fields) in the serialized output.Challenge: The proposed fix is to have
Neo.core.Base.prototype.toJSONdynamically invoketoJSONon all applied mixins to aggregate their data. However, many mixins (likeObservable) extendBaseand callsuper.toJSON()in their implementation. IfBase.toJSONcallsmixin.toJSON.call(this), and that mixin callssuper.toJSON()(which resolves toBase.toJSON), it triggers infinite recursion.Solution:
src/Neo.mjsto add'toJSON'to theignoreMixinlist. This ensures the class's owntoJSON(or its inherited chain) is preserved.src/core/Base.mjs:this.__inToJSON) totoJSON.super), return only the base instance data to break the loop.this.mixinsand invoketoJSONon each, merging the results.