views:

420

answers:

6

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.

+3  A: 

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.

Ian Suttle
Wow... is "yes" is too simple or something?
Simon Buchan
Haha - i know. It's a matter of allowing any old string using a string data type, or restricting it to particular values.
Ian Suttle
:P. Remember Enum.(Try)Parse() is case-sensitive....
Simon Buchan
Also, I just realized I sounded like a total dick :'(
Simon Buchan
No problem - I didn't take it that way :).
Ian Suttle
Bools already serialize to strings - they just happen to be "true" and "false", rather than "yes" and "no".. I really want to do this with as little work as possible in the actual class to be serialized, since there are many such classes..
Blorgbeard
When you're dealing with XML it's all strings isn't it? This feels like one of those problems where you either have to put up with more work or change the problem so it's no longer a problem. I'd love to see someone answer this for you so we can all learn something new.
Ian Suttle
A: 

what about implementing OnSerializing and OnDeserializing methods?

Oscar Cabrero
A: 

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.

Jim H.
Uhh, he's talking about serializing to XML...
Simon Buchan
"I'm sending xml to another program" sounds like he doesn't control the 'other' program.
SCdF
+2  A: 

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.

Cheeso
good catch. Edited.
Cheeso
Yup, but that's the example that I gave in the question.. I want to know if there's a *better* way :)
Blorgbeard
Better? don't think so.
Cheeso
A: 

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

Simon Buchan
It seems it does have to be public, unless I'm Doing It Wrong [tm]..
Blorgbeard
+4  A: 

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.ReadString() == "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?

Blorgbeard