Question

I'm sending xml to another program, which expects boolean flags as "yes" or "no", rather than "true" or "false".

I have a class defined like:

[XmlRoot()]
public class Foo {
    public bool Bar { get; set; }
}

When I serialize it, my output looks like this:

<Foo><Bar>true</Bar></Foo>

But I would like it to be this:

<Foo><Bar>yes</Bar></Foo>

Can I do this at the time of serialization? I would prefer not to have to resort to this:

[XmlRoot()]
public class Foo {
    [XmlIgnore()]
    public bool Bar { get; set; }

    [XmlElement("Bar")]
    public string BarXml { get { return (Bar) ? "yes" : "no"; } }
}

Note that I also want to be able to deserialize this data back again.

Was it helpful?

Solution

Ok, I've been looking into this some more. Here's what I've come up with:

// use this instead of a bool, and it will serialize to "yes" or "no"
// minimal example, not very robust
public struct YesNo : IXmlSerializable {

    // we're just wrapping a bool
    private bool Value;

    // allow implicit casts to/from bool
    public static implicit operator bool(YesNo yn) {
        return yn.Value;
    }
    public static implicit operator YesNo(bool b) {
        return new YesNo() {Value = b};
    }

    // implement IXmlSerializable
    public XmlSchema GetSchema() { return null; }
    public void ReadXml(XmlReader reader) {
        Value = (reader.ReadElementContentAsString() == "yes");
    }
    public void WriteXml(XmlWriter writer) {
        writer.WriteString((Value) ? "yes" : "no");
    }
}

Then I change my Foo class to this:

[XmlRoot()]
public class Foo {      
    public YesNo Bar { get; set; }
}

Note that because YesNo is implicitly castable to bool (and vice versa), you can still do this:

Foo foo = new Foo() { Bar = true; };
if ( foo.Bar ) {
   // ... etc

In other words, you can treat it like a bool.

And w00t! It serializes to this:

<Foo><Bar>yes</Bar></Foo>

It also deserializes correctly.

There is probably some way to get my XmlSerializer to automatically cast any bools it encounters to YesNos as it goes - but I haven't found it yet. Anyone?

OTHER TIPS

Very simple. Use a surrogate property. Apply XmlIgnore on the actual property. The surrogate is a string, and must use the XmlElement attribute that takes a element-name override. Specify the name of the actual property in the override. The surrogate property serializes differently based on the value of the actual property. You must also provide a setter for the Surrogate, and the setter should set the actual property appropriately, for whatever value it serialized. In other words it needs to go both ways.

Snip:

    public class SomeType 
    {

        [XmlElement]
        public int IntValue;

        [XmlIgnore]
        public bool Value;

        [XmlElement("Value")]
        public string Value_Surrogate {
            get { return (Value)? "Yes, definitely!":"Absolutely NOT!"; }
            set { Value= (value=="Yes, definitely!"); }
        }

    }

click here for full compilable source example.

Making a bool value serialize as "yes" or "no" changes the data type from being a boolean at all. Instead, can you add a separate property which evaluates a boolean and returns "yes" or "no" as appropriate for it's data type? Maybe you could even force "yes" or "no" by making the return type be an enum which only specifies those values.

public YesOrNo DoYouLoveIt
{
    get { return boolToEvaluate ? YesOrNo.Yes : YesOrNo.No; }
}

That might be overkill, but might answer your need. The only reason I bring up an enum for such a simple value is you'd be restricting the values vs. allowing any string.

I use the property method, but instead of checking to see if the string is equal to yes or no, I prefer to check if the string starts with (case insensitive) "YT1". This allows the file to contain true, True, t, T, y, Y, yes, Yes, 1, etc. all which will evaluate to true. While I can specify that false is false, False, f, F, n, N, no, No, 0, etc., anything that doesn't match the true still evaluates to false.

Your property example is probably the simplest way you could do it. If it helps, I believe you don't need to make it a public property, since the attribute implements ISerializable on the class behind your back. To enable deserialization, you should be able to just implement set { Bar = value == "yes"; }

@Blorgbeard: If you have more then one of these YesNo classes in an object class, make sure to read the entire element.

public void ReadXml(XmlReader reader)
{
    string element = reader.ReadOuterXml();
    int startIndex = element.IndexOf('>') + 1;
    int length = element.LastIndexOf('<') - startIndex;

    string text = (element.Substring(startIndex, length).ToLowerInvariant();

    Value = (text == "yes");
}

Otherwise this might cause problems.

The ReadXml method must reconstitute your object using the information that was written by the WriteXml method.

When this method is called, the reader is positioned at the start of the element that wraps the information for your type. That is, just before the start tag that indicates the beginning of a serialized object. When this method returns, it must have read the entire element from beginning to end, including all of its contents. Unlike the WriteXml method, the framework does not handle the wrapper element automatically. Your implementation must do so. Failing to observe these positioning rules may cause code to generate unexpected runtime exceptions or corrupt data.

What you're needing to do sounds more like a display issue. If your application allows, you will be better off keeping the data type as a boolean and displaying Yes/No in your user interface.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top