Protobuf-net: How to serialize complex collection?

北慕城南 提交于 2019-12-07 14:07:37

问题


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.


回答1:


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 additional Type information with the type (by default it includes the AssemblyQualifiedName, although this can be controlled by the user). This makes it possible to serialize weak models, i.e. where object 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 with AsReference, 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.)




回答2:


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()));
    } } } }


来源:https://stackoverflow.com/questions/39208449/protobuf-net-how-to-serialize-complex-collection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!