I need to model a composite key type that could have any number of key / value pairs.
I need the key to be convertible into a hashed string value that can be stored in a database for querying. As part of this, I need the key to be able to be recreated at any time, with the possibility the key value pairs will be added in a different order. In my implementation I've figured using a SortedDictionary<string, string> will prevent issues with hashing and ordering.
I also need the type to have sound equality comparison in code.
I don't often need something like this, so I've created the below as a quick approach that could work.
I'm not sure if I'm overcooking this, and whether there's a simpler and/or more performant approach?
public class KeyDictionary
{
public KeyDictionary()
{
Items = new SortedDictionary<string, string>();
}
public SortedDictionary<string, string> Items { get; init; }
}
class CompositeKey
{
private readonly KeyDictionary keyDictionary;
public CompositeKey()
{
keyDictionary = new KeyDictionary();
}
public CompositeKey(string base64)
{
var raw = Base64Decode(base64);
var deserialized = JsonSerializer.Deserialize<KeyDictionary>(raw);
if (deserialized == null)
{
throw new InvalidOperationException($"Attempted to deserialize a key into a sorted dictionary and got null. Key in base64 is {base64}");
}
keyDictionary = deserialized;
}
public void Add(string key, string value)
{
if (keyDictionary.Items.ContainsKey(key))
{
throw new InvalidOperationException($"Key {key} cannot be added more than once");
}
keyDictionary.Items.Add(key, value);
}
private static string Base64Decode(string base64EncodedData)
{
var base64EncodedBytes = Convert.FromBase64String(base64EncodedData);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
}
private static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return Convert.ToBase64String(plainTextBytes);
}
public override string ToString()
{
return Base64Encode(JsonSerializer.Serialize(keyDictionary));
}
public override bool Equals(object? obj)
{
if (obj is not CompositeKey item)
{
return false;
}
return item.ToString() == ToString();
}
public override int GetHashCode()
{
return ToString().GetHashCode();
}
}
In Use
// Creation of a key
var originalKey = new CompositeKey();
originalKey.Add("seasonId", "45045054");
originalKey.Add("matchId", "66724565");
// Hashed value of the key
var hasOfOriginalKey = originalKey.ToString();
Console.WriteLine(hasOfOriginalKey); // eyJJdGVtcyI6eyJhZHZhbnRhZ2VJZCI6IjY2NzI0NTY1Iiwic2Vhc29uSWQiOiI0NTA0NTA1NCJ9fQ==
// De hashing of the key
var dehashedKey = new CompositeKey(hasOfOriginalKey);
Console.WriteLine(originalKey.Equals(dehashedKey)); // True
// Creating the same key independently (with keys in different order).
var remadeKey = new CompositeKey();
remadeKey.Add("matchId", "66724565");
remadeKey.Add("seasonId", "45045054");
Console.WriteLine(originalKey.Equals(remadeKey)); // True
// This should not evaluate as equal to the original key
var differentKey = new CompositeKey();
differentKey.Add("matchId", "7767676");
differentKey.Add("seasonId", "45045054");
Console.WriteLine(originalKey.Equals(differentKey)); // False