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

发布于 2025-01-02 10:41:53 字数 1774 浏览 1 评论 0原文

我正在使用 XmlSerializer。我的类:

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

我想对其进行序列化,以便 Value 最终作为名为(例如)“Text”的子元素的属性。

期望的结果:

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

但是NOT(这是将Value标记为XmlAttribute的效果)

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

并且NOT(这将 Value 标记为 XmlElement 的效果):

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

如何实现此目的?

我知道我可以将 Value 的类型从字符串更改为另一个可序列化的自定义类。

不幸的是,我有很多这样的属性,因此我需要创建数十个小班。

有没有更快的解决方案?


编辑:

回应您的评论:

  • 不,并非每个属性都必须序列化为名为“Text”的子元素。子元素的名称是唯一且明确的。

  • 示例输出 XML:

    <预><代码><可见性>; <站点可见=“是”/> <比较器可见=“否”/> <过期天数=“7”/> <评论>blahblahblah <可见性>
  • 示例类:

[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;
}

I'm using an 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".

Desired outcome:

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

But NOT (which would be the effect of marking Value as an XmlAttribute)

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

And NOT (which would be the effect of marking Value as an 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.

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". Subelement's name is unique and unambiguous.

  • Sample output 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;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

情何以堪。 2025-01-09 10:41:53

如果不改变 Value 的类型,我认为这是不可能的。您可以在 Value 上添加属性 XmlElement(ElementName="Text"),但您将获得与此类似的结果:

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

已编辑:
另一种解决方案可能涉及 XSLT 转换:您可以使用 .Net 序列化生成 xml,然后应用 xml 转换。

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

我的例子的改造应该是这样的:

<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>

Without changing the type of Value I think it's not possible. You can add the attribute XmlElement(ElementName="Text") on Value but you will obtain a result similar to this:

<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.

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>
埖埖迣鎅 2025-01-09 10:41:53

对于这种灵活性,您应该真正考虑实现 IXmlSerialized,因为这可以为您提供更多控制:

[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();
        }
    }
}

显然,这里需要一些手动工作,但它确实允许您将所有序列化代码保留在一个地方,而不是小班激增。

上面的示例仅实现序列化 - 如果您需要从 xml 反序列化为您的类型,则需要编写等效的反序列化实现。

For this sort of flexibility, you should really think about implementing IXmlSerializable as this gives you much more control:

[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.

如若梦似彩虹 2025-01-09 10:41:53

感谢所有的答案。遗憾的是 .NET XmlSerialization 库不允许这样做(我认为它应该!)。我正在寻找尽可能通用的解决方案。

我能想到的最好的方法(考虑到最大通用性标准的最佳方法,同时可以相当快地实现)是让 XmlSerializer 按照它喜欢的方式序列化我的类,然后只转换输出,将某些元素重新定位到嵌套位置。

类似这样的事情:

    /// <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;
    }

我想我会将它与自定义属性结合起来(类似于我希望存在的 XPath 属性:请参阅我的问题中的示例代码),并将此功能封装在我自己的序列化器类。

对这种方法有什么评论/见解吗?

我可以想到潜在的性能缺陷(每次序列化后重写/重新解析 XML),但生成的 XML 片段预计不会很大,因此这可能可以忽略不计。

反序列化的问题此时并不困扰我(反序列化已经实现,并且是通过 XPath 和一些实用方法“手动”完成的)。

Thanks for all the answers. It's a pity that .NET XmlSerialization library does not allow for that (I think it should!). 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.

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.

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.

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).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文