I'm trying to serialize this type of object using protobuf-net:
[ProtoContract]
public class RedisDataObject
{
[ProtoMember(1)]
public string DataHash;
[ProtoMember(2, DynamicType = true)]
public Dictionary<ContextActions, List<Tuple<string, List<object>>>> Value;
}
[Serializable]
public enum ContextActions
{
Insert,
Update,
Delete
}
I'm using List<object> because I'm storing there different class instances of other classes I have in my code.
But I'm getting this error message:
Unable to resolve a suitable Add method for System.Collections.Generic.Dictionary...
This is clearly because of the dictionary, but I couldn't find a solution how to resolve this issue.
Your basic problem is that DynamicType = true applies to that specific property only, and serializes type information for the value of that specific property only. It doesn't apply recursively to any of the properties contained by that object. However, your object value is nested deep within several levels of container:
public Dictionary<ContextActions, List<Tuple<string, List<object>>>> Value;
What you need to do is to serialize type information for each object inside this dictionary of tuples of lists. You can do this by introducing a surrogate value type:
[ProtoContract]
public struct DynamicTypeSurrogate<T>
{
[ProtoMember(1, DynamicType = true)]
public T Value { get; set; }
}
public static class DynamicTypeSurrogateExtensions
{
public static List<DynamicTypeSurrogate<T>> ToSurrogateList<T>(this IList<T> list)
{
if (list == null)
return null;
return list.Select(i => new DynamicTypeSurrogate<T> { Value = i }).ToList();
}
public static List<T> FromSurrogateList<T>(this IList<DynamicTypeSurrogate<T>> list)
{
if (list == null)
return null;
return list.Select(i => i.Value).ToList();
}
}
And then modifying your RedisDataObject to serialize a surrogate dictionary as follows:
[ProtoContract]
public class RedisDataObject
{
[ProtoMember(1)]
public string DataHash;
[ProtoIgnore]
public Dictionary<ContextActions, List<Tuple<string, List<object>>>> Value;
[ProtoMember(2)]
private Dictionary<ContextActions, List<Tuple<string, List<DynamicTypeSurrogate<object>>>>> SurrogateValue
{
get
{
if (Value == null)
return null;
var dictionary = Value.ToDictionary(
p => p.Key,
p => (p.Value == null ? null : p.Value.Select(i => Tuple.Create(i.Item1, i.Item2.ToSurrogateList())).ToList()));
return dictionary;
}
set
{
if (value == null)
Value = null;
else
{
Value = value.ToDictionary(
p => p.Key,
p => (p.Value == null ? null : p.Value.Select(i => Tuple.Create(i.Item1, i.Item2.FromSurrogateList())).ToList()));
}
}
}
}
Note also the restrictions on DynamicType mentioned here:
DynamicType- stores additionalTypeinformation with the type (by default it includes theAssemblyQualifiedName, although this can be controlled by the user). This makes it possible to serialize weak models, i.e. whereobjectis used for property members, however currently this is limited to contract types (not primitives), and does not work for types with inheritance (these limitations may be removed at a later time). Like withAsReference, this uses a very different layout format
While the documentation above exists at the former project site and has not been moved to the current site, the restriction on non-contract types definitely still exists as of version 2.0.0.668. (I tested that adding an int value to the List<object> fails; I have not checked whether the restriction on inheritance still exists.)
With help from dbc, and all links mentioned in his answer and comments
[ProtoContract]
[ProtoInclude(1, typeof(ObjectWrapper<int>))]
[ProtoInclude(2, typeof(ObjectWrapper<decimal>))]
[ProtoInclude(3, typeof(ObjectWrapper<DateTime>))]
[ProtoInclude(4, typeof(ObjectWrapper<string>))]
[ProtoInclude(5, typeof(ObjectWrapper<double>))]
[ProtoInclude(6, typeof(ObjectWrapper<long>))]
[ProtoInclude(8, typeof(ObjectWrapper<Custom>))]
[ProtoInclude(9, typeof(ObjectWrapper<CustomType[]>))]
public abstract class ObjectWrapper
{
protected ObjectWrapper() { }
abstract public object ObjectValue { get; set; }
public static ObjectWrapper Create(object o) {
Type objectType = o.GetType();
Type genericType = typeof(ObjectWrapper<>);
Type specializedType = genericType.MakeGenericType(objectType);
return (ObjectWrapper)Activator.CreateInstance(specializedType, new object[] { o });
}
}
Downside is you have to register all types you are using in your list of objects. Every time a new type that is not included in the ProtoInclude series surfaces out, you would get an InvalidOperationException with message Unexpected sub-type: ObjectWrapper`1[[NewType]].
[ProtoContract]
public class RedisDataObjectWrapper {
[ProtoMember(1)] public string DataHash;
[ProtoIgnore] public Dictionary<ContextActions, List<Tuple<string, List<object>>>> Value;
[ProtoMember(2)]
private Dictionary<ContextActions, List<Tuple<string, List<ObjectWrapper>>>> AdaptedValue {
get {
if (Value == null) return null;
var dictionary = Value.ToDictionary(
p => p.Key,
p => (p.Value == null ? null : p.Value.Select(i => Tuple.Create(i.Item1, i.Item2.Select(x=>ObjectWrapper.Create(x)).ToList() )).ToList()));
return dictionary;
}
set {
if (value == null) Value = null;
else {
Value = value.ToDictionary(
p => p.Key,
p => (p.Value == null ? null : p.Value.Select(i => Tuple.Create(i.Item1, i.Item2.Select(x=>x.ObjectValue).ToList() )).ToList()));
} } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With