如何使用“通用XML根”

时间:2018-08-17 15:11:32

标签: c# xml serialization xml-deserialization

一些(外部)天才决定向我们提供XML:

<message_X>
    <header>
        <foo>Foo</foo>
        <bar>Bar</bar>
    </header>
    <body>
        <blah>
            <yadda1 />
            <yadda2 />
            <yadda3 />
            <contentX>
                <!-- message_X specific content -->
            </contentX>
        </blah>
    </body>
</message_X>

但是,还有其他消息(例如message_Ymessage_Z)。除了content节点中的内容之外,所有这些都具有完全相同的基本结构,并且,导致该问题的原因是不同的根节点:

<message_Y>
    <header>
        <foo>Foo</foo>
        <bar>Bar</bar>
    </header>
    <body>
        <blah>
            <yadda1 />
            <yadda2 />
            <yadda3 />
            <contentY>
                <!-- message_X specific content -->
            </contentY>
        </blah>
    </body>
</message_Y>

为什么根节点不仅仅被命名为<message>,因为我会这么做,这让我感到困惑。谁想到这个?

我相应地创建了一个抽象类Message

public abstract class Message {
    public Header Header { get; set; }
    public Body Body { get; set; }
}

public class Header {
    public string Foo { get; set; }
    public string Bar { get; set; }
}

// Etc...

我希望我能做到这一点:

[XmlInclude(typeof(XMessage))]
[XmlInclude(typeof(YMessage))]
public abstract class Message {
    // ...
}

[XmlRoot("message_X")]
public class XMessage : Message {
    // ...
}

[XmlRoot("message_Y")]
public class YMessage : Message {
    // ...
}

但这不起作用:InvalidOperationException: <message_X xmlns=''> was not expected.。要反序列化,我使用:

var ser = new XmlSerializer(typeof(Message));
using (var sr = new StringReader(xmlString))
    return (Message)ser.Deserialize(sr);

我无法控制XML,因此我不希望为X,Y和Z一次又一次地实现此消息。

我将Content放入Message并通过指定T等进行继承来整理Message<T>部分,但这将在以后引起关注。

我还尝试将XmlSerializer ConstructorMessage指定为Type,将XMessageYMessage指定为ExtraTypes,但这没有帮助要么。我还尝试过使用带有DataContractSerializerDataContract等注释的KnownType路线,但这也不起作用。

我希望您能以干净的方式解决此问题的技巧/指针。

1 个答案:

答案 0 :(得分:0)

使用@ steve16351的**思想,我编写了以下反序列化器(我对序列化不感兴趣,仅对反序列化感兴趣):

Paths.js

此反序列化器使用public class XmlDeserializer<T> where T : class { // "Globally" caches T => Dictionary<xmlelementnames, type> private static readonly ConcurrentDictionary<Type, IDictionary<string, Type>> _typecache = new ConcurrentDictionary<Type, IDictionary<string, Type>>(); // We store instances of serializers per type T in this pool so we need not create a new one each time private static readonly ConcurrentDictionary<Type, XmlSerializer> _serializers = new ConcurrentDictionary<Type, XmlSerializer>(); // And all serializers get the same instance of XmlReaderSettings which, again saves creating objects / garbage collecting. private static readonly XmlReaderSettings _readersettings = new XmlReaderSettings() { IgnoreWhitespace = true }; // Lookup for current T, with this we keep a reference for the current T in the global cache so we need one less dictionary lookup private readonly IDictionary<string, Type> _thistypedict; public XmlDeserializer() { // Enumerate T's XmlInclude attributes var includes = ((IEnumerable<XmlIncludeAttribute>)typeof(T).GetCustomAttributes(typeof(XmlIncludeAttribute), true)); // Get all the mappings var mappings = includes.Select(a => new { a.Type, ((XmlRootAttribute)a.Type.GetCustomAttributes(typeof(XmlRootAttribute), true).FirstOrDefault())?.ElementName }).Where(m => !string.IsNullOrEmpty(m.ElementName)); // Store all mappings in our current instance and at the same time store the mappings for T in our "global cache" _thistypedict = _typecache.GetOrAdd(typeof(T), mappings.ToDictionary(v => v.ElementName, v => v.Type)); } public T Deserialize(string input) { // Read our input using (var stringReader = new StringReader(input)) using (var xmlReader = XmlReader.Create(stringReader, _readersettings)) { xmlReader.MoveToContent(); // Make sure we know how to deserialize this element if (!_thistypedict.TryGetValue(xmlReader.Name, out var type)) throw new InvalidOperationException($"Unable to deserialize type '{xmlReader.Name}'"); // Grab serializer from pool or create one if required var serializer = _serializers.GetOrAdd(type, (t) => new XmlSerializer(t, new XmlRootAttribute(xmlReader.Name))); // Finally, now deserialize... return (T)serializer.Deserialize(xmlReader); } } } 属性来确定要使用XmlInclude属性将哪些类映射到哪个元素名称。使用再简单不过了:

XmlRoot

它对对象进行一些内部的“缓存”和“池化”,以使其对内存/ GC友好并具有良好的性能。因此,这解决了我的每种类型的根节点不同名称的问题。现在,我需要弄清楚如何处理不同的内容节点...

**此后谁因某些未知原因删除了答案...


问题的第二部分,“通用内容”很容易解决:

var ser = new XmlDeserializer<Message>();
ser.Deserialize("<message_X>...");

这只让我想知道我是否可以在根节点上应用相同的想法...