21

I'm having an issue with deserializing an XML file with boolean values. The source XML files I'm deserializing were created from a VB6 app, where all boolean values are capitalized (True, False). When I try to deserialize the XML, I'm getting a

System.FormatException: The string 'False' is not a valid Boolean value.

Is there a way to say ignore case with an attribute?

franzlorenzon
  • 5,845
  • 6
  • 36
  • 58
shannon.stewart
  • 974
  • 1
  • 8
  • 18

10 Answers10

10

Based on another stack overflow question you can do:

public class MySerilizedObject
{
    [XmlIgnore]
    public bool BadBoolField { get; set; }

    [XmlElement("BadBoolField")]
    public string BadBoolFieldSerialize
    {
        get { return this.BadBoolField ? "True" : "False"; }
        set
        {
            if(value.Equals("True"))
                this.BadBoolField = true;
            else if(value.Equals("False"))
                this.BadBoolField = false;
            else
                this.BadBoolField = XmlConvert.ToBoolean(value);
        }
    }
}
Community
  • 1
  • 1
jeoffman
  • 1,303
  • 10
  • 23
5

Here is a much cleaner solution I came up with based on some other questions I found. It is much cleaner because then you don't need to anything in your code except declare the type as SafeBool, like this:

public class MyXMLClass
{
    public SafeBool Bool { get; set; }
    public SafeBool? OptionalBool { get; set; }
}

you can even make them optional and it all just works. This SafeBool struct will handle any case variants of true/false, yes/no or y/n. It will always serialize as true/false, however I have other structs similar to this I use to serialize specifically as y/n or yes/no when the schema requires that (ie: BoolYN, BoolYesNo structs).

using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace AMain.CommonScaffold
{
    public struct SafeBool : IXmlSerializable
    {
        private bool _value;

        /// <summary>
        /// Allow implicit cast to a real bool
        /// </summary>
        /// <param name="yn">Value to cast to bool</param>
        public static implicit operator bool(
            SafeBool yn)
        {
            return yn._value;
        }

        /// <summary>
        /// Allow implicit cast from a real bool
        /// </summary>
        /// <param name="b">Value to cash to y/n</param>
        public static implicit operator SafeBool(
            bool b)
        {
            return new SafeBool { _value = b };
        }

        /// <summary>
        /// This is not used
        /// </summary>
        public XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Reads a value from XML
        /// </summary>
        /// <param name="reader">XML reader to read</param>
        public void ReadXml(
            XmlReader reader)
        {
            var s = reader.ReadElementContentAsString().ToLowerInvariant();
            _value = s == "true" || s == "yes" || s == "y";
        }

        /// <summary>
        /// Writes the value to XML
        /// </summary>
        /// <param name="writer">XML writer to write to</param>
        public void WriteXml(
            XmlWriter writer)
        {
            writer.WriteString(_value ? "true" : "false");
        }
    }
}
Kendall Bennett
  • 2,353
  • 1
  • 17
  • 18
  • See my search and replace answer. You have a lot of code that may have better O(n), but sometimes the problem just requires a Ross Perot solution. open the hood and fix it. – ATL_DEV Dec 01 '17 at 02:14
  • I like this solution but for completeness, I also implemented some other interfaces, operators and methods. I'll edit your answer to include them (I also renamed the struct to FlexibleBool) and the unit tests for them. – Dewey Vozel Dec 12 '18 at 17:22
4

You could read that value as a string into a string field, then have a readonly bool field that had an if statement in it to return bool true or false.

For example (using c#):

public bool str2bool(string str)
{
  if (str.Trim().ToUpper() == "TRUE")
      return true;
  else
      return false;
}

And you can use it in the template:

<xsl:if test="user:str2bool($mystr)">
Marcin
  • 1,889
  • 2
  • 16
  • 20
Shiraz Bhaiji
  • 64,065
  • 34
  • 143
  • 252
4

Instead of using True or False, use 0 or 1. It will work for Boolean.

4

I have an xml with many booleans and I didn't want to end up having so many duplicate boolean properties, so I tried a different approach of providing a custom xml reader to do the work:

public class MyXmlReader : XmlTextReader
{
    public MyXmlReader(TextReader reader) : base(reader) { }
    public override string ReadElementString()
    {
        var text = base.ReadElementString();

        // bool TryParse accepts case-insensitive 'true' and 'false'
        if (bool.TryParse(text, out bool result))
        {
            text = XmlConvert.ToString(result);
        }

        return text;
    }
}

and use with:

using (var sr = new StringReader(text))
using (var r = new MyXmlReader(sr))
{
    var result = serializer.Deserialize(r);
}
ewolfman
  • 361
  • 3
  • 4
  • 18
  • I'm in .NET Core 2.2 and had to override `ReadElementContentAsString()` instead of `ReadElementString()`, but other than that this does work as expected in my initial tests. – DJ Grossman Dec 05 '18 at 18:50
3

There isn't. The XML Serializer works with XML Schema, and "True" and "False" are not valid booleans.

You could either use an XML Transform to convert these two values, or you could implement the IXmlSerializable interface and do the serialization and deserialization on your own.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
  • you're right John, thats what happens when you answer before eating lunch :\..but to be fair your suggestion of Xml Transform could potentially also transform a value thats not intended to be a boolean. – Stan R. Jul 20 '09 at 20:03
  • Actually, no. I would have him explicitly reference only the attributes and/or elements which are boolean. I didn't mean to transform every attribute. – John Saunders Jul 20 '09 at 20:08
2

There is an incredibly simple and short solution in a special case.

I have encountered a similar problem today, with an externally given XML file that contains the values TRUE/FALSE which are supposed to have boolean meaning.

If it is not mandatory for one's application that the deserialized document contains a native bool, but it's merely about deserializing it to something that is constrained to any two alternative values, then one can simply use an enum (here for an Attribute by way of example):

public enum BOOL {FALSE, TRUE};

public MyClass
{
    [XmlAttribute]
    public BOOL MyStrangeBooleanAttribute {get; set;}
}

This will just deserialize without any problem from an Element like this

<MyClass MyStrangeBooleanAttribute = "TRUE" />

Of course it is not possible then to use the property in code for direct boolean operations, like

if (MyStrangeBooleanAttribute) // ... doesn't work

I think it could probably be possible to handle this by defining an implicit conversion, but I haven't tested it because I don't need it.

oliver
  • 2,771
  • 15
  • 32
1

I dont think there is. You could make it string and do a comparison (String.Compare) by setting the ignoreCase value to true.

SwDevMan81
  • 48,814
  • 22
  • 151
  • 184
1

Don't bother fixing a broken xml system or fighting XmlSerializer, especially for something so trivial. It's not worth it. VB6 isn't coming back anytime soon.

Instead, get hold of the document before it's deserialized and change the values. If you're worried about changing them outside the tags, then use regular expressions or include the angle brackets in the values.

    xml = xml.Replace("True", "true").Replace("False", "false");

It's not going to win any awards for elegance, but it gets you back to work. Sometimes you just have to blue collar it.

As for performance, yes, you are reiterating through the string O(n), but since the replacement strings are the same length, it doesn't require any moving string elements around. Moreover, depending on the implementation, there might be a greater overhead in modifying XmlSerializer.

ATL_DEV
  • 9,256
  • 11
  • 60
  • 102
  • You are assuming you have easy access to the original HTML to be able to do a search and replace on, so your answer is not a good one. Not to mention if the XML packet is large, your code is going to produce a huge temporary string twice, because strings in .net are immutable. – Kendall Bennett Dec 02 '17 at 04:01
  • @KendallBennett Well aren't you also assuming he has access to the classes? I have never seen a case where you can deserialize data without having access to the xml input. If you're really concerned about performance and memory, you really should avoid using XmlSerializer as it uses reflection which is one of the most expensive operations in .NET. There is nothing about my solution that says it can't work on streaming data. My point is that naive implementations sometimes are the best ones. I've learned this the hardway. – ATL_DEV Dec 02 '17 at 05:04
  • You can't simply block replace everything in the XML that is True with true and False with false. It is certainly naive and certainly going to break on some type of responses. What if there was something stored in there that was case sensitive and had the word True in it? Suddenly that no longer works ... – Kendall Bennett Dec 03 '17 at 08:04
  • Yes, that's why I mentioned searching for a pattern like ">False<" if that's a concern. Again, my implementation is ugly, but handles incompatibilities. Generally, this is called normalization and is a standard practice when dealing with incompatibilities and inconsistencies. It should be seperated from the standard implementation. Ideally, it should be done outside the program as a preprocess. Your implementation would be ideal if "True" and "False" were standard, but XmlSerilizer didn't support it. – ATL_DEV Dec 03 '17 at 13:49
0

I stumbled upon the same issue, and based on the answer by jman, I solved it this way:

    [XmlIgnore]
    public bool BadBoolField { get; set; }

    [XmlAttribute("badBoolField")]
    public string BadBoolFieldSerializable
    {
        get
        {
            return this.BadBoolField.ToString();
        }
        set
        {
            this.BadBoolField= Convert.ToBoolean(value);
        }
    }

Be aware this is not necessarily by XML / Serialization specification, but it works good and it can handle a widespread conversion values (i.e. string like "True", "true", if you would replace the constraint of being a string it could handle numbers as well).

this.myself
  • 2,101
  • 2
  • 22
  • 36