DataContractSerializer и Dictionary‹строка,объект› завершается с ошибкой при чтении

Я использую DataContractSerializer для сериализации объекта, содержащего член Dictionary<string,object>, помеченный [DataMember()]. Идея состоит в том, чтобы иметь гибкий набор атрибутов объекта, и я не знаю, какими могут быть эти атрибуты.

Это прекрасно работает, когда я помещаю объекты int, double и string в словарь, но когда я помещаю в него 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> или что-то в этом роде.

Как правильно поступить в этой ситуации?


person Chris Herborth    schedule 01.10.2011    source источник


Ответы (3)


Попробуйте использовать KnownTypeAttribute, чтобы DataContractSerializer знал о типе List<string>. К сожалению, это, кажется, идет вразрез с вашей идеей не знать о типах заранее.

Я основываюсь на следующем коде, который использует DataContractSerializer для сериализации Dictionary<string, object>, содержащего List<string>:

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[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]» с именем контракта данных «ArrayOfstring:http://schemas. microsoft.com/2003/10/Serialization/Arrays не ожидается. Рассмотрите возможность использования DataContractResolver или добавления любых типов, которые не известны статически, в список известных типов, например, с помощью атрибута KnownTypeAttribute или путем добавления их в список известных типов, передаваемых в DataContractSerializer.

person Jeff Ogata    schedule 01.10.2011

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);
        }
    }
}
person carlosfigueira    schedule 01.10.2011

У меня несколько раз была похожая идея, и я реализовал ее с дополнительным полем, содержащим полные имена сборок всех элементов словаря. Он заполняет список при каждом добавлении или перезаписи элемента, затем использует его при сериализации и использует 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;
    }
}
person Андрей Саяпин    schedule 05.03.2019