Skip to content

Conversation

DemonHa
Copy link
Contributor

@DemonHa DemonHa commented Aug 24, 2025

The current Clone function does not handle recursive (circular) structures. When an object references itself, the function runs into infinite recursion and throws a Maximum call stack size exceeded error.

See issue: #1300

const a = { x: 1 };
a.b = a;

Clone(a); // ❌ Fails with Maximum call stack error

This PR adds support for cloning recursive structures by detecting and handling circular references. Objects that reference themselves (directly or indirectly) can now be cloned safely without causing stack overflows.

@DemonHa DemonHa changed the title feat: support recursive structures feat: Value.Clone support for recursive structures Aug 24, 2025
@sinclairzx81
Copy link
Owner

@DemonHa Hi, just to give an update here.

So, I spent some time with this during the 1.0 version update, but I am concerned about the performance hit caused by <ref *> tracking. As a reference, if we compare the performance of Value.Clone vs structuredClone, there is an order of magnitude difference which is largely a consequence of structuredClone tracking recursive ref, transferrable and other structures.

import Value from 'typebox/value'

const A = { x: 1, y: 2, z: 3 }

console.time()
for(let i = 0; i < 1_000_000; i++) Value.Clone(A)     // 348ms
console.timeEnd()

console.time()
for(let i = 0; i < 1_000_000; i++) structuredClone(A) // 3041ms !!!
console.timeEnd()

I should provision that the Clone function in TypeBox is somewhat performance sensitive, so degrading performance by 10x just to support a relatively uncommon data structure is not going to be a good trade off, especially as <ref *> cyclics cannot be represented in JSON (the primary data format this library was written for)

I think I am going to close out this PR as a result, sorry about this. I think at this stage, I'm going to put cyclic self referential values in the "out of scope" category, as supporting them for Clone would also imply support for validation, which implies a need to <ref *> track there also. This said, TypeBox does support singular and mutual recursive types, but is limited to recursive values that terminate, not non-terminating cyclic values that require ref tracking (which should generally be avoided in data formats anyway)

So, sorry about this. I may consider this functionality in future, at this stage, the cost of taking this one would impact too much. If you need to clone cyclic values, the built in structuredClone function should work.

Will close this one out.
Thanks again
S

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.

2 participants