1

I have this XML format I need to replicate:

<item>
    <attribute1>1</attribute1>
    <attribute2 something="true">
        2
    </attribute2>
    <attribute3 something="false">
        3
    </attribute3>
    <!-- goes on until attribute25 -->
</item>

I'm currently using something like this to achieve what I want:

Item.cs:

[XmlType(TypeName = "item")]
public class Item {

    [XmlElement("attribute1")]
    public CustomElement<string> Attribute1 { get; set; }

    [XmlElement("attribute2")]
    public CustomElement<string> Attribute2 { get; set; }

    [XmlElement("attribute3")]
    public CustomElement<string> Attribute3 { get; set; }

    // Etc Etc
}

CustomElement.cs:

/// <summary>
///     Represents a CustomElement.
/// </summary>
/// <typeparam name="T">The type for the value of the element.</typeparam>

public class CustomElement<T>
{
    [XmlIgnore] public T Value;

    [XmlText]
    public T XmlValue => Value;

    public CustomElement()
    {
    }

    public CustomElement(T value)
    {
        Value = value;
    }

    [XmlIgnore]
    public bool? Something { get; set; }

    [XmlAttribute("something")]
    public bool XmlSomething
    {
        get => Something != null && Something.Value;
        set => Something = value;
    }

    public bool XmlSomethingSpecified => Something.HasValue;

    public static implicit operator CustomElement<T>(T x)
    {
        return new CustomElement<T>(x);
    }
}

However, after serializing my Item I get:

<item>
    <attribute1 />
    <attribute2 />
    <attribute3 />
</item>

How do I fix my CustomElement class so the value doesn't get lost?

tyteen4a03
  • 1,812
  • 24
  • 45
  • You are serializing an instance of a class with no data. So add data to the instance of the class. – jdweng Dec 05 '17 at 07:48
  • @jdweng The class does have data inside, set by code outside of this code snippet. – tyteen4a03 Dec 05 '17 at 13:58
  • Are you sure? Error message indicates otherwise. I suspect you have two instances of the class a you are trying to serialize the one that is empty instead of the one that contains data. – jdweng Dec 05 '17 at 14:04
  • I've checked the debugger and there is definitely data inside. – tyteen4a03 Dec 05 '17 at 14:08

1 Answers1

3

You have several problems here:

  1. You are trying to serialize the XmlValue member as the element value for the element generated for an instance of CustomElement<T>, but you have defined it as a read-only expression-bodied member:

    public T XmlValue => Value;
    

    XmlSerializer will only serialize public read/write properties and fields, so you must add a setter as well as a getter:

    [XmlText]
    public T XmlValue { get => Value; set => Value = value; }
    

    Alternatively, to simplify your model you could eliminate XmlValue entirely and serialize Value directly by marking it with [XmlText]. XML serialization attributes can be applied to fields as well as properties.

  2. You are trying to suppress serialization of the <something> element when the underlying value is null using the {propertyName}Specified pattern. This pattern is primarily used for tracking whether or not an associated element is encountered, and so the xxxSpecified property must be marked with [XmlIgnore]:

    [XmlIgnore]
    public bool XmlSomethingSpecified => Something.HasValue;
    

    Or, since you don't actually need to track the presence of the <something> element, you could simplify your model by switching to the ShouldSerialize{PropertyName}() pattern:

    public bool ShouldSerializeXmlSomething() => Something.HasValue;
    

    The differences between the two patterns are described in ShouldSerialize() vs Specified Conditional Serialization Pattern.

  3. If your Item type is going to be the root element of your XML document, you must mark it with [XmlRoot("item")] to make the root element's name be <item>:

    [XmlRoot("item")]
    [XmlType(TypeName = "ci")]
    public class Item
    {
        // Etc Etc
    }
    
  4. In your c# code you have marked the XmlSomething property with [XmlElement] but in your XML something is shown as an element: <something>true</something>.

    If you really want it to be an element you must mark XmlSomething with [XmlElement]:

    [XmlElement("something")]
    public bool XmlSomething
    {
        get => Something != null && Something.Value;
        set => Something = value;
    }
    
  5. In your question, you show an example of a <something> element with a non-boolean textual value:

    <attribute3>
        3
        <something>text</something>
    </attribute3>
    

    I reckon this is a typo in the question, but if not, you will need to redesign your CustomElement<T> type to make Something be a string.

Sample working Roslyn .Net fiddle, and a second with the suggested simplifications for CustomElement<T>, and a third where <something> appears as a child element.

The classes from the second fiddle look like:

public class CustomElement<T>
{
    [XmlText]
    public T Value;

    public CustomElement()
    {
    }

    public CustomElement(T value)
    {
        Value = value;
    }

    [XmlIgnore]
    public bool? Something { get; set; }

    [XmlAttribute("something")]
    public bool XmlSomething
    {
        get => Something != null && Something.Value;
        set => Something = value;
    }

    public bool ShouldSerializeXmlSomething() => Something.HasValue;

    public static implicit operator CustomElement<T>(T x)
    {
        return new CustomElement<T>(x);
    }
}

[XmlRoot("item")]
[XmlType(TypeName = "ci")]
public class Item
{
    [XmlElement("attribute1")]
    public CustomElement<string> Attribute1 { get; set; }

    [XmlElement("attribute2")]
    public CustomElement<string> Attribute2 { get; set; }

    [XmlElement("attribute3")]
    public CustomElement<string> Attribute3 { get; set; }

    // Etc Etc
}
dbc
  • 104,963
  • 20
  • 228
  • 340
  • Thanks for the answers. Re 5: It is not a type. My class needs to support serialization of any primitive types like `bool`, `string`, `decimal` and `DateTime`. This is why `CustomElement` is a generic class. – tyteen4a03 Dec 05 '17 at 22:33
  • @tyteen4a03 - OK, but in your `CustomElement` you use the generic parameter for the *element value* not the *`` child element value*. Was that an error in the question? The question had other errors including invalid XML syntax, which I fixed. – dbc Dec 05 '17 at 22:36
  • Re 4 - That was an error on my part; `something` is an attribute, not an element. – tyteen4a03 Dec 05 '17 at 22:37
  • No, the value of the `` do map to a `CustomElement::Value`. The value will ultimately be a string, but I needed a way to transparently format `DateTime`s when serialized so this is the solution I've come up with. That custom behaviour is defined in a subclass of `CustomElement`, not shown here. – tyteen4a03 Dec 05 '17 at 22:44
  • @tyteen4a03 - I can't answer a question about something you don't show. In what you actually show, `XmlSomething` is hardcoded to be a `bool`. Anyway, is the question *How do I fix my `CustomElement` class so the value doesn't get lost?* answered? If so you might want to ask another question as the preferred format is [one question per post](https://meta.stackexchange.com/q/222735). – dbc Dec 05 '17 at 23:19
  • Terribly sorry for the confusion: the value can be of any type. `` can only be bool. I've edited my question to fix this. – tyteen4a03 Dec 05 '17 at 23:30
  • @tyteen4a03 - then in that case I believe I've answered the question. Is there a remaining problem? – dbc Dec 05 '17 at 23:49