.net - 为 .net 类生成两个不同的 xml 序列化

标签 .net serialization xml-serialization

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

例如,假设这些是我的类(class):

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 实际上有两个子类型,每个子类型都有几个额外的字段。默认的序列化可以很好地处理这个问题。任何新方法都需要处理这些子类型的序列化。这意味着我不热衷于纯粹出于序列化目的而制作不同子类型的解决方案。

最佳答案

我最终通过创建一个新类来对需要它的属性进行自定义序列化来解决这个问题,然后使用 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();
        }
    }

}

关于.net - 为 .net 类生成两个不同的 xml 序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3910455/

相关文章:

c# - 来自源的延迟绑定(bind)

c# - 在 C# 中序列化和反序列化外部程序集

xml-serialization - 关于选择不同二进制 xml 工具的建议

来自 xml 的 C# 类

java - XStream: UnknownFieldException - 没有这样的字段

javascript - 禁用 JavaScript 时防止表单提交

c# - 关于 Task.StartNew(Action<Object>, Object) 方法

c# - 为什么有时在计时器滴答事件中它会连续调用该方法两次?

serialization - 如何在不移动数据的情况下使用零拷贝序列化库?

java - 是否可以在 java 中使用反射创建没有无参数构造函数的类的 'blank' 实例?