patchbayCanonicalJson function
Encodes value as JSON with every object's keys in sorted order.
Two catalogs that declare the same thing must produce the same bytes, and map iteration order is an implementation detail of whatever built the descriptor — so it is sorted away here rather than depended on.
Non-string keys are coerced with toString instead of throwing: the digest
runs while a response is being assembled, and a catalog that cannot be JSON
encoded already fails at the transport with a message about that. A digest
must never be the thing that takes a host down.
Implementation
String patchbayCanonicalJson(Object? value) => jsonEncode(_canonical(value));