Skip to content

feat: add deserializeComplexTypes option for Date round-tripping#208

Closed
phusi319 wants to merge 1 commit intosindresorhus:mainfrom
phusi319:feat/support-dates
Closed

feat: add deserializeComplexTypes option for Date round-tripping#208
phusi319 wants to merge 1 commit intosindresorhus:mainfrom
phusi319:feat/support-dates

Conversation

@phusi319
Copy link
Copy Markdown

Closes sindresorhus/electron-store#18

Summary

Adds a new deserializeComplexTypes option that preserves Date objects 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?

  • No separate metadata tracking type info is stored inline, so nested objects, arrays, and dot-prop access all work automatically without fragile path-tracking
  • Collision-safe the reviver only matches objects with exactly two keys (___BEGIN___COMMAND_DONE_MARKER___$LASTEXITCODEtype and ___BEGIN___COMMAND_DONE_MARKER___$LASTEXITCODEvalue) and a known type string. Objects with extra keys or unknown types pass through unchanged
  • Backward compatible opt-in via deserializeComplexTypes: true. Existing configs are unaffected
  • Extensible new types (Map, Set, RegExp, etc.) can be added by extending the replacer/reviver without changing the format

Design decisions

  • Opt-in avoids breaking existing configs. Users explicitly enable the feature
  • Ignored with custom serialize/deserialize if users provide their own serialization, they control the format
  • Uses this[key] in replacer accesses the raw Date before .toJSON() converts it to a string

What's included

  • deserializeComplexTypes option in types and implementation
  • JSON replacer (complexTypeReplacer) and reviver (complexTypeReviver)
  • Enhanced #configureSerialization with clean fallback logic
  • Comprehensive test suite (test/complex-types.ts):
    • Basic Date round-tripping
    • Dot-notation keys
    • Nested objects with multiple Date fields
    • Arrays (pure Date and mixed)
    • Deeply nested structures (3+ levels)
    • Invalid Date handling
    • False positive prevention (extra keys, missing keys, unknown types)
    • Raw JSON file format verification
    • .store getter/setter
    • Encryption compatibility
    • Custom serialize/deserialize override behavior
    • accessPropertiesByDotNotation: false compatibility
  • README documentation with example
  • Full JSDoc with example in types.ts

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
Copy link
Copy Markdown

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

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 deserializeComplexTypes option in types and README docs.
  • Implements JSON replacer/reviver logic for tagging and reviving Date values.
  • 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.

Comment thread source/index.ts
Comment on lines +82 to +85
const rawValue = this[key];
if (rawValue instanceof Date) {
return {[COMPLEX_TYPE_TAG]: 'Date', [COMPLEX_VALUE_TAG]: rawValue.toISOString()};
}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment thread source/types.ts
Comment on lines +309 to +314
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"}`.

Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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.

Copilot uses AI. Check for mistakes.
Comment thread test/complex-types.ts
Comment on lines +164 to +170
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());
});
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
@sindresorhus
Copy link
Copy Markdown
Owner

Thanks, but I already declined this in #154 (comment)

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.

Support dates

3 participants