feat: add deserializeComplexTypes option for Date round-tripping#208
feat: add deserializeComplexTypes option for Date round-tripping#208phusi319 wants to merge 1 commit intosindresorhus:mainfrom
Conversation
Add a new deserializeComplexTypes option that preserves Date objects through JSON serialization and deserialization. When enabled: - Date objects are tagged during serialization using a wrapper format - Tagged objects are restored to Date instances during deserialization - Works with nested objects, arrays, dot-notation, and encryption - Collision-safe: only matches objects with exactly the two tag keys The reviver strictly validates the tag format (exact key count + known type) to prevent false positives. The option is ignored when custom serialize or deserialize functions are provided. Closes sindresorhus/electron-store#18
There was a problem hiding this comment.
Pull request overview
Adds an opt-in deserializeComplexTypes option to round-trip Date instances through JSON serialization/deserialization in Conf, along with documentation and a dedicated test suite.
Changes:
- Introduces
deserializeComplexTypesoption in types and README docs. - Implements JSON
replacer/reviverlogic for tagging and revivingDatevalues. - Adds comprehensive tests covering nested structures, arrays, dot-notation, encryption, and custom (de)serialization overrides.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
source/index.ts |
Adds complexTypeReplacer/complexTypeReviver and hooks them into serialization when deserializeComplexTypes is enabled. |
source/types.ts |
Documents the new option in Options with JSDoc and examples. |
readme.md |
Documents deserializeComplexTypes usage and behavior. |
test/complex-types.ts |
Adds a new test suite validating Date round-tripping and related edge cases. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const rawValue = this[key]; | ||
| if (rawValue instanceof Date) { | ||
| return {[COMPLEX_TYPE_TAG]: 'Date', [COMPLEX_VALUE_TAG]: rawValue.toISOString()}; | ||
| } |
There was a problem hiding this comment.
rawValue.toISOString() will throw a RangeError for invalid Date instances (e.g. new Date('not-a-date')), so enabling deserializeComplexTypes will currently make .set() fail for invalid dates. Handle invalid dates explicitly (for example, tag them with a sentinel and revive to new Date(NaN), or store the original string and revive from that) so serialization doesn’t throw.
| Preserve non-JSON types like `Date` through serialization and deserialization. | ||
|
|
||
| When enabled, `Date` objects are tagged during serialization so they can be restored as proper `Date` instances when read back. This allows round-tripping of `Date` values. | ||
|
|
||
| Uses a tagged wrapper format in the JSON: `{"$$type": "Date", "$$value": "2024-01-01T00:00:00.000Z"}`. | ||
|
|
There was a problem hiding this comment.
The docs describe preserving “non-JSON types like Date”, but the current implementation/reviver only supports Date. Consider explicitly documenting that only Date is supported today (and potentially list future candidates) to avoid users assuming other complex types will also round-trip.
| Preserve non-JSON types like `Date` through serialization and deserialization. | |
| When enabled, `Date` objects are tagged during serialization so they can be restored as proper `Date` instances when read back. This allows round-tripping of `Date` values. | |
| Uses a tagged wrapper format in the JSON: `{"$$type": "Date", "$$value": "2024-01-01T00:00:00.000Z"}`. | |
| Preserve `Date` instances (and only `Date`) through serialization and deserialization. | |
| When enabled, `Date` objects are tagged during serialization so they can be restored as proper `Date` instances when read back. This allows round-tripping of `Date` values. | |
| Uses a tagged wrapper format in the JSON for `Date` values: `{"$$type": "Date", "$$value": "2024-01-01T00:00:00.000Z"}`. | |
| Other non-JSON types (such as `Map`, `Set`, or `BigInt`) are not currently preserved by this option and will not round-trip as their original types. |
| it('handles Date.now() timestamps', () => { | ||
| const date = new Date(); | ||
| config.set('now', date); | ||
| const result = config.get('now'); | ||
| assert.ok(result instanceof Date); | ||
| assert.strictEqual((result as Date).getTime(), date.getTime()); | ||
| }); |
There was a problem hiding this comment.
Test title says it “handles Date.now() timestamps” but the test constructs a Date with new Date() rather than using Date.now()/a numeric timestamp. Either update the title to match what’s being tested or adjust the test to actually cover new Date(Date.now())/timestamp-based construction.
|
Thanks, but I already declined this in #154 (comment) |
Closes sindresorhus/electron-store#18
Summary
Adds a new
deserializeComplexTypesoption that preservesDateobjects through JSON serialization/deserialization. This enables:`js
const config = new Conf({
projectName: 'foo',
deserializeComplexTypes: true
});
config.set('timestamp', new Date());
config.get('timestamp') instanceof Date;
//=> true
`
Approach
Uses JSON's built-in replacer/reviver mechanism (as suggested in the original issue) with a tagged wrapper format:
json { "timestamp": { "BEGIN___COMMAND_DONE_MARKER$LASTEXITCODEtype": "Date", "BEGIN___COMMAND_DONE_MARKER$LASTEXITCODEvalue": "2024-06-15T12:00:00.000Z" } }Why this approach?
___BEGIN___COMMAND_DONE_MARKER___$LASTEXITCODEtypeand___BEGIN___COMMAND_DONE_MARKER___$LASTEXITCODEvalue) and a known type string. Objects with extra keys or unknown types pass through unchangeddeserializeComplexTypes: true. Existing configs are unaffectedDesign decisions
this[key]in replacer accesses the raw Date before.toJSON()converts it to a stringWhat's included
deserializeComplexTypesoption in types and implementationcomplexTypeReplacer) and reviver (complexTypeReviver)#configureSerializationwith clean fallback logictest/complex-types.ts):.storegetter/setteraccessPropertiesByDotNotation: falsecompatibility