代码之家  ›  专栏  ›  技术社区  ›  RobIII Lukas

如何使用“通用xml根”

  •  0
  • RobIII Lukas  · 技术社区  · 6 年前

    <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_Y message_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> 继承时指定T等,但这将是以后关注的问题。

    我也试过指定 消息 作为 Type XMessage YMessage ExtraTypes XmlSerializer Constructor 但这也没用。我也试着去 DataContractSerializer DataContract , KnownType 等等注释,但这也不起作用。

    我希望你能给我一些关于如何用干净的方式解决这个问题的建议。

    1 回复  |  直到 6 年前
        1
  •  0
  •   RobIII Lukas    6 年前

    与@ steve16351 我写了下面的反序列化程序(我对序列化不感兴趣,只对反序列化感兴趣):

    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 属性。用法再简单不过了:

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

    它执行一些内部的对象“缓存”和“池”,以保持内存/GC友好并具有相当高的性能。所以这就解决了我的根节点对于每种类型的不同名称的问题。现在我需要弄清楚如何处理不同的内容节点。。。

    **他因为某种未知的原因删除了他的答案。。。


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

    public class Blah {
        public Yadda1 Yadda1 { get; set; }
        public Yadda2 Yadda2 { get; set; }
        public Yadda3 Yadda3 { get; set; }
    
        [XmlElement("contentX", typeof(ContentX))]
        [XmlElement("contentY", typeof(ContentY))]
        [XmlChoiceIdentifier(nameof(PayloadType))]
        public Payload Payload { get; set; }
    
        public PayloadType PayloadType { get; set; }
    }
    
    public abstract class Payload { ... }
    public class ContentX : Payload { ... }
    public class ContentY : Payload { ... }
    
    [XmlType]
    public enum PayloadType
    {
        [XmlEnum("contentX")]
        ContentX,
        [XmlEnum("contentY")]
        ContentY,
    }