Converting GUID’s to Base36 and back again
GUID’s, or Globally Unique Identifiers, are a common sight in URLs, used for identification or referencing specific resources. However, their traditional 32-character hexadecimal format is not the most pleasing to the eye in a URL.
Optimally, a GUID should be converted to a Base-36 string that contains simple alphanumeric characters.
Many solutions opt to use base-64 for URL encoding, but this often results in producing characters (+
, /
, =
) that are not URI-friendly. These characters cause problems as they are reserved characters in URLs and have a specific semantic meaning. This requires additional steps to replace and restore these characters during encoding and decoding, which in turn could present a potential for errors.
The beauty of the base-36 approach is that it only uses characters safe for use in URLs. It removes the need for extra decoding steps or error-prone replacements. Implementing methods in C# for this we get:
using System; using System.Linq; using System.Numerics; public class Program { public static void Main() { Guid originalGuid = Guid.NewGuid(); string base36String = originalGuid.ToBase36String(); Console.WriteLine("Original GUID: "+originalGuid); Console.WriteLine("Base36 String: "+base36String); try { Guid convertedGuid = base36String.FromBase36String(); Console.WriteLine("Converted GUID: "+convertedGuid); } catch (ArgumentException ex) { Console.WriteLine("Error: "+ex.Message); } } } public static class GuidExtensions { private static readonly string _chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; public static string NewGuidToBase36String() { Guid newGuid = Guid.NewGuid(); return newGuid.ToBase36String(); } public static string ToBase36String(this Guid guid) { var bytes = guid.ToByteArray().Reverse().ToArray(); var bigintBytes = new byte[bytes.Length + 1]; bytes.CopyTo(bigintBytes, 0); BigInteger value = new BigInteger(bigintBytes); return ToBase36String(value); } private static string ToBase36String(BigInteger value) { if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), value, "value must be positive."); if (value == 0) return "0"; string result = ""; while (value > 0) { result = _chars[(int)BigInteger.Remainder(value, 36)] + result; value = BigInteger.Divide(value, 36); } return result; } public static Guid FromBase36String(this string base36) { if (base36 == null) throw new ArgumentNullException(nameof(base36)); BigInteger value = FromBase36(base36); byte[] bytes = value.ToByteArray(); // If the byte array is larger than 16 bytes due to the added 0 during conversion to BigInteger, trim it. if (bytes.Length > 16) { bytes = bytes.Take(16).ToArray(); } return new Guid(bytes.Reverse().ToArray()); } private static BigInteger FromBase36(string base36) { base36 = base36.ToUpper(); BigInteger result = new BigInteger(0); BigInteger multiplier = new BigInteger(1); for (int i = base36.Length - 1; i >= 0; i--) { int value = _chars.IndexOf(base36[i]); if (value < 0) throw new ArgumentException("Invalid character in base36 string.", nameof(base36)); result += value * multiplier; multiplier *= 36; } return result; } }
In the encoding method, we’re converting the Guid to a string representation, parsing each hexadecimal character back to an integer, and then aggregating the result into a BigInteger with base-36.
While decoding, the base-36 string is segmented and parsed back to bytes, then to a Guid.
The base-36 encoded string gives you a shorter string than base-64, increasing the readability of your URLs, and avoiding the traps of reserved characters, enhancing the overall usability of your application.
Give it a try the next time you’re handling GUID’s in URLs and enhance not only their aesthetics but also their functionality!