简体   繁体   English

XmlSerializer:将类属性序列化为自定义子元素的属性

[英]XmlSerializer: serializing a class property as an attribute of a custom subelement

I'm using an XmlSerializer . 我正在使用XmlSerializer My class: 我的课:

[Serializable]
[XmlRoot(ElementName="MyClass")]
public class MyClass
{
    public string Value;
}

I would like to serialize it so that Value ends up as an attribute of a subelement named (for instance) "Text". 我想序列化它,以便Value最终作为名为(例如)“Text”的子元素的属性。

Desired outcome: 期望的结果:

<MyClass>
    <Text Value="3"/>
</MyClass>

But NOT (which would be the effect of marking Value as an XmlAttribute ) 但是NOT (将Value标记为XmlAttribute的效果)

<MyClass Value="3">
</MyClass>

And NOT (which would be the effect of marking Value as an XmlElement ): NOT (将Value标记为XmlElement的效果):

<MyClass>
    <Value>3</Value>
</MyClass>

How do I achieve this? 我该如何实现这一目标?

I am aware that I could change the type of Value from string to another serializable custom class. 我知道我可以将Value的类型从string更改为另一个可序列化的自定义类。

Unfortunately, I have plenty of such properties, so I'd need to create dozens of tiny classes. 不幸的是,我有很多这样的属性,所以我需要创建几十个小类。

Is there any quicker solution? 有没有更快的解决方案?


EDIT: 编辑:

In response to your comments: 回应你的意见:

  • No, not every property has to be serialized to a subelement named "Text". 不,并非每个属性都必须序列化为名为“Text”的子元素。 Subelement's name is unique and unambiguous. Subelement的名称是独特且明确的。

  • Sample output XML: 示例输出XML:

     <visibility> <site visible="yes"/> <comparator visible="no"/> <expiration days="7"/> <comment>blahblahblah</comment> <visibility> 
  • Sample class: 样本类:

!

[XmlRoot(ElementName="Visibility")]
public class Visibility
{
    [XPath("/site@visible")] // if only this was possible!
    public string OnSite
    {
        get { return SiteVisible ? "yes" : "no"; }
    }

    [XPath("/comparator@visible")] // as above...
    public string InComparator
    {
        get { return ComparatorVisible ? "yes" : "no"; }
    }

    [XmlIgnore]
    public bool SiteVisible;
    [XmlIgnore]
    public bool ComparatorVisible;

    [XPath("/expiration@days")] // as above...
    public int ExpiresAfterDays; 

    [XmlElement("comment")] // this is easy
    public string Comment;
}

Without changing the type of Value I think it's not possible. 在不改变Value类型的情况下,我认为这是不可能的。 You can add the attribute XmlElement(ElementName="Text") on Value but you will obtain a result similar to this: 您可以在Value上添加属性XmlElement(ElementName="Text") ,但您将获得与此类似的结果:

<MyClass> 
    <Text>3</Text> 
</MyClass> 

Edited: Another solution can involve XSLT transformation: you can generate the xml using .Net serialization and after apply a xml transformation. 编辑:另一个解决方案可以涉及XSLT转换:您可以使用.Net序列化生成xml并应用xml转换。

XslTransform myXslTransform = new XslTransform();
myXslTransform.Load(xsltDoc);
myXslTransform.Transform(sourceDoc, resultDoc);

The trasformation of my example should be something like this: 我的例子的变形应该是这样的:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
    <root>
        <xsl:apply-templates/>
    </root>
    </xsl:template>
    <xsl:template match="MyClass">
        <MyClass>
            <Text>
               <xsl:attribute name="Value">
                    <xsl:value-of select="Text"/>
               </xsl:attribute>
            </Text>
        </MyClass>
    </xsl:template>
</xsl:stylesheet>

For this sort of flexibility, you should really think about implementing IXmlSerializable as this gives you much more control: 为了这种灵活性,您应该考虑实现IXmlSerializable因为这样可以提供更多控制:

[XmlRoot("visibility")]
public class Visibility : IXmlSerializable
{
    public string Site;
    public string Comparator;
    public int Expiration;
    public string Comment;

    public XmlSchema GetSchema()
    {
        throw new NotImplementedException();
    }

    public void ReadXml(XmlReader reader)
    {
        // implement me if you want to deserialize too.
        throw new NotImplementedException();
    }

    public void WriteXml(XmlWriter writer)
    {
        WriteProperty(writer, "site", "visible", Site);
        WriteProperty(writer, "comparator ", "visible", Comparator);
        WriteProperty(writer, "expiration ", "days", Expiration);

        if (!string.IsNullOrEmpty(Comment))
        {
            writer.WriteElementString("comment", Comment);
        }
    }

    private void WriteProperty<T>(XmlWriter writer, string elementName, string attibuteName, T value)
    {
        if (value != null)
        {
            writer.WriteStartElement(elementName);
            writer.WriteAttributeString(attibuteName, value.ToString());
            writer.WriteEndElement();
        }
    }
}

Obviously, there is bit of manual work here, but it does allow you to keep all the serialization code in one place, rather than having a proliferation of smaller classes. 显然,这里有一些手动工作,但它确实允许您将所有序列化代码保存在一个地方,而不是增加较小的类。

The example above only implements serialization - you'd need to write an equivalent deserialize implementation if you need to deserialize from xml to your type. 上面的示例仅实现序列化 - 如果需要从xml反序列化到类型,则需要编写等效的反序列化实现。

Thanks for all the answers. 感谢所有的答案。 It's a pity that .NET XmlSerialization library does not allow for that (I think it should!). 遗憾的是,.NET XmlSerialization库不允许这样做(我认为它应该!)。 I'm looking for a solution as generic as possible. 我正在寻找尽可能通用的解决方案。

The best one I could come up with (best one considering the criteria of maximum genericity, while being reasonably quick to implement) is letting the XmlSerializer serialize my class the way it likes, then just convert the output, relocating certain elements into nested locations. 我能想出的最好的一个(考虑到最大通用性的标准,同时合理地快速实现)是让XmlSerializer自己喜欢的方式序列化我的类,然后只需转换输出,将某些元素重新定位到嵌套位置。

Something like that: 像这样的东西:

    /// <remarks>
    /// (angle brackets replaced with round ones to avoid confusing the XML-based documentation comments format)
    /// 
    /// Let input XML be:
    /// 
    ///     (root)
    ///         (days)3(/days)
    ///     (/root)
    ///     
    /// Calling Reposition on this input with mappings argument being:
    ///     (key) "days"
    ///     (value) { "time", "days" }
    ///     
    /// Returns:
    /// (root)
    ///     (time days="3" /)
    /// (/root)
    /// </remarks>        
    static XElement Reposition(XElement input, KeyValuePair<string, string[]>[] mappings)
    {
        var result = new XElement(input);
        foreach (var mapping in mappings)
        {
            var element = result.Element(mapping.Key);
            if (element == null)
            {
                continue;
            }
            var value = element.Value;
            element.Remove();

            var insertAt = result;
            foreach (var breadcrumb in mapping.Value)
            {
                if (breadcrumb == mapping.Value.Last())
                {
                    insertAt.Add(new XAttribute(breadcrumb, value));
                }
                else
                {
                    insertAt.Add(new XElement(breadcrumb));
                    insertAt = insertAt.Element(breadcrumb);
                }
            }
        }
        return result;
    }

I think I would combine it with a custom attribute (kind of similar to the XPath attribute that I wished would exist: see the sample code in my question), and enwrap this functionality in a serializer class of my own. 我想我会将它与自定义属性(类似于我希望存在的XPath属性类似:请参阅我的问题中的示例代码),并在我自己的序列化程序类中包装此功能。

Any comments / insights on this approach? 对此方法有何评论/见解?

I can think of a potential performance drawback (rewriting/reparsing the XML after each serialization), but the resulting pieces of XML aren't expected to be of large size, so this is probably negligible. 我可以想到一个潜在的性能缺陷(在每次序列化后重写/重新分析XML),但是由此产生的XML片段预计不会很大,所以这可能是微不足道的。

The question of deserialization does not bother me at this point (deserialization has already been implemented and is done quite "manually", by XPath and some utility methods). 反序列化的问题在这一点上并没有打扰我(反序列化已经实现并且通过XPath和一些实用方法完全“手动”完成)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM