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!