feat: add serialization support for Set, Map, Date, RegExp, and BigInt via ISerializer contract#1136
Conversation
…t via __type pattern - Add ISerializer contract interface for custom type serialization - Add built-in serializers: DateSerializer, SetSerializer, MapSerializer, RegExpSerializer, BigIntSerializer - Update JsonAdapter to use serializer-based replacer/reviver (replaces legacy regex date parsing) - Export all serializer types for consumer extensibility - Add comprehensive tests for all built-in types, nested types, and custom serializers Co-authored-by: Belphemur <[email protected]>
…port Co-authored-by: Belphemur <[email protected]>
stefnotch
left a comment
There was a problem hiding this comment.
Doesn't this break the roundtripping guarantee? As in, if I have something that is valid to serialize, then the following should always hold true: deserialize(serialize(x)) == x
But if I pass in an object that already has a __type and __value, then this seems to be broken? At least I don't see any place in the code where those things get escaped.
As in, I think if I do deserialize(serialize({ "__type": "RegExp", "__value": { "source": "hello\\s+world", "flags": "gi" } }) then I'd suddenly get a regular expression back.
Correctly implementing it requires escaping https://stackoverflow.com/questions/29085197/how-do-you-json-stringify-an-es6-map/73155667#73155667
|
/gemini review |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #1136 +/- ##
===========================================
+ Coverage 99.60% 99.66% +0.05%
===========================================
Files 12 13 +1
Lines 1531 1770 +239
Branches 226 362 +136
===========================================
+ Hits 1525 1764 +239
Misses 6 6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR adds serialization support for JavaScript types that don't survive JSON.stringify natively: Set, Map, RegExp, and BigInt. It also improves the existing Date serialization by replacing the previous regex-heuristic approach with a pluggable ISerializer interface and a __type/__value envelope pattern. This is a breaking change for existing databases that stored Date values as bare ISO strings.
Changes:
- Adds
ISerializerinterface and five built-in serializers (DateSerializer,SetSerializer,MapSerializer,RegExpSerializer,BigIntSerializer) withdefaultSerializersarray - Rewrites
JsonAdapter's replacer/reviver to use the new serializer chain instead of a regex-based date reviver - Exports new types and classes from the package entry point (
src/JsonDB.ts)
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
src/adapter/data/ISerializer.ts |
New interface defining the serializer contract (type, serialize, deserialize, test) |
src/adapter/data/Serializers.ts |
Five concrete serializer implementations + defaultSerializers export |
src/adapter/data/JsonAdapter.ts |
Rewritten replacer/reviver using the serializer chain; new serializers constructor parameter |
src/JsonDB.ts |
Re-exports ISerializer, serializer classes, and defaultSerializers for consumers |
test/adapter/adapters.test.ts |
Integration tests for new serializer behavior in JsonAdapter |
test/adapter/Serializers.test.ts |
Unit tests for each serializer class and defaultSerializers |
|
@copilot apply changes based on the comments in this thread Add a AddCustomSerializer method to the Config to simplify adding new serializer for user of the lib. Update the readme too. Also check this comment and see if we can fix the issue |
…er data Co-authored-by: Belphemur <[email protected]>
All changes applied in c33e9aa:
|
|
@all-contributors add @stefnotch for code |
|
I've put up a pull request to add @stefnotch! 🎉 |
|
@stefnotch thank you for the review and the great answer on stack overflow. |
There was a problem hiding this comment.
Code Review
This pull request introduces a robust and extensible serialization mechanism for Set, Map, Date, RegExp, and BigInt types, effectively addressing the issue of these types not being correctly handled by JSON.stringify. The implementation utilizes a __type/__value envelope pattern with a pluggable ISerializer interface, allowing for custom type serialization. The JsonAdapter has been refactored to integrate this new system, and the Config class now provides a convenient addSerializer() method for registering custom serializers. Comprehensive tests have been added to validate the new functionality, covering various scenarios including edge cases and the __type escaping mechanism. The README.md has been updated to reflect these significant changes, providing clear documentation and examples for both built-in and custom serializers. Overall, this is a well-implemented and thoroughly tested feature.
|
@copilot inside the JsonAdapter, let's use a Map of Serializer that is based on the type key for faster deserialization. |
Co-authored-by: Belphemur <[email protected]>
Done in bb0318c. The constructor now builds a The |
# [2.6.0](v2.5.0...v2.6.0) (2026-03-10) ### Features * add serialization support for Set, Map, Date, RegExp, and BigInt via ISerializer contract ([#1136](#1136)) ([ac65a44](ac65a44))
|
🎉 This PR is included in version 2.6.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Set,Map,RegExp, andBigIntdon't surviveJSON.stringify— Sets/Maps become{}, BigInt throws. Dates worked via a regex heuristic that could false-positive on ISO-looking strings. This adds a__type/__valueenvelope pattern with a pluggableISerializerinterface.Changes
ISerializercontract (src/adapter/data/ISerializer.ts) —type,serialize(),deserialize(),test()methods for custom type supportsrc/adapter/data/Serializers.ts) —DateSerializer,SetSerializer,MapSerializer,RegExpSerializer,BigIntSerializerwithdefaultSerializersexported as a frozen (immutable) arrayJsonAdapterrewrite — replaced regex-based date reviver with serializer-drivenreplacer/reviver. Usesthis[key]in the replacer to access raw values before.toJSON()runs (needed for Date). The legacyparseDatesparameter has been removed — Date handling is now managed entirely through the serializer system. The reviver uses aMap<string, ISerializer>keyed by type for O(1) deserialization lookup__typeescaping — user data that naturally contains__typeproperties is automatically escaped during serialization and unescaped during deserialization, preventing false type matchingConfig.addSerializer()— new method onConfigto easily register custom serializers without manually constructing adaptersISerializer, all serializer classes, anddefaultSerializersexported from package entry pointConfig.addSerializer()anddefaultSerializersspread patternSerialized format
{ "__type": "Set", "__value": [1, 2, 3] } { "__type": "Map", "__value": [["key", "value"]] } { "__type": "Date", "__value": "2023-01-01T00:00:00.000Z" } { "__type": "RegExp", "__value": { "source": "^test$", "flags": "gi" } } { "__type": "BigInt", "__value": "9007199254740993" }Consumer extensibility
Or with full control via
defaultSerializers:Breaking changes
{ "__type": "Date", "__value": "..." }instead of bare ISO strings. Existing databases with dates stored as plain ISO strings will read them back as strings, not Date objects.parseDatesparameter has been removed from bothConfigandJsonAdapterconstructors. Date handling is now controlled by the presence ofDateSerializerin the serializers list.Original prompt
🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.