Skip to content

[FIX] Fix circular reference error when serializing Observer data#55

Merged
willeastcott merged 6 commits intomainfrom
enum-false
Jan 6, 2026
Merged

[FIX] Fix circular reference error when serializing Observer data#55
willeastcott merged 6 commits intomainfrom
enum-false

Conversation

@willeastcott
Copy link
Contributor

@willeastcott willeastcott commented Jan 5, 2026

Fixes playcanvas/editor#1649

Problem

When editing JSON array attributes in the Editor, ShareDB would fail with:

TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Observer'
    |     property '_parent' -> object with constructor 'Observer'
    --- property '_data' closes the circle

This occurred because internal properties on Events, Observer, and ObserverList classes were enumerable by default. When JSON.stringify was called during ShareDB sync, these properties (which contain circular references like _parent, _additionalEmitters) were included in serialization.

Solution

Made all internal properties non-enumerable using Object.defineProperty:

  • Events: _events, _suspendEvents, _additionalEmitters
  • Observer: _destroyed, _path, _keys, _data, _pathsWithDuplicates, _parent, _parentPath, _parentField, _parentKey, _latestFn, _silent
  • ObserverList: _indexed

Non-enumerable properties are excluded from Object.keys(), for...in loops, and JSON.stringify().

Additional Improvements

  • Added definite assignment assertions (!) for strict TypeScript compatibility
  • Fixed type declarations for _parent and _latestFn to include | null
  • Split test file into events.test.mjs, observer.test.mjs, observer-list.test.mjs
  • Added Mocha globals to ESLint config for test files
  • Added test/ directory to lint script

Testing

Added unit tests verifying:

  • Internal properties don't appear in Object.keys()
  • Circular emitter references don't break JSON.stringify()
  • Nested Observers with parent references can be serialized
  • pathsWithDuplicates option works correctly with Set implementation

@willeastcott willeastcott self-assigned this Jan 5, 2026
@willeastcott willeastcott added the bug Something isn't working label Jan 5, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a circular reference error that occurred when serializing Observer data to JSON during ShareDB synchronization. The solution makes all internal properties non-enumerable across the Events, Observer, and ObserverList classes, preventing them from being included in JSON.stringify() operations.

Key changes:

  • Converted internal property initialization from inline declarations to constructor-based Object.defineProperty calls
  • Changed _pathsWithDuplicates from an object-based lookup to a Set for more idiomatic duplicate checking
  • Added comprehensive unit tests verifying non-enumerable properties and JSON serialization without circular references

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 10 comments.

File Description
test/observer.test.mjs Adds three new test suites verifying that internal properties are non-enumerable and that objects with circular references can be safely serialized to JSON
src/observer.ts Refactors property initialization to use Object.defineProperty for all internal properties, converts _pathsWithDuplicates from object to Set, and removes property initializers from declarations
src/observer-list.ts Makes _indexed property non-enumerable using Object.defineProperty in the constructor
src/events.ts Refactors all three internal properties (_events, _suspendEvents, _additionalEmitters) to be non-enumerable using a consistent initialization pattern

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@kpal81xd kpal81xd left a comment

Choose a reason for hiding this comment

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

Approving with a couple comments

@willeastcott willeastcott merged commit 5ed3cd1 into main Jan 6, 2026
3 checks passed
@willeastcott willeastcott deleted the enum-false branch January 6, 2026 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error while saving changes, when changing the value of an array

2 participants

Comments