Hello! First, thank you for maintaining SuperJSON, i am using it every single project.
Issue Description
I've been investigating performance while deserializing large datasets and noticed that when using SuperJSON.parse(), there's a redundant deep copy operation that significantly impacts performance.
Technical Analysis
Looking at the current implementation:
parse() method (src/index.ts:84) calls JSON.parse(string) which creates a brand new object
- This new object is then passed to
deserialize()
deserialize() (src/index.ts:64) immediately calls copy(json) to create another deep copy to not mutate the input itself
The flow is:
parse(string) → JSON.parse(string) → new object → deserialize(new object) → copy(new object)
Since JSON.parse() already creates a completely new object with no external references, the subsequent copy() operation is redundant in this specific flow.
Performance Impact
In our benchmarks with large datasets (arrays of complex objects), the copy() operation accounts for approximately 80% of the total deserialization time. This is particularly noticeable when deserializing arrays with thousands of items. I can go in more details in the specific case i am running into if needed.
Why the Copy Exists
I understand the copy is necessary when deserialize() is called directly with an existing object to prevent mutations:
const payload = { json: existingObject, meta: {...} };
const result = SuperJSON.deserialize(payload);
// Without copy, existingObject would be mutated by applyValueAnnotations/applyReferentialEqualityAnnotations
Proposed Solution
Would you consider adding an optional flag to deserialize() to skip the copy when it's safe to do so?
deserialize<T = unknown>(payload: SuperJSONResult, options?: { skipCopy?: boolean }): T {
const { json, meta } = payload;
let result: T = options?.skipCopy ? json : copy(json) as any;
if (meta?.values) {
result = applyValueAnnotations(result, meta.values, this);
}
// ... rest of the method
}
parse<T = unknown>(string: string): T {
return this.deserialize(JSON.parse(string), { skipCopy: true });
}
This would:
- Maintain backward compatibility (default behavior unchanged)
- Allow
parse() to skip the redundant copy
- Let advanced users optimize performance when they know the input is safe to mutate
Alternative Approaches
If adding a parameter isn't desirable, an internal method like _deserializeInPlace() could work, though the flag approach seems cleaner and could benefit other use cases where users know their input is safe to mutate.
Thank you for considering this optimization. I'd be happy to submit a PR if you think this approach makes sense!
Hello! First, thank you for maintaining SuperJSON, i am using it every single project.
Issue Description
I've been investigating performance while deserializing large datasets and noticed that when using
SuperJSON.parse(), there's a redundant deep copy operation that significantly impacts performance.Technical Analysis
Looking at the current implementation:
parse()method (src/index.ts:84) callsJSON.parse(string)which creates a brand new objectdeserialize()deserialize()(src/index.ts:64) immediately callscopy(json)to create another deep copy to not mutate the input itselfThe flow is:
Since
JSON.parse()already creates a completely new object with no external references, the subsequentcopy()operation is redundant in this specific flow.Performance Impact
In our benchmarks with large datasets (arrays of complex objects), the
copy()operation accounts for approximately 80% of the total deserialization time. This is particularly noticeable when deserializing arrays with thousands of items. I can go in more details in the specific case i am running into if needed.Why the Copy Exists
I understand the copy is necessary when
deserialize()is called directly with an existing object to prevent mutations:Proposed Solution
Would you consider adding an optional flag to
deserialize()to skip the copy when it's safe to do so?This would:
parse()to skip the redundant copyAlternative Approaches
If adding a parameter isn't desirable, an internal method like
_deserializeInPlace()could work, though the flag approach seems cleaner and could benefit other use cases where users know their input is safe to mutate.Thank you for considering this optimization. I'd be happy to submit a PR if you think this approach makes sense!