Skip to content

fix: handle class instances with private fields#5140

Open
logaretm wants to merge 1 commit intomainfrom
fix/4977-private-properties
Open

fix: handle class instances with private fields#5140
logaretm wants to merge 1 commit intomainfrom
fix/4977-private-properties

Conversation

@logaretm
Copy link
Owner

@logaretm logaretm commented Mar 4, 2026

Summary

  • Fixes class instances with private properties #4977: class instances with private properties (# syntax) now work correctly as form values
  • Added a deepCopy wrapper around klona that detects non-plain objects (class instances) and returns them by reference instead of attempting deep cloning, which would fail on private fields
  • Updated isEqual to use reference equality for class instances instead of trying to traverse their properties
  • All direct klona imports across the codebase replaced with the safe deepCopy wrapper

Test plan

  • Added test: isEqual does not throw on class instances with private properties
  • Added test: isEqual handles class instances nested in plain objects
  • Added test: deepCopy does not throw on class instances with private properties
  • Added test: deepCopy handles class instances nested in arrays
  • Added test: deepCopy still correctly clones plain objects and primitives
  • All existing tests pass (360 tests, 3 pre-existing failures unrelated to this change)

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 4, 2026 06:49
@changeset-bot
Copy link

changeset-bot bot commented Mar 4, 2026

🦋 Changeset detected

Latest commit: ef5a13c

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@netlify
Copy link

netlify bot commented Mar 4, 2026

Deploy Preview for vee-validate-docs canceled.

Name Link
🔨 Latest commit ef5a13c
🔍 Latest deploy log https://app.netlify.com/projects/vee-validate-docs/deploys/69a7d5f86bf70d0007b256c5

@netlify
Copy link

netlify bot commented Mar 4, 2026

Deploy Preview for vee-validate-v5 ready!

Name Link
🔨 Latest commit ef5a13c
🔍 Latest deploy log https://app.netlify.com/projects/vee-validate-v5/deploys/69a7d5f8cad0470008cc93b7
😎 Deploy Preview https://deploy-preview-5140--vee-validate-v5.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

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 issue #4977 where class instances with private fields (# syntax) would cause errors (TypeError: attempted to get private field on non-instance) when used as form values in vee-validate. The root cause is that klona (the deep-cloning library) cannot handle private fields, and Vue's reactivity system similarly triggers this error.

Changes:

  • Added a deepCopy wrapper around klona in utils/common.ts that detects non-plain objects (class instances) and returns them by reference, preventing cloning failures on private fields
  • Updated isEqual in utils/assertions.ts to use reference equality for non-plain objects, avoiding property enumeration that could fail on class instances with private fields
  • Replaced all direct klona imports across five source files (validate.ts, useForm.ts, useFieldArray.ts, useField.ts, Form.ts) with the new safe deepCopy wrapper

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/vee-validate/src/utils/common.ts Core fix: adds deepCopy wrapper and isClassInstance helper
packages/vee-validate/src/utils/assertions.ts Adds isPlainObject guard in isEqual for reference equality on class instances
packages/vee-validate/src/validate.ts Replaces klona as deepCopy import with deepCopy from utils
packages/vee-validate/src/useForm.ts Replaces klona as deepCopy import with deepCopy from utils
packages/vee-validate/src/useFieldArray.ts Replaces klona as deepCopy import with deepCopy from utils
packages/vee-validate/src/useField.ts Replaces klona as deepCopy import with deepCopy from utils
packages/vee-validate/src/Form.ts Replaces klona as deepCopy import with deepCopy from utils
packages/vee-validate/tests/utils/assertions.spec.ts Adds unit tests for new isEqual and deepCopy behavior with class instances
.changeset/fix-4977-private-properties.md Changeset entry for the patch release

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

Comment on lines +32 to +46
// Non-plain objects with [object Object] tag are class instances.
// They may have private fields that cannot be cloned, so return by reference.
if (isClassInstance(value)) {
return value;
}

// For arrays, recursively process each item to protect nested class instances
if (Array.isArray(value)) {
return value.map(item => deepCopy(item)) as T;
}

// For other known types that klona handles (Date, RegExp, Map, Set, etc.), delegate to klona
const tag = Object.prototype.toString.call(value);
if (tag !== '[object Object]') {
return klona(value);
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

The deepCopy function correctly handles class instances with [object Object] toString tag by detecting them via isClassInstance and returning by reference. However, class instances that define a custom Symbol.toStringTag (e.g., get [Symbol.toStringTag]() { return 'MyClass'; }) will have a tag different from [object Object], so isClassInstance returns false for them. They then fall through to klona(value) at line 46, which will still fail if those class instances contain private fields.

While this is an edge case, the inconsistency between deepCopy and isEqual is worth noting: isEqual uses isPlainObject for its guard, which does handle objects with custom Symbol.toStringTag correctly (returns false for them), so isEqual would use reference equality for these instances. But deepCopy would still attempt to clone them via klona, potentially throwing on private fields.

Consider using !isPlainObject(value) as the condition to return by reference in deepCopy as well (for non-array, non-null objects), rather than checking the tag separately, to ensure consistency with how isEqual handles these objects.

Suggested change
// Non-plain objects with [object Object] tag are class instances.
// They may have private fields that cannot be cloned, so return by reference.
if (isClassInstance(value)) {
return value;
}
// For arrays, recursively process each item to protect nested class instances
if (Array.isArray(value)) {
return value.map(item => deepCopy(item)) as T;
}
// For other known types that klona handles (Date, RegExp, Map, Set, etc.), delegate to klona
const tag = Object.prototype.toString.call(value);
if (tag !== '[object Object]') {
return klona(value);
// For arrays, recursively process each item to protect nested class instances
if (Array.isArray(value)) {
return value.map(item => deepCopy(item)) as T;
}
// Non-plain objects (including class instances and other built-ins) are returned by reference.
// This avoids attempting to clone instances that may contain private fields and aligns with isEqual,
// which also treats non-plain objects by reference.
if (!isPlainObject(value)) {
return value;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

class instances with private properties

2 participants