【问题标题】:Changing type of element in XML Serialization更改 XML 序列化中的元素类型
【发布时间】:2015-05-16 15:45:43
【问题描述】:

我在 XML 序列化方面遇到了很大的问题。我有两个类,都需要可序列化。在继承的类中,我想更改序列化行为,以便将字符串属性序列化为复杂类型。

public class Animal
{
    [XmlElement(ElementName = "NAME")]
    public string Name { get; set; }
    public virtual bool ShouldSerializeName() { return true; }
}

public class Cat : Animal
{
    public override bool ShouldSerializeName() { return false; }

    [XmlElement(ElementName = "NAME")]
    public NameAndType Name2 { get; set; }
}

public class NameAndType
{
    public string Name { get; set; }
    public string Type { get; set; }
}

...

var cat = new Cat {Name2 = new NameAndType {Name = "LittleCat"}};
new XmlSerializer(typeof(Cat)).Serialize(Console.Out, cat);

我尝试了不同的方法,但没有找到改变NAME 元素的序列化方式的方法。 使用上面的示例,我收到错误消息:

The XML element 'NAME' from namespace '' is already present in the current scope. Use XML attributes to specify another XML name or namespace for the element.

【问题讨论】:

    标签: c# .net xml-serialization


    【解决方案1】:

    您收到错误的原因是,在XmlSerializer code generation 期间,代码生成器不了解Cat 上的两个潜在NAME 元素永远不会同时序列化,因此抛出异常。

    相反,您可以将XmlAnyElementAttribute 应用于返回XElement 的虚拟属性,然后手动创建并返回适当的XElement 作为层次结构中每个类的名称:

    [XmlInclude(typeof(Cat))]
    public class Animal
    {
        [XmlIgnore]
        public string Name { get; set; }
    
        [XmlAnyElement]
        public virtual XElement XmlName
        {
            get
            {
                return Name == null ? null : new XElement("NAME", Name);
            }
            set
            {
                Name = (value == null ? null : value.Value);
            }
        }
    }
    
    public class Cat : Animal
    {
        // Must be cached as per https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer%28v=vs.110%29.aspx
        static XmlSerializer nameSerializer;
    
        static Cat()
        {
            nameSerializer = new XmlSerializer(typeof(NameAndType), new XmlRootAttribute("NAME"));
        }
    
        [XmlIgnore]
        public NameAndType Name2 { get; set; }
    
        [XmlAnyElement]
        public override XElement XmlName
        {
            get
            {
                return (Name2 == null ? null : XObjectExtensions.SerializeToXElement(Name2, nameSerializer, true));
            }
            set
            {
                Name2 = (value == null ? null : XObjectExtensions.Deserialize<NameAndType>(value, nameSerializer));
            }
        }
    }
    

    使用扩展方法:

    public static class XObjectExtensions
    {
        public static T Deserialize<T>(this XContainer element)
        {
            return element.Deserialize<T>(new XmlSerializer(typeof(T)));
        }
    
        public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
        {
            using (var reader = element.CreateReader())
            {
                object result = serializer.Deserialize(reader);
                if (result is T)
                    return (T)result;
            }
            return default(T);
        }
    
        public static XElement SerializeToXElement<T>(this T obj)
        {
            return obj.SerializeToXElement(new XmlSerializer(obj.GetType()), true);
        }
    
        public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, bool omitStandardNamespaces)
        {
            var doc = new XDocument();
            using (var writer = doc.CreateWriter())
            {
                XmlSerializerNamespaces ns = null;
                if (omitStandardNamespaces)
                    (ns = new XmlSerializerNamespaces()).Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
                serializer.Serialize(writer, obj, ns);
            }
            var element = doc.Root;
            if (element != null)
                element.Remove();
            return element;
        }
    }
    

    对于List&lt;Animal&gt;,生成如下 XML:

    <ArrayOfAnimal xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <Animal>
            <NAME>duck</NAME>
        </Animal>
        <Animal xsi:type="Cat">
            <NAME>
                <Name>Smokey</Name>
                <Type>Siamese</Type>
            </NAME>
        </Animal>
    </ArrayOfAnimal>
    

    【讨论】:

      【解决方案2】:

      您在同一图形级别上指定了两个具有相同名称的不同 XML 元素,这是不允许的。

      我将提供一个解决方案,但它涉及连接 Cat 类序列化的类型和名称。如果不需要序列化 ​​NameAndType 类,那么继续: 在 Animal 上,将 Name 设置为 virtual; 在 Cat 上,在 Name2 上设置 XmlIgnore。然后覆盖 Name 并以您喜欢的方式返回 Name2 的两个属性。

      如果您真的需要按原样序列化该类,那么恐怕您必须使用不同的 elementName 来序列化它。

      编辑:示例代码:

      public class Animal
      {
          [XmlElement(ElementName = "NAME")]
          public virtual string Name { get; set; }
      
          public virtual bool ShouldSerializeName() { return true; }
      } 
      
      public class Cat : Animal
      {
          public override bool ShouldSerializeName() { return false; }
      
          [XmlIgnore]
          public NameAndType Name2 { get; set; }
      
          [XmlElement(ElementName = "NAME")]
          public override string Name 
          { 
              get
              {
                  return String.Format("{0} [Type:{1}]", Name2.Name, Name2.Type); 
              }
              set { } 
          }        
      }
      

      【讨论】:

      • 感谢您的回答。我无法设置Name protected,因为我还需要使用Animal 类。这两个类都需要可序列化。覆盖Name 听起来不错,但是如何返回NameAndType 的两个属性?
      • 检查编辑并告诉我它是否合适。如果没有,请尝试添加除输出 XML 之外的格式,以便我为您提供另一种解决方案。
      猜你喜欢
      • 2011-09-21
      • 1970-01-01
      • 1970-01-01
      • 2019-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多