I was able to figure this out after being pointed in the right direction by Mike Christensen in the comments.
All of this code is probably overkill, and somethings are probably inefficient because I was doing it as fast as I could. But here it is...
I started with the same extend class as in the question (I modified it a little, because I'm still not sure how to implement the generics).
public class SerializableDictionary : Dictionary<string, object>
{
public static explicit operator SerializableDictionary(String serialized)
{
return JsonConvert.DeserializeObject<SerializableDictionary>(serialized);
}
public static explicit operator String(SerializableDictionary dict)
{
return JsonConvert.SerializeObject(dict);
}
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
I next had to create a custom SqlType for NHibernate. This class had to implement IUserType, which came with a bunch methods that had to be implemented (I'm not sure I implemented these the best way possible). This class now allows NHibernate to use the NullSafeSet to set the model property with the string from the database. There I was able to do the deserialization.
public class SerializableDictionaryType : IUserType
{
public new bool Equals(object x, object y)
{
return x != null && x.Equals(y);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
string dbString = (string) NHibernateUtil.String.NullSafeGet(rs, names);
SerializableDictionary dict = JsonConvert.DeserializeObject<SerializableDictionary>(dbString);
return dict;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value == null)
{
NHibernateUtil.String.NullSafeSet(cmd, null, index);
return;
}
value = value.ToString();
NHibernateUtil.String.NullSafeSet(cmd, value, index);
}
public object DeepCopy(object value)
{
if (value == null) return null;
SerializableDictionary newDict = new SerializableDictionary();
foreach (KeyValuePair<string, object> item in (SerializableDictionary)value)
{
newDict.Add(item.Key, item.Value);
}
return newDict;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return JsonConvert.DeserializeObject<SerializableDictionary>(cached.ToString());
}
public object Disassemble(object value)
{
return JsonConvert.SerializeObject(value);
}
public SqlType[] SqlTypes
{
get
{
SqlType[] types = new SqlType[1];
types[0] = new SqlType(DbType.String);
return types;
}
}
public Type ReturnedType
{
get { return typeof (SerializableDictionary); }
}
public bool IsMutable
{
get { return false; }
}
}
Then on the model itself I had to put the following:
[Property(ColumnType = "MyNamespace.SerializableDictionaryType, MyNamespace")]
public SerializableDictionary UserData { get; set; }
This ColumnType refers to the custom type mapper that I created. If you notice I had to use the full namespace and class along with the assembly it's located in.
With all of this setup it works like a charm. I'm planning on optimizing things to make it look nicer and would still like to find a way to deal with generics instead of being stuck with:
<string, object>
I'll update the answer with anything I find.