简体   繁体   English

Protobuf字典反序列化引发空键异常

[英]Protobuf dictionary diserialization throws null key exception

I'm working on project written on C# with protobuf-net . 我正在使用protobuf-netC#编写项目。 I have code which serializes/deserializes Dictionary<MyClass1, MyClass2> to file . 我有将Dictionary<MyClass1, MyClass2>序列化/反序列Dictionary<MyClass1, MyClass2> file的代码。 Also, i have a file with serialized data. 另外,我有一个带有序列化数据的文件。 When i try to deserialize it, i receive an exception key can't be null . 当我尝试反序列化时,我收到一个异常key ,不能为null I don't understand how is it possible since Dictionary doesn't allows null , it looks like i couldn't serialize such dictionary. 我不明白怎么可能,因为Dictionary不允许null ,看来我无法序列化这样的字典。 I think the file was corrupted, but i'm not sure. 我认为文件已损坏,但我不确定。 I tried to debug it, and looks like few keys and values were deserialized correctly, but in the middle of the deserialization process, null key occured and i see exception. 我尝试调试它,看起来好像正确地反序列化了几个键和值,但是在反序列化过程的中间,出现了null键,并且我看到了异常。 I tried to use surrogate for ProductName as mentioned here but it doesn't help. 我尝试使用此处提到的ProductName替代方法,但这无济于事。

How to deserialize this file? 如何反序列化此文件? May be there is a way to deserialize to some object instead of null for ProductName ? 也许有一种方法可以将反序列化到某个对象,而不是为ProductName null

Exception: 例外:

System.ArgumentNullException: Value cannot be null. System.ArgumentNullException:值不能为null。 Parameter name: key at System.Collections.Generic.Dictionary 2.Insert(TKey key, TValue value, Boolean add) at System.Collections.Generic.Dictionary 2.System.Collections.Generic.ICollection>.Add(KeyValuePair`2 keyValuePair) at proto_10(Object , ProtoReader ) at ProtoBuf.Meta.TypeModel.DeserializeCore(ProtoReader reader, Type type, Object value, Boolean noAutoCreate) in c:\\Dev\\protobuf-net\\protobuf-net\\Meta\\TypeModel.cs:line 704 at ProtoBuf.Meta.TypeModel.Deserialize(Stream source, Object value, Type type, SerializationContext context) in c:\\Dev\\protobuf-net\\protobuf-net\\Meta\\TypeModel.cs:line 588 at ProtoBuf.Serializer.Deserialize[T](Stream source) in c:\\Dev\\protobuf-net\\protobuf-net\\Serializer.cs:line 77 参数名称:System.Collections.Generic.Dictionary 2.Insert(TKey key, TValue value, Boolean add) at System.Collections.Generic.Dictionary 2.System.Collections.Generic.ICollection> .Add(KeyValuePair`2 keyValuePair上的2.Insert(TKey key, TValue value, Boolean add) at System.Collections.Generic.Dictionary )在ProtoBuf.Meta.TypeModel.DeserializeCore(ProtoReader reader,Type type,Object value,Boolean noAutoCreate)中的proto_10(Object,ProtoReader)中,位于c:\\ Dev \\ protobuf-net \\ protobuf-net \\ Meta \\ TypeModel.cs:line ProtoBuf.Meta.TypeModel.Deserialize上的704(c:\\ Dev \\ protobuf-net \\ protobuf-net \\ Meta \\ TypeModel.cs:ProtoBuf.Serializer.Deserialize的第588行中的流源,对象值,类型类型,SerializationContext上下文) [T](流源)在c:\\ Dev \\ protobuf-net \\ protobuf-net \\ Serializer.cs:第77行

Code: 码:

[ DataContract ]
    public sealed class ProductName
    {
        [ DataMember( Order = 1 ) ]
        public string Name { get; private set; }

        public static readonly ProductName Undefined = Create( "Unknown" );

        private ProductName()
        {
        }

        private ProductName( string Name )
        {
            this.Name = Name.Trim();
        }

        public static ProductName Create( string Name )
        {
            Condition.Requires( Name, "Name" ).IsNotNullOrEmpty();

            return new ProductName( Name );
        }

        public static ProductName TryCreate( string Name )
        {
            return Name.IsValidName() ? new ProductName( Name ) : null;
        }

        public override string ToString()
        {
            return this.Name;
        }

        public override int GetHashCode()
        {
                var stableHashCodeIgnoringCase = this.Name.GetStableHashCodeIgnoringCase();
                return stableHashCodeIgnoringCase;
        }

        #region Equality members
        public bool Equals( ProductName other )
        {
            if( ReferenceEquals( null, other ) )
                return false;
            if( ReferenceEquals( this, other ) )
                return true;
            return string.Equals( this.Name, other.Name, StringComparison.InvariantCultureIgnoreCase );
        }

        public override bool Equals( object obj )
        {
            if( ReferenceEquals( null, obj ) )
                return false;
            if( ReferenceEquals( this, obj ) )
                return true;
            if( obj.GetType() != this.GetType() )
                return false;
            return this.Equals( ( ProductName )obj );
        }
        #endregion
    }


    [ DataContract ]
    public class ProductNameIndex
    {
        [ DataMember( Order = 1 ) ]
        public IDictionary< ProductName, ProductId > Products{ get; private set; }

        public ProductNameIndex()
        {
            this.Products = new Dictionary< ProductName, ProductId >();
        }       
    }

First, to test whether the file is in fact corrupt, you can try to follow the instructions in Recovering corrupted file serialize with Protobuf-net to check to see if the file is corrupt. 首先,要测试文件是否实际上已损坏,您可以尝试按照Protobuf-net序列化恢复损坏的文件中的说明检查文件是否已损坏。

Next, regardless of whether the file is corrupt or not, to deserialize as far as possible, you can use Serializer.Merge<T>(Stream source, T instance) to merge the file onto a pre-allocated ProductNameIndex , like so: 接下来,无论文件是否损坏,要尽可能反Serializer.Merge<T>(Stream source, T instance) ,都可以使用Serializer.Merge<T>(Stream source, T instance)将文件合并到预先分配的ProductNameIndex ,如下所示:

var index = new ProductNameIndex();
try
{
    Serializer.Merge(stream, index);
}
catch (Exception ex)
{
    // Log the error
    Debug.WriteLine(ex);
}

index should now contain as many entries as it was possible to completely deserialize. index现在应包含尽可能完全反序列化的尽可能多的条目。

Now, if the file is corrupt, that is probably the best you can do unless you want to load it into a MemoryStream and attempt to manually fix it byte-by-byte. 现在,如果文件损坏,除非您想将其加载到MemoryStream并尝试逐字节手动修复它,否则可能是最好的选择。 But if the file is not corrupt, you need to debug to find out what the problem is. 但是,如果文件损坏,则需要进行调试以找出问题所在。 One action to take is to compare the contract generated for ProductNameIndex with the contract actually used in the file. 采取的一项措施是将为ProductNameIndex生成的合同与文件中实际使用的合同进行比较。 To see the contract for ProductNameIndex , you can do: 要查看ProductNameIndex的合同,您可以执行以下操作:

Debug.WriteLine(ProtoBuf.Meta.RuntimeTypeModel.Default.GetSchema(typeof(ProductNameIndex)));

Then compare the output from Recovering corrupted file serialize with Protobuf-net , which I believe only dumps top-level fields. 然后, 使用Protobuf-net比较“ 恢复损坏的文件序列化”的输出,我相信这只会转储顶级字段。 If the field numbers and wire types do not match you will know your are reading a file whose root object is not actually ProductNameIndex . 如果字段号和导线类型不匹配,您将知道您正在读取其根对象实际上不是ProductNameIndex You can also use the contract infortmation from GetSchema() to manually step into repeated fields (strings) and nested objects. 您还可以使用GetSchema()的协定提示来手动进入重复的字段(字符串)和嵌套对象。

Finally, if the contracts match but somehow the sending system is emitting a KeyValuePair_ProductName_ProductId message with a missing optional ProductName Key , you could modify your ProductNameIndex to ignore such invalid entries by using a surrogate array property: 最后,如果合同匹配,但是发送系统以某种方式发出了缺少optional ProductName KeyKeyValuePair_ProductName_ProductId消息,则可以使用代理数组属性来修改ProductNameIndex以忽略此类无效条目:

[DataContract]
[ProtoContract]
public class ProductNameIndex
{
    [ProtoMember(1, Name = "Products")]
    KeyValuePair<ProductName, ProductId>[] ProductsSurrogateArray
    {
        get
        {
            return Products.ToArray();
        }
        set
        {
            if (value == null)
                return;
            foreach (var p in value)
            {
                if (p.Key == null)
                    Debug.WriteLine("Ignoring invalid null key");
                else
                    Products.Add(p);
            }
        }
    }

    [ProtoIgnore]
    [DataMember(Order = 1)]
    public IDictionary<ProductName, ProductId> Products { get; private set; }

    public ProductNameIndex()
    {
        this.Products = new Dictionary<ProductName, ProductId>();
    }
}

Or, if you cannot add protobuf attributes to your type, you could introduce a surrogate type for ProductNameIndex with the necessary checks, as explained here . 或者,如果你不能添加protobuf的属性,你的类型,你可以介绍的替代类型ProductNameIndex必要的检查,如解释在这里

Finally, take a look at Using Protobuf-net, I suddenly got an exception about an unknown wire-type which has several useful suggestions for diagnosing protobuf-net problems, including: 最后,看一看使用Protobuf-net,我突然发现了一个未知的电线类型的异常,该异常对诊断Protobuf -net问题有一些有用的建议,包括:

The most likely cause (in my experience) is that you have overwritten an existing file, but have not truncated it; (根据我的经验) 最可能的原因是您覆盖了一个现有文件,但是没有将其截断。 ie it was 200 bytes; 即是200字节; you've re-written it, but with only 182 bytes. 您已经重写了它,但是只有182个字节。 There are now 18 bytes of garbage on the end of your stream that is tripping it up. 现在,流的末尾有18个字节的垃圾将其绊倒。 Files must be truncated when re-writing protocol buffers. 重写协议缓冲区时,文件必须被截断。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM