数据合同序列化程序和字典<字符串,对象> 读取时失败



我使用DataContractSerializer来序列化包含Dictionary<string,object>成员的对象,该成员被标记为[DataMember()]。这个想法是有一个灵活的对象属性包,我不知道这些属性可以是什么。

当我将int, doublestring对象放入字典时,这工作得很好,但是当我将List<string>放入字典时,它无法反序列化对象:

System.InvalidOperationException: Node type Element is not supported in this operation.

整个字典被序列化为XML,它看起来非常合理:

<Attributes xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
    <d2p1:KeyValueOfstringanyType>
        <d2p1:Key>name</d2p1:Key>
        <d2p1:Value xmlns:d4p1="http://www.w3.org/2001/XMLSchema" i:type="d4p1:string">Test object</d2p1:Value>
    </d2p1:KeyValueOfstringanyType>
    <d2p1:KeyValueOfstringanyType>
        <d2p1:Key>x</d2p1:Key>
        <d2p1:Value xmlns:d4p1="http://www.w3.org/2001/XMLSchema" i:type="d4p1:double">0.5</d2p1:Value>
    </d2p1:KeyValueOfstringanyType>
    <d2p1:KeyValueOfstringanyType>
        <d2p1:Key>y</d2p1:Key>
        <d2p1:Value xmlns:d4p1="http://www.w3.org/2001/XMLSchema" i:type="d4p1:double">1.25</d2p1:Value>
    </d2p1:KeyValueOfstringanyType>
    <d2p1:KeyValueOfstringanyType>
        <d2p1:Key>age</d2p1:Key>
        <d2p1:Value xmlns:d4p1="http://www.w3.org/2001/XMLSchema" i:type="d4p1:int">4</d2p1:Value>
    </d2p1:KeyValueOfstringanyType>
    <d2p1:KeyValueOfstringanyType>
        <d2p1:Key>list-of-strings</d2p1:Key>
        <d2p1:Value>
            <d2p1:string>one string</d2p1:string>
            <d2p1:string>two string</d2p1:string>
            <d2p1:string>last string</d2p1:string>
        </d2p1:Value>
    </d2p1:KeyValueOfstringanyType>
</Attributes>

注意末尾的list-of-strings。它有所有的值,但没有显示它是List<string>或其他的。

处理这种情况的正确方法是什么?

尝试使用KnownTypeAttribute使DataContractSerializer知道List<string>的类型。不幸的是,这似乎违背了你不必事先了解类型的想法。

我基于以下代码,它使用DataContractSerializer来序列化包含List<string>Dictionary<string, object>:

Dictionary<string,object> dictionary = new Dictionary<string, object>();
dictionary.Add("k1", new List<string> { "L1", "L2", "L3" });
List<Type> knownTypes = new List<Type> { typeof(List<string>) };
DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary<string,object>), knownTypes);
MemoryStream stream = new MemoryStream();
serializer.WriteObject(stream, dictionary);
StreamReader reader = new StreamReader(stream);
stream.Position = 0;
string xml = reader.ReadToEnd();

如果你的knownTypes没有提供给DataContractSerializer,它会抛出一个异常。

SerializationException:类型"System.Collections.Generic.List"1[[系统。字符串,mscorlib,版本=4.0.0.0,文化=中性,PublicKeyToken=b77a5c561934e089]]'带有数据合约名称"ArrayOfstring: http://schemas.microsoft.com/2003/10/Serialization/Arrays"没有预料到。考虑使用DataContractResolver或添加任何已知类型列表中静态未知的类型-例如,通过使用KnownTypeAttribute属性或将它们添加到传递给DataContractSerializer的已知类型列表。

WCF无法知道您在那里拥有的是List<string> -注意所有其他<Value>元素都有"类型提示"(i:type属性)。如果你想反序列化它,它需要有标记,,你还需要告诉WCF, List<string>是一个"已知类型" -见下文。要了解更多关于已知类型的信息(以及为什么需要它们),网上有很多很好的资源。

public class StackOverflow_7620718
{
    public static void Test()
    {
        Dictionary<string, object> dict = new Dictionary<string, object>
        {
            { "name", "Test object" },
            { "x", 0.5 },
            { "y", 1.25 },
            { "age", 4 },
            { "list-of-strings", new List<string> { "one string", "two string", "last string" } }
        };
        MemoryStream ms = new MemoryStream();
        XmlWriter w = XmlWriter.Create(ms, new XmlWriterSettings
        {
            Indent = true,
            Encoding = new UTF8Encoding(false),
            IndentChars = "  ",
            OmitXmlDeclaration = true,
        });
        DataContractSerializer dcs = new DataContractSerializer(dict.GetType(), new Type[] { typeof(List<string>) });
        dcs.WriteObject(w, dict);
        w.Flush();
        Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray()));
        ms.Position = 0;
        Console.WriteLine("Now deserializing it:");
        Dictionary<string, object> dict2 = (Dictionary<string, object>)dcs.ReadObject(ms);
        foreach (var key in dict2.Keys)
        {
            Console.WriteLine("{0}: {1}", key, dict2[key].GetType().Name);
        }
    }
}

我有过很多次类似的想法,我用附加的字段来实现它,这些字段包含所有Dictionary项的程序集限定名。它在添加或重写每个项目时填充列表,然后在序列化时使用它,并使用XmlReader提取类型信息、构建类型列表和反序列化对象。

代码:

[DataContract]
public class Message
{
    [DataMember] private List<string> Types = new List<string>();
    [DataMember] private Dictionary<string, object> Data = new Dictionary<string, object>();
    public object this[string id]
    {
        get => Data.TryGetValue(id, out var o) ? o : null;
        set {
            Data[id] = value;
            if (!Types.Contains(value.GetType().AssemblyQualifiedName))
                Types.Add(value.GetType().AssemblyQualifiedName);
        }
    }
    public byte[] Serialize()
    {
        var dcs = new DataContractSerializer(typeof(Message), Types.Select(Type.GetType));
        using (var ms = new MemoryStream()) {
            dcs.WriteObject(ms, this);
            return ms.ToArray();
        }
    }
    public static Message Deserialize(byte[] input)
    {
        var types = new List<string>();
        using (var xr = XmlReader.Create(new StringReader(Encoding.UTF8.GetString(input)))) {
            if (xr.ReadToFollowing(nameof(Types))) {
                xr.ReadStartElement();
                while (xr.NodeType != XmlNodeType.EndElement) {
                    var res = xr.ReadElementContentAsString();
                    if (!string.IsNullOrWhiteSpace(res))
                        types.Add(res);
                }
            }
        }
        var dcs = new DataContractSerializer(typeof(Message), types.Select(Type.GetType));
        using (var ms = new MemoryStream(input))
            if (dcs.ReadObject(ms) is Message msg)
                return msg;
        return null;
    }
}

最新更新