C# 反序列化 xml 同时忽略命名空间

C# deserialize xml while ignoring namespace

我必须将 Xml 文件加载并反序列化为一个对象。我可以读取 xml,到达描述对象的位置并仅从很棒的部分解析 xml,但是在 [=21= 的根目录中声明了一个命名空间].

我不明白为什么,但是在读取 xml 时,即使我从给定节点读取它,xmlns 属性也会添加到它,导致我的程序不被由于意外成员,能够将其反序列化为对象。

我的代码:

public static SomeClass GetObjectFromXml (string path)
    {
        XmlReader reader = XmlReader.Create(path);
        string wantedNodeContents = string.Empty;
        while (reader.Read())
        {
            if (reader.NodeType == XmlNodeType.Element && reader.Name == "IWantThis")
            {
                wantedNodeContents = reader.ReadOuterXml();
                break;
            }
        }
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(SomeClass));
        System.IO.StringReader stringReader = new System.IO.StringReader(wantedNodeContents);
        SomeClass loadedSomeClassXml = xmlSerializer.Deserialize(stringReader) as SomeClass;
        return loadedSomeClassXml;
    }

如何摆脱 xmlns 并将 xml 反序列化为对象?

XDocument 在反序列化任何 XML 时为您提供了更多的灵活性。我有一个类似的问题,使用下一个代码片段解决了这个问题:

///Type T must have a default constructor

private T XMLToObject (string pathXML)
{
   T myObjectParsedFromXML= default(T);

   LoadOptions loadOpt = LoadOptions.SetLineInfo;
   XDocument xmlDocument = XDocument.Load(pathXML , loadOpt);

   string namespaceXML = xmlDocument.Root.Name.Namespace.NamespaceName;
   XmlSerializer serializer = new XmlSerializer(typeof(T), defaultNamespace: namespaceXML); 
   
   XmlReader XMLreader = xmlDocument.CreateReader();

   myObjectParsedFromXML= (T)serializer.Deserialize(XMLreader);   
   
   return myObjectParsedFromXML;
}

此外,XmlSerializer 为您提供了一组事件,用于记录序列化过程中的任何问题或错误:

 XmlSerializer serializer = new XmlSerializer(typeof(T), defaultNamespace: namespaceXML);
 
 serializer.UnknownAttribute += new XmlAttributeEventHandler((sender, args) =>
            {
                //Your code for manage the errors during serialization
            });

 serializer.UnknownElement += new XmlElementEventHandler((sender, args) =>
            {  
               //Your code for manage the errors during serialization  
            });

这里有几个问题:

  1. 默认命名空间属性被添加到ReadOuterXml()返回的字符串中,因为ReadOuterXml()旨在不改变返回的[=63]的语义=]。显然在您的 XML 中有一个 default namespace 应用于 <IWantThis> 的某个父节点——作为默认名称空间,它递归地应用于 <IWantThis> 本身。要保留此命名空间成员资格,ReadOuterXml() 必须在写出嵌套的 XML.

    时发出默认命名空间

    如果你真的想完全忽略 XML 上的命名空间,你需要创建一个自定义的 XmlReader,例如如图

    • this answer to Can I make XmlSerializer ignore the namespace on deserialization? by Cheeso.
    • this answer to How do I create a XmlTextReader that ignores Namespaces and does not check characters by Alterant.
  2. 您需要为SomeClass构造一个XmlSerializer,其预期根节点为<IWantThis>。您可以使用 XmlSerializer(Type, XmlRootAttribute) constructor, however, if you do, you must statically cache and reuse the serializer to avoid a severe memory leak, as explained in Memory Leak using StreamReader and XmlSerializer.

  3. 您正在创建要反序列化的元素的本地副本 wantedNodeContents,然后 re-parsing 该本地副本。没有必要这样做,您可以使用 XmlReader.ReadSubtree() 来反序列化 XML.

    的一部分

将所有这些问题放在一起,您的 GetObjectFromXml() 可能看起来像:

public static partial class XmlExtensions
{
    public static T GetObjectFromXml<T>(string path, string localName, string namespaceURI, bool ignoreNamespaces = false)
    {
        using (var textReader = new StreamReader(path))
            return GetObjectFromXml<T>(textReader, localName, namespaceURI);
    }
    
    public static T GetObjectFromXml<T>(TextReader textReader, string localName, string namespaceURI, bool ignoreNamespaces = false)
    {
        using (var xmlReader = ignoreNamespaces ? new NamespaceIgnorantXmlTextReader(textReader) : XmlReader.Create(textReader))
            return GetObjectFromXml<T>(xmlReader, localName, namespaceURI);
    }
    
    public static T GetObjectFromXml<T>(XmlReader reader, string localName, string namespaceURI)
    {
        while (reader.Read())
        {
            if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "IWantThis" && reader.NamespaceURI == namespaceURI)
            {
                var serializer = XmlSerializerFactory.Create(typeof(T), localName, namespaceURI);
                using (var subReader = reader.ReadSubtree())
                    return (T)serializer.Deserialize(subReader);
            }
        }
        // Or throw an exception?
        return default(T);
    }
}

// This class copied from this answer 
// To 
// By https://whosebug.com/users/48082/cheeso
// helper class to ignore namespaces when de-serializing
public class NamespaceIgnorantXmlTextReader : XmlTextReader
{
    public NamespaceIgnorantXmlTextReader(System.IO.TextReader reader): base(reader) { }

    public override string NamespaceURI { get { return ""; } }
}

public static class XmlSerializerFactory
{
    // To avoid a memory leak the serializer must be cached.
    // 
    // This factory taken from 
    // 

    readonly static Dictionary<Tuple<Type, string, string>, XmlSerializer> cache;
    readonly static object padlock;

    static XmlSerializerFactory()
    {
        padlock = new object();
        cache = new Dictionary<Tuple<Type, string, string>, XmlSerializer>();
    }

    public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace)
    {
        if (serializedType == null)
            throw new ArgumentNullException();
        if (rootName == null && rootNamespace == null)
            return new XmlSerializer(serializedType);
        lock (padlock)
        {
            XmlSerializer serializer;
            var key = Tuple.Create(serializedType, rootName, rootNamespace);
            if (!cache.TryGetValue(key, out serializer))
            {
                cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace });
            }
            return serializer;
        }
    }
}

演示 fiddle here.