Looking at Java UUID's toString() method for inspiration,
public String toString() {
return (digits(mostSigBits >> 32, 8) + "-" +
digits(mostSigBits >> 16, 4) + "-" +
digits(mostSigBits, 4) + "-" +
digits(leastSigBits >> 48, 4) + "-" +
digits(leastSigBits, 12));
}
private static String digits(long val, int digits) {
long hi = 1L << (digits * 4);
return Long.toHexString(hi | (val & (hi - 1))).substring(1);
}
We can do the same using BigInt. This assumes Node 10.8+ (tested with 14.15.5), TypeScript targeting ES2020+, and with this browser compatibility.
Note: If you get "BigInt literals are not available..." wrap all literals ending with n with BigInt instead (e.g., instead of 32n, use BigInt(32)).
export function toUuidString(lsb: bigint, msb: bigint): string {
return `${digits(msb >> 32n, 8n)}-${digits(msb >> 16n, 4n)}-${digits(
msb,
4n
)}-${digits(lsb >> 48n, 4n)}-${digits(lsb, 12n)}`;
}
function digits(val: bigint, ds: bigint): string {
const hi = 1n << (ds * 4n);
return (hi | (val & (hi - 1n))).toString(16).substring(1);
}
And an example test, notice msb/lsb are passed to BigInt as strings,
it('converts UUID from msb/lsb to string', () => {
expect(
toUuidString(
BigInt('-1160168401362026442'),
BigInt('-6694969989912915968')
)
).toEqual('a316b044-0157-1000-efe6-40fc5d2f0036');
});
The final piece is protocol buffers. By default, google-protobuf uses number for 64-bit float and int values, which causes overflow above Number.MAX_VALUE or 253 - 1. To avoid this, use the jstype annotation on 64-bit fields,
message Uuid {
sfixed64 msb = 1 [jstype = JS_STRING];
sfixed64 lsb = 2 [jstype = JS_STRING];
}