【问题标题】:Generate two different xml serializations for a .net class为 .net 类生成两个不同的 xml 序列化
【发布时间】:2010-10-11 22:41:46
【问题描述】:

我有一组 .net 类,我目前将它们序列化并与一堆其他代码一起使用,因此该 xml 的格式相对固定(格式 #1)。我需要以另一种格式(格式#2)生成 xml,它的结构非常相似,但不完全相同,我想知道最好的方法。

例如,假设这些是我的课程:

public class Resource 
{ 
    public string Name { get; set; }
    public string Description { get; set; }
    public string AnotherField { get; set; }
    public string AnotherField2 { get; set; }
    public Address Address1 { get; set; }
    public Address Address2 { get; set; }
    public Settings Settings { get; set; }
}
public class Address
{ 
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string City { get; set; }
} 
// This class has custom serialization because it's sort-of a dictionary. 
// (Maybe that's no longer needed but it seemed necessary back in .net 2.0).
public class Settings : IXmlSerializable
{ 
    public string GetSetting(string settingName) { ... }
    public string SetSetting(string settingName, string value) { ... }
    public XmlSchema GetSchema() { return null; }
    public void ReadXml(XmlReader reader) 
    {
        // ... reads nested <Setting> elements and calls SetSetting() appropriately 
    }
    public void WriteXml(XmlWriter writer)
    {
        // ... writes nested <Setting> elements 
    }
} 

我通常使用标准的 XmlSerialization,它会生成很棒的 XML(格式 #1)。类似的东西:

<Resource>
  <Name>The big one</Name>
  <Description>This is a really big resource</Description>
  <AnotherField1>ADVMW391</AnotherField1>
  <AnotherField2>green</AnotherField2>
  <Address1>
    <Line1>1 Park Lane</Line1>
    <Line2>Mayfair</Line2>
    <City>London</City>
  </Address1>
  <Address2>
    <Line1>11 Pentonville Rd</Line1>
    <Line2>Islington</Line2>
    <City>London</City>
  </Address2>
  <Settings>
    <Setting>
      <Name>Height</Name>
      <Value>12.4</Value>
    </Setting>
    <Setting>
      <Name>Depth</Name>
      <Value>14.1028</Value>
    </Setting>
  </Settings>
</Resource>

我要生成的新 XML(格式 #2)看起来像当前的 XML,除了:

  • 现在应该将这些字段表示为设置,而不是字段 AnotherFieldAnotherField2。即好像 SetSetting() 在序列化之前被调用了两次,所以这些值在 .

  • 中显示为新元素
  • 而不是字段Address1Address2,它们应该表示为包含两个元素的元素。元素应该有一个或两个额外的属性,例如位置和地址类型。

例如

<Resource>
  <Name>The big one</Name>
  <Description>This is a really big resource</Description>
  <Addresses>
    <Address>
      <Line1>1 Park Lane</Line1>
      <Line2>Mayfair</Line2>
      <City>London</City>
      <Position>1</Position>
      <AddressType>Postal</AddressType>
    </Address>
    <Address>
      <Line1>11 Pentonville Rd</Line1>
      <Line2>Islington</Line2>
      <City>London</City>
      <Position>2</Position>
      <AddressType>Postal</AddressType>
    </Address>
  </Addresses>
  <Settings>
    <Setting>
      <Name>Height</Name>
      <Value>12.4</Value>
    </Setting>
    <Setting>
      <Name>Depth</Name>
      <Value>14.1028</Value>
    </Setting>
    <Setting>
      <Name>AnotherField</Name>
      <Value>ADVMW391</Value>
    </Setting>
    <Setting>
      <Name>AnotherField2</Name>
      <Value>green</Value>
    </Setting>
  </Settings>
</Resource>

我可以使用 XmlAttributeOverrides 以这种方式控制序列化吗?否则我应该如何处理它?

请记住,我的真实类的字段数至少是其 10 倍,并且有一些嵌套类我对默认序列化非常满意,因此我想避免过多的手动序列化代码。

可能的选择

我可以看到这些选项:

  1. 也许可以使用覆盖来控制我关心的属性的序列化?
  2. 格式 #2 的资源类的自定义序列化,在适当的情况下调用嵌套类的默认序列化。不确定如何处理设置,因为我实际上想添加设置,使用默认值进行序列化,然后删除添加的设置。
  3. 使用默认序列化创建 xml,然后操作 XML 以进行我需要的更改。 (哎呀!)。

另一个小麻烦是我上面例子中的Resource 实际上有两个子类型,每个子类型都有几个额外的字段。默认的序列化很好地处理了这个问题。任何新方法也需要处理这些子类型的序列化。这意味着我不热衷于让我制作不同的子类型纯粹用于序列化目的的解决方案。

【问题讨论】:

  • 您可能想要显示示例 XML 格式
  • 是的......我很懒惰,试图避免所有这些打字。

标签: .net serialization xml-serialization


【解决方案1】:

我最终通过创建一个新类来解决这个问题,该类对需要它的属性进行自定义序列化,然后使用 XmlAttributeOverrides 来确保使用该类而不是属性的默认序列化。

public class Resource
{ 
    ...

    // the method that actually does the serialization
    public void SerializeToFormat2Xml(XmlWriter writer)
    { 
        Format2Serializer.Serialize(writer, this);
    }

    // Cache the custom XmlSerializer. Since we're using overrides it won't be cached
    // by the runtime so if this is used frequently it'll be a big performance hit
    // and memory leak if it's not cached. See docs on XmlSerializer for more.
    static XmlSerializer _format2Serializer = null;
    static XmlSerializer Format2Serializer
    { 
        get { 
            if (_format2Serializer == null) 
            { 
                XmlAttributeOverrides overrides = new XmlAttributeOverrides();
                XmlAttributes ignore = new XmlAttributes();
                ignore.XmlIgnore = true;

                // ignore serialization of fields that will go into Settings 
                overrides.Add(typeof (Resource), "AnotherField", ignore);
                overrides.Add(typeof (Resource), "AnotherField2", ignore);

                // instead of serializing the normal Settings object, we use a custom serializer field
                overrides.Add(typeof (Resource), "Settings", ignore);
                XmlAttributes attributes = new XmlAttributes();
                attributes.XmlIgnore = false;
                attributes.XmlElements.Add(new XmlElementAttribute("Settings"));
                overrides.Add(typeof (Resource), "CustomSettingsSerializer", attributes);

                // ... do similar stuff for Addresses ... not in this example


                _format2Serializer = new XmlSerializer(typeof(Resource), overrides);
           }
           return _format2Serializer;
        }
    }

    // a property only used for custom serialization of settings
    [XmlIgnore]
    public CustomSerializeHelper CustomSettingsSerializer
    {
        get { return new CustomSerializeHelper (this, "Settings"); }
        set { } // needs setter otherwise won't be serialized!
    }

    // would have a similar property for custom serialization of addresses, 
    // defaulting to XmlIgnore.
}


public class CustomSerializeHelper : IXmlSerializable
{
    // resource to serialize
    private Resource _resource;

    // which field is being serialized. 
    private string _property;

    public CustomSerializeHelper() { } // must have a default constructor
    public CustomSerializeHelper(Resource resource, string property)
    {
        _resource = resource;  
        _property = property;  
    }

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        return;
    }

    public void WriteXml(XmlWriter writer)
    {
        if (_property == "Settings")
        {
            Dictionary<string, string> customSettings = new Dictionary<string, string>();
            customSettings.Add("AnotherField", _resource.AnotherField);
            customSettings.Add("AnotherField2", _resource.AnotherField2);

            _resource.Settings.WriteXml(writer, customSettings);
        }
        if (_property == "Addresses")
        { 
            // ... similar custom serialization for Address, 
            // in that case getting a new XmlSerializer(typeof(Address)) and calling
            // Serialize(writer,Address), with override to add Position.
        }
    }


public partial class Settings
{ 
    // added this new method to Settings so it can serialize itself plus
    // some additional settings.
    public void WriteXml(XmlWriter writer, Dictionary<string, string> additionalSettingsToWrite)
    {
        WriteXml(writer);
        foreach (string key in additionalSettingsToWrite.Keys)
        {
            string value = additionalSettingsToWrite[key];
            writer.WriteStartElement("Setting");
            writer.WriteElementString("SettingType", key);
            writer.WriteElementString("SettingValue", value);
            writer.WriteEndElement();
        }
    }

}

【讨论】:

    【解决方案2】:

    更新:
    正如其他人所指出的,我下面的方法也可以使用 XmlAttributeOverrides 以更少的努力来实现。我仍然会将我的答案发布为另一种为常见继承属性发出 2 个不同 XML 标记的方式,但我建议您也查看 XmlAttributeOverrides。

    原始答案:
    如果您尝试使用简单的父子继承方法来解决此问题,我预计您很快就会遇到 XmlSerializer 的问题。

    您应该做的(恕我直言)是将当前类变成基类,并将 XmlElement 属性设置为 XmlIgnore(对于您要修改的字段)。这个基类集应该包含所有必要的 getter/setter 逻辑。

    将继承分成 2 组子项。一个集合应该是一个简单集合,它将 XmlIgnore 更改为[XmlElement](无需为此集合指定 ElementName)。这就是这个类的全部目的。

    第二组将从基类继承,并将 XmlIgnore 更改为[XmlElement(ElementName=myNameHere)] 用于那些有问题的相同字段。这就是这个类需要做的所有事情。

    这里有一个示例来说明我在说什么:

    基类:

    public class OriginalClass
    {
        private string m_field;
    
        [XmlIgnore]
        public virtual string Field 
        {
            get
            {
                return m_field;
            }
            set
            {
                m_field = value;
            }
        }
    }
    

    儿童班(一):

    public class ChildClass : OriginalClass
    {
        public ChildClass() { }
    
        [XmlElement]
        public override string Field
        {
            get { return base.Field; }
            set { base.Field = value; }
        }
    }
    

    子类 (2) - 覆盖字段名称的类:

    public class ChildClass2 : OriginalClass
    {
        public ChildClass2() { }
    
        [XmlElement(ElementName = "NewField")]
        public override string Field
        {
            get { return base.Field; }
            set { base.Field = value; }
        }
    }
    

    示例程序:

    class Program
    {
        static void Main(string[] args)
        {
            ChildClass obj1 = new ChildClass();
            ChildClass2 obj2 = new ChildClass2();
    
            obj1.Field = "testing overridden field";
            obj2.Field = "testing overridden field (2)";
    
    
            var sw = new StreamWriter(Console.OpenStandardOutput());
    
            XmlSerializer xs = new XmlSerializer(typeof(ChildClass));
            xs.Serialize(sw, obj1);
            Console.WriteLine();
    
            XmlSerializer xs2 = new XmlSerializer(typeof(ChildClass2));
            xs2.Serialize(sw, obj2);
    
            Console.ReadLine();
        }
    }
    

    ChildClass2 的 XML 输出将显示为“NewField”。

    【讨论】:

    • 我认为您所描述的最好使用 XmlAttributeOverrides 而不是拥有子类来实现。但是您的建议并不能解决我的实际问题,例如如何将字段从一个元素移动到另一个元素。
    • 我知道的让 XML 序列化程序嵌套的唯一方法是将其移动到嵌套元素中。
    • @Rory,很抱歉没有正确阅读问题......!查看 Address 属性,您似乎需要重构 Address 类以及 Resource 类 - 正如 drachenstern 所提到的,否则您无法让 XML 序列化程序嵌套。你是对的,XmlAttributeOverrides 也可以完成这项工作。我会相应地更新我的帖子。
    • 不幸的是,重构任何类都超出了范围 - 有很多代码依赖于它们,它们需要继续以当前格式 #1 和格式 #2 输出 XML
    猜你喜欢
    • 2018-10-12
    • 1970-01-01
    • 2017-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-04
    • 1970-01-01
    • 2011-08-10
    相关资源
    最近更新 更多