C# 在忽略命名空间的同时反序列化 xml
Posted
技术标签:
【中文标题】C# 在忽略命名空间的同时反序列化 xml【英文标题】:C# deserialize xml while ignoring namespace 【发布时间】:2021-02-17 02:51:31 【问题描述】:我必须将 Xml 文件加载并反序列化为对象。我可以读取 xml,到达描述对象的位置并仅从该部分解析 xml,这很好,但是在 xml 的根中声明了一个命名空间。
我不明白为什么,但是在读取 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 反序列化为对象?
【问题讨论】:
它被添加到外部 XML 中,因为某些包含节点有一个 default namespace,默认情况下,它适用于您的<IWantThis>
节点。由于ReadOuterXml()
旨在不更改正在读取的 XML 的语义,因此它必须在返回的 XML 中添加默认命名空间。
我明白了,我可以采取什么方法?
【参考方案1】:
这里有一些问题:
默认命名空间属性被添加到ReadOuterXml()
返回的字符串中,因为ReadOuterXml()
旨在不改变返回的 XML 的语义。显然,在您的 XML 中有一个 default namespace 应用于 <IWantThis>
的某个父节点——作为默认命名空间,它递归地应用于 <IWantThis>
本身。要保留此命名空间成员资格,ReadOuterXml()
在写出嵌套 XML 时必须发出默认命名空间。
如果你真的想完全忽略 XML 上的命名空间,你需要创建一个自定义的XmlReader
,例如如图
您需要为SomeClass
构造一个XmlSerializer
,其预期根节点为<IWantThis>
。您可以使用XmlSerializer(Type, XmlRootAttribute)
构造函数执行此操作,但是,如果您这样做,您必须静态缓存并重用序列化程序以避免严重的内存泄漏,如 Memory Leak using StreamReader and XmlSerializer 中所述em>。
您正在创建要反序列化的元素的本地副本wantedNodeContents
,然后重新解析该本地副本。无需这样做,您可以使用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 https://***.com/a/873281/3744182
// To https://***.com/questions/870293/can-i-make-xmlserializer-ignore-the-namespace-on-deserialization
// By https://***.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.
// https://***.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer
// This factory taken from
// https://***.com/questions/34128757/wrap-properties-with-cdata-section-xml-serialization-c-sharp/34138648#34138648
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;
演示小提琴here.
【讨论】:
【参考方案2】:XDocument 在反序列化任何 XML 时为您提供了更多的灵活性。我有一个类似的问题,它是使用下一个 sn-p 代码解决的:
///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
);
【讨论】:
这似乎几乎可以工作,但现在命名空间刚刚到达我的最后一个节点,再次导致异常。以上是关于C# 在忽略命名空间的同时反序列化 xml的主要内容,如果未能解决你的问题,请参考以下文章
c# XmlSerializer 反序列化器缺少默认命名空间
我可以让 XmlSerializer 在反序列化时忽略命名空间吗?