-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow comparing special/tagged objects #120
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Handling special (built-in) objects | ||
|
||
The `createCustomEqual` uses `@@toStringTag` to decide which well-known comparator to call. However, some values might not fit either of them. For example `WeakMap` is compared intentionally. Additionally, it's possible that new built-in objects are added to the language. Third parties might also add `@@toStringTag` to their objects. | ||
|
||
For example the [TC39 Temporal spec](https://tc39.es/proposal-temporal/docs/) requires `@@toStringTag` being implemetend and the polyfills do that. Passing `areObjectsEqual` will not work in that scenario. Instead you'll have to register a handler for the tag instead. | ||
|
||
```ts | ||
import { createCustomEqual } from 'fast-equals'; | ||
import type { TypeEqualityComparator } from 'fast-equals'; | ||
|
||
const areZonedDateTimesEqual: TypeEqualityComparator<unknown, undefined> = ( | ||
a, | ||
b, | ||
) => a instanceof Temporal.ZonedDateTime | ||
&& b instanceof Temporal.ZonedDateTime | ||
&& a.equals(b); | ||
|
||
const isSpecialObjectEqual = createCustomEqual({ | ||
createCustomConfig: () => ({ | ||
unknownTagComparators: { | ||
"Temporal.ZonedDateTime", areZonedDateTimesEqual, | ||
} | ||
}), | ||
}); | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,6 +38,7 @@ const { assign } = Object; | |
const getTag = Object.prototype.toString.call.bind( | ||
Object.prototype.toString, | ||
) as (a: object) => string; | ||
const getShortTag = (a: object): string | undefined => a != null ? (a as any)[Symbol.toStringTag] : undefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue: Since the use of |
||
|
||
interface CreateIsEqualOptions<Meta> { | ||
circular: boolean; | ||
|
@@ -59,6 +60,7 @@ export function createEqualityComparator<Meta>({ | |
areRegExpsEqual, | ||
areSetsEqual, | ||
areTypedArraysEqual, | ||
unknownTagComparators, | ||
}: ComparatorConfig<Meta>): EqualityComparator<Meta> { | ||
/** | ||
* compare the value of the two objects and return true if they are equivalent in values | ||
|
@@ -194,7 +196,8 @@ export function createEqualityComparator<Meta>({ | |
// equality is (`Error`, etc.), the subjective decision is to be conservative and strictly compare. | ||
// In all cases, these decisions should be reevaluated based on changes to the language and | ||
// common development practices. | ||
return false; | ||
const unknownTagComparator = unknownTagComparators[getShortTag(a) ?? tag]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Request: I know this is a bit more concise, but I request that the explicit if (unknownTagComparator) {
return unknownTagComparator(a, b, state);
}
// Massive comment explaining ultimate fallback
...
return false; It keeps the code comments applicable, and also allows for smaller diffs in the future if it was preferred to be bumped in priority instead of the ultimate fallback. |
||
return unknownTagComparator ? unknownTagComparator(a, b, state) : false; | ||
}; | ||
} | ||
|
||
|
@@ -225,6 +228,7 @@ export function createEqualityComparatorConfig<Meta>({ | |
areTypedArraysEqual: strict | ||
? areObjectsEqualStrictDefault | ||
: areTypedArraysEqual, | ||
unknownTagComparators: {}, | ||
}; | ||
|
||
if (createCustomConfig) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recommendation: Change to a shared callback
areUnhandledObjectsEqual
which handles all unhandled tags.In the description you mention that you wanted to go with an object of keys to avoid potential refactor pitfalls with version changes, but in reality the refactor work would still be the same. There really isn't a scale issue here since practical usage doesn't involve having a ton of these custom tags for comparison, and it would also allow for an escape hatch if someone wanted to include known-but-unhandled object types in their comparison, such as
Promise
,WeakMap
/WeakSet
, etc.