Skip to content

fix: use locale-independent comparison for deterministic serialization (#177)#181

Open
guoyangzhen wants to merge 2 commits into
unjs:mainfrom
guoyangzhen:fix-locale-sort
Open

fix: use locale-independent comparison for deterministic serialization (#177)#181
guoyangzhen wants to merge 2 commits into
unjs:mainfrom
guoyangzhen:fix-locale-sort

Conversation

@guoyangzhen

@guoyangzhen guoyangzhen commented Mar 23, 2026

Copy link
Copy Markdown

🔗 Linked issue

Closes #177

📝 Description

serialize() uses String.localeCompare() for sorting object keys, which produces different results depending on the system locale.

In Slovak (sk-SK), the digraph ch is treated as a distinct letter after h. So {checkIn: 'foo', destination: 'bar'} serializes as {destination:'bar',checkIn:'foo'} in Slovak, but {checkIn:'foo',destination:'bar'} in English. This causes hash() to produce different values for the same input on different machines.

The same issue affects Set sorting and the compare() fallback path.

Fix

Replace all localeCompare calls with code-point comparison (a < b ? -1 : a > b ? 1 : 0) which is deterministic across all locales:

  1. compare() for stringsa.localeCompare(b)a < b ? -1 : a > b ? 1 : 0
  2. compare() fallbackString.prototype.localeCompare.call(...) → code-point compare on serialized values
  3. Object.keys() sorta.localeCompare(b) → code-point compare

Code-point comparison uses UTF-16 code unit values, which is stable and locale-independent.

Impact

Without this fix, hash() produces different values for the same input depending on the system's LANG/LC_ALL setting. This breaks caching, content deduplication, and any system that relies on hash stability across environments.

Summary by CodeRabbit

  • Refactor
    • Optimized internal string comparison and object key sorting to use direct comparisons instead of locale-based calls.
    • Added caching for serialized values during mixed-type comparisons to reduce repeated work and improve performance.

@coderabbitai

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a9dedc6-9187-49ef-9581-72c55054b2fb

📥 Commits

Reviewing files that changed from the base of the PR and between 6ca2854 and f596bf9.

📒 Files selected for processing (1)
  • src/serialize.ts
✅ Files skipped from review due to trivial changes (1)
  • src/serialize.ts

📝 Walkthrough

Walkthrough

Replaced locale-dependent String.prototype.localeCompare usage with explicit </> ternary comparisons in serialization comparison logic and object key sorting to produce deterministic, locale-independent ordering.

Changes

Cohort / File(s) Summary
Serialization Logic
src/serialize.ts
Replaced localeCompare with direct </>-based comparator returning -1/0/1 in Serializer.compare for string-string and mixed-type comparisons (now caches serialized strings sa/sb), and in serializeObject key sorting to ensure deterministic ordering across locales.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 I hop through keys both near and far,
Replacing locales with a simple bar.
Less fuss, more order, neat and spry,
Strings line up, no quirks deny —
A tidy hash beneath the sky.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: replacing locale-dependent comparison with locale-independent comparison for deterministic serialization.
Linked Issues check ✅ Passed All coding requirements from issue #177 are met: localeCompare replaced with deterministic code-point comparison in compare() and serializeObject key sorting.
Out of Scope Changes check ✅ Passed All changes are focused on replacing locale-dependent string comparison with locale-independent comparison as specified in issue #177, with no out-of-scope modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/serialize.ts`:
- Line 42: Parenthesize the nested ternary expressions to satisfy
unicorn/no-nested-ternary: replace instances like "return a < b ? -1 : a > b ? 1
: 0;" with a parenthesized nested branch, e.g. "return a < b ? -1 : (a > b ? 1 :
0);". Apply the same pattern to the other occurrences with the same nested
ternary structure so each outer ternary's alternate branch is wrapped in
parentheses.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e6b84451-d8af-4ac1-b06d-702fb8eb701d

📥 Commits

Reviewing files that changed from the base of the PR and between 7e603d4 and 6ca2854.

📒 Files selected for processing (1)
  • src/serialize.ts

Comment thread src/serialize.ts Outdated
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.

Locale-dependant string comparison causes hash to produce inconsistent results

1 participant