接口属性的XML序列化

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了接口属性的XML序列化相关的知识,希望对你有一定的参考价值。

我想XML序列化一个对象,该对象具有(以及其他)IModelObject类型的属性(这是一个接口)。

public class Example
{
    public IModelObject Model { get; set; }
}

当我尝试序列化此类的对象时,我收到以下错误: “无法序列化成员Example.Model类型为Example,因为它是一个接口。”

我知道问题是接口无法序列化。但是,具体的Model对象类型在运行时才会被识别。

用抽象或具体类型替换IModelObject接口并使用XMLInclude的继承是可能的,但似乎是一个丑陋的解决方法。

有什么建议?

答案

这只是声明性序列化的固有限制,其中类型信息未嵌入在输出中。

试图将<Flibble Foo="10" />转换回来

public class Flibble { public object Foo { get; set; } }

串行器如何知道它应该是int,string,double(或其他)...

为了使这项工作你有几个选择,但如果你真的不知道,直到运行时,最简单的方法是使用XmlAttributeOverrides

遗憾的是,这只适用于基类,而不适用于接口。您可以做的最好的事情是忽略不足以满足您需求的房产。

如果你真的必须使用接口,你有三个真正的选择:

隐藏它并在另一个属性中处理它

丑陋,令人不快的锅炉板和多次重复,但班上的大多数消费者将不必处理这个问题:

[XmlIgnore()]
public object Foo { get; set; }

[XmlElement("Foo")]
[EditorVisibile(EditorVisibility.Advanced)]
public string FooSerialized 
{ 
  get { /* code here to convert any type in Foo to string */ } 
  set { /* code to parse out serialized value and make Foo an instance of the proper type*/ } 
}

这很可能成为维护的噩梦......

实现IXmlSerializable

类似于第一个选项,你可以完全控制事物

  • 优点 你没有肮脏的'假'属性。 您可以直接与xml结构交互,增加灵活性/版本控制
  • 缺点 你可能最终不得不为班上的所有其他属性重新实现轮子

重复劳动的问题与第一个问题类似。

修改属性以使用包装类型

public sealed class XmlAnything<T> : IXmlSerializable
{
    public XmlAnything() {}
    public XmlAnything(T t) { this.Value = t;}
    public T Value {get; set;}

    public void WriteXml (XmlWriter writer)
    {
        if (Value == null)
        {
            writer.WriteAttributeString("type", "null");
            return;
        }
        Type type = this.Value.GetType();
        XmlSerializer serializer = new XmlSerializer(type);
        writer.WriteAttributeString("type", type.AssemblyQualifiedName);
        serializer.Serialize(writer, this.Value);   
    }

    public void ReadXml(XmlReader reader)
    {
        if(!reader.HasAttributes)
            throw new FormatException("expected a type attribute!");
        string type = reader.GetAttribute("type");
        reader.Read(); // consume the value
        if (type == "null")
            return;// leave T at default value
        XmlSerializer serializer = new XmlSerializer(Type.GetType(type));
        this.Value = (T)serializer.Deserialize(reader);
        reader.ReadEndElement();
    }

    public XmlSchema GetSchema() { return(null); }
}

使用它会涉及类似(在项目P中):

public namespace P
{
    public interface IFoo {}
    public class RealFoo : IFoo { public int X; }
    public class OtherFoo : IFoo { public double X; }

    public class Flibble
    {
        public XmlAnything<IFoo> Foo;
    }


    public static void Main(string[] args)
    {
        var x = new Flibble();
        x.Foo = new XmlAnything<IFoo>(new RealFoo());
        var s = new XmlSerializer(typeof(Flibble));
        var sw = new StringWriter();
        s.Serialize(sw, x);
        Console.WriteLine(sw);
    }
}

这给你:

<?xml version="1.0" encoding="utf-16"?>
<MainClass 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <Foo type="P.RealFoo, P, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
  <RealFoo>
   <X>0</X>
  </RealFoo>
 </Foo>
</MainClass>

对于班级用户来说,这显然更加麻烦,尽管避免了很多锅炉板。

一个幸福的媒介可能是将XmlAnything想法合并到第一种技术的“支持”属性中。通过这种方式,大部分繁重的工作都是为你完成的,但是除了内省的混乱之外,班级的消费者不会受到任何影响。

另一答案

解决方法是使用DataContractSerializer进行反射。您甚至不必使用[DataContract]或[DataMember]标记您的类。它将序列化任何对象,无论它是否具有接口类型属性(包括字典)到xml中。这是一个简单的扩展方法,它将任何对象序列化为XML,即使它有接口(注意你可以调整它以便递归运行)。

    public static XElement ToXML(this object o)
    {
        Type t = o.GetType();

        Type[] extraTypes = t.GetProperties()
            .Where(p => p.PropertyType.IsInterface)
            .Select(p => p.GetValue(o, null).GetType())
            .ToArray();

        DataContractSerializer serializer = new DataContractSerializer(t, extraTypes);
        StringWriter sw = new StringWriter();
        XmlTextWriter xw = new XmlTextWriter(sw);
        serializer.WriteObject(xw, o);
        return XElement.Parse(sw.ToString());
    }

LINQ表达式的作用是枚举每个属性,返回作为接口的每个属性,获取该属性的值(底层对象),获取该具体对象的类型将其放入数组中,并将其添加到序列化程序中已知类型列表。

现在,序列化器知道它序列化的类型如何,它可以完成它的工作。

另一答案

你可以使用ExtendedXmlSerializer。此序列化程序支持接口属性的序列化,没有任何技巧。

var serializer = new ConfigurationContainer().UseOptimizedNamespaces().Create();

var obj = new Example
                {
                    Model = new Model { Name = "name" }
                };

var xml = serializer.Serialize(obj);

你的xml看起来像:

<?xml version="1.0" encoding="utf-8"?>
<Example xmlns:exs="https://extendedxmlserializer.github.io/v2" xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Simple;assembly=ExtendedXmlSerializer.Samples">
    <Model exs:type="Model">
        <Name>name</Name>
    </Model>
</Example>

ExtendedXmlSerializer支持.net 4.5和.net Core。

另一答案

如果您事先知道您的接口实现者,那么您可以使用相当简单的hack来使您的接口类型序列化而无需编写任何解析代码:

public interface IInterface {}
public class KnownImplementor01 : IInterface {}
public class KnownImplementor02 : IInterface {}
public class KnownImplementor03 : IInterface {}
public class ToSerialize {
  [XmlIgnore]
  public IInterface InterfaceProperty { get; set; }
  [XmlArray("interface")]
  [XmlArrayItem("ofTypeKnownImplementor01", typeof(KnownImplementor01))]
  [XmlArrayItem("ofTypeKnownImplementor02", typeof(KnownImplementor02))]
  [XmlArrayItem("ofTypeKnownImplementor03", typeof(KnownImplementor03))]
  public object[] InterfacePropertySerialization {
    get { return new[] { InterfaceProperty }; ; }
    set { InterfaceProperty = (IInterface)value.Single(); }
  }
}

生成的xml应该看起来像是行

 <interface><ofTypeKnownImplementor01><!-- etc... -->
另一答案

用抽象或具体类型替换IModelObject接口并使用XMLInclude的继承是可能的,但似乎是一个丑陋的解决方法。

如果可以使用抽象基础,我会推荐该路线。它仍然比使用手卷序列化更清洁。我在抽象基础上看到的唯一麻烦是你还需要具体类型吗?至少这就是我过去使用它的方式,例如:

public abstract class IHaveSomething
{
    public abstract string Something { get; set; }
}

public class MySomething : IHaveSomething
{
    string _sometext;
    public override string Something 
    { get { return _sometext; } set { _sometext = value; } }
}

[XmlRoot("abc")]
public class seriaized
{
    [XmlElement("item", typeof(MySomething))]
    public IHaveSomething data;
}
另一答案

不幸的是,没有简单的答案,因为序列化程序不知道要为接口序列化什么。我找到了关于如何在MSDN上解决这个问题的更完整的解释

另一答案

在我的项目中,我有一个 List <IFormatStyle> FormatStyleTemplates; 包含不同的类型。

然后我使用上面的解决方案'XmlAnything'来序列化这个不同类型的列表。生成的xml很漂亮。

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [XmlArray("FormatStyleTemplates")]
    [XmlArrayItem("FormatStyle")]
    public XmlAnything<IFormatStyle>[] FormatStyleTemplatesXML
    {
        get
        {
            return FormatStyleTemplates.Select(t => new XmlAnything<IFormatStyle>(t)).ToArray();
        }
        set
        {
            // read the values back into some new object or whatever
            m_FormatStyleTemplates = new FormatStyleProvider(null, true);
            value.ForEach(t => m_FormatStyleTemplates.Add(t.Value));
        }
    }
另一答案

不幸的是,我有一个案例,要序列化的类具有接口作为属性的属性,所以我需要递归处理每个属性。此外,一些接口属性被标记为[XmlIgnore],所以我想跳过这些。我接受了

以上是关于接口属性的XML序列化的主要内容,如果未能解决你的问题,请参考以下文章

在Tomcat的安装目录下conf目录下的server.xml文件中增加一个xml代码片段,该代码片段中每个属性的含义与用途

XML 属性未获取命名空间前缀

从父片段到选项卡片段的接口侦听器不起作用

来自活动xml的片段中的findViewById属性不起作用[重复]

GroovyXml 反序列化 ( 使用 XmlParser 解析 Xml 文件 | 获取 Xml 文件中的节点和属性 | 获取 Xml 文件中的节点属性 )

在 sapui5 片段的 xml 文件中使用 jquery