Reputation: 4287
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.
Upvotes: 3
Views: 1707
Reputation: 3025
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()));
} } } }
Upvotes: 1
Reputation: 116794
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 additionalType
information 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. whereobject
is 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.)
Upvotes: 5