How it works
Two maps can contain the same key-value pairs while being constructed in different orders. A stable export may need one predictable textual order for review, snapshots, or reproducible local fixtures. The sample copies the small map into a SortedDictionary using ordinal key comparison before serializing it.
Both source dictionaries therefore produce a first and b second, and their serialized strings compare equal. The contract is deliberately narrow: string keys and integer values in one object. An explicit ordering rule removes one source of incidental differences without pretending to solve every possible representation issue in JSON.
JSON object member order generally should not determine the meaning of a consumer's domain model. A stable textual order can still be useful for human diffs and tests. Keep semantic equality separate from byte-for-byte equality, and choose which comparison is appropriate for each task.
This helper is not a universal canonical JSON implementation. Nested objects, numeric formatting, escaping choices, duplicate property names, and other representation details need additional rules in a genuine canonicalization scheme. Do not use this tiny example as a cryptographic signing contract merely because its two local outputs match.
Practice by adding uppercase and lowercase keys and predicting ordinal order. Add a second map with a changed value and verify that stable ordering does not conceal the semantic difference. Then extend the bounded model to contain a nested dictionary and decide explicitly whether and how nested keys should also be sorted.
Finally, write a test that constructs equivalent maps in several insertion orders and compares the stable export. Include an empty map and a map whose value differs. The useful invariant is reproducibility under the application's stated shape and options; documenting that limited invariant is more reliable than naming a helper CanonicalJson without specifying a complete standard.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
var first = new Dictionary<string, int> { ["b"] = 2, ["a"] = 1 };
var second = new Dictionary<string, int> { ["a"] = 1, ["b"] = 2 };
string Stable(Dictionary<string, int> values) => JsonSerializer.Serialize(
new SortedDictionary<string, int>(values, StringComparer.Ordinal));
Console.WriteLine(Stable(first));
Console.WriteLine(Stable(first) == Stable(second));
Expected output
{"a":1,"b":2}
TrueSort keys with an explicit comparer before serializing this bounded map shape.