Scott Hanselman

Making the XmlSerializer output alternate values for simple types

September 1, '05 Comments [4] Posted in ASP.NET | XmlSerializer
Sponsored By

Given a class like this:

public class Student
{
    public int SSN = 555555555;
    public decimal GPA = 0.1;
    public bool cool = false;
    public DateTime birthday = DateTime.Now;
    public long Height = 80;
}

The XmlSerializer will output XML like this:

<Student>
  <SSN>555555555</SSN>
  <GPA>0.1</GPA>
  <cool>false</cool>
  <birthday>2005-09-01T13:25:41.4964384-07:00</birthday>
  <Height>60</Height>
</Student>

As you'd expect. Note the standard 8601 DateTime and the word "false" for the boolean element "cool."

However, what if you want to output "bar" for false and "foo" for true? Here's one way that we've built into some code generation templates. The trick is that the actual boolean that the developer accesses is marked as XmlIgnore, and a parallel string value is marked with and [XmlElement] attribute that changes the name of the serialized element.

    1 using System;
    2 using System.Xml;
    3 using System.Collections;
    4 using System.Xml.Serialization;
    5 using System.IO;
    6 
    7 public class SerTest
    8 {
    9     public static void Main(string[] args)
   10     {
   11         Student[] students = new Student[2];
   12 
   13         students[0] = new Student();
   14         students[0].SSN = 555555555;
   15         students[0].GPA = 0.1M;
   16         students[0].Height = 60;
   17         students[0].cool = false;
   18 
   19         students[1] = new Student();
   20         students[1].SSN = 555554444;
   21         students[1].GPA = 3.1M;
   22         students[1].Height = 160;
   23         students[1].cool = true;
   24 
   25 
   26         XmlSerializer mySerializer = new 
   27             XmlSerializer(typeof(Student[] ));
   28 
   29         StreamWriter myWriter = new StreamWriter("Students.xml");
   30         mySerializer.Serialize(myWriter, students);
   31 
   32         myWriter.Close();
   33         StreamReader myReader = new StreamReader("Students.xml");
   34         Student[] somefolks = (Student[])mySerializer.Deserialize(myReader);
   35     }
   36 }
   37 
   38 public class Student
   39 {
   40     public int SSN;
   41 
   42     public decimal GPA;
   43 
   44     //It is assumed that the values "FOO" and "BAR" are put here by
   45     // a code generator, so it's not that big of a deal that they are 
   46     // duplicated in this code. Just trying to cover all bases. 
   47     [XmlIgnore]
   48     public bool cool
   49     {
   50         get
   51         {
   52             if (coolStringValue == null || coolStringValue.Length == 0) return false;
   53 
   54             if (CaseInsensitiveComparer.DefaultInvariant.Compare(coolStringValue,"FOO") == 0)
   55             {
   56                 return true;
   57             }
   58             else if (CaseInsensitiveComparer.DefaultInvariant.Compare(coolStringValue,"BAR") == 0)
   59             {
   60                 return false;
   61             }
   62             throw new ApplicationException(coolStringValue + " 
is neither FOO (true) nor BAR (false). This is an invalid state for this boolean.");
   63         }
   64         set
   65         {
   66             coolStringValue = (value ? "FOO" : "BAR" );            
   67         }
   68     }    
   69     [XmlElementAttribute(ElementName="cool",DataType="string")]
   70     public string coolStringValue = "bar";
   71 
   72     public DateTime birthday = DateTime.Now;
   73 
   74     public long Height = 80;
   75 }

This code listing outputs this XML:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfStudent xmlns:xsd="
http://www.w3.org/2001/XMLSchema
       xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance">
  <Student>
    <SSN>555555555</SSN>
    <GPA>0.1</GPA>
    <cool>BAR</cool>
    <birthday>2005-09-01T13:35:31.0041088-07:00</birthday>
    <Height>60</Height>
  </Student>
  <Student>
    <SSN>555554444</SSN>
    <GPA>3.1</GPA>
    <cool>FOO</cool>
    <birthday>2005-09-01T13:35:31.0041088-07:00</birthday>
    <Height>160</Height>
  </Student>
</ArrayOfStudent>

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb
Thursday, 01 September 2005 21:15:20 UTC
Thanks, Scott! Very creative. :) This saga with XML-serializing booleans and enums drove me to the conclusion that it's a PITA all around.

Eventually, I went with Daniel's tip (http://weblogs.asp.net/cazzu/archive/2003/10/21/32822.aspx), took apart the produced serializer code, cleaned it and rolled it into a codegen template. It's a hassle, but every approach I've looked at has some insanity to it. Having a public string field hanging out of a class makes me nervous. :|

Thanks again!
Friday, 02 September 2005 04:43:32 UTC
Thank you for this technic ;) Very nice.
Friday, 02 September 2005 07:21:34 UTC
Hey Scott,

Why not use an enum for the coolStringValue (coolEnumValue) to encourage strong(er) typing? Additionally, if your class was used in a web service (as many people reading this post will) the possible values will be 'discoverable' in the WSDL...
Josh Twist
Friday, 02 September 2005 12:04:55 UTC
Scott, i see you are on path of redemption :) That just first step on long road to realize why default XmlSerializer is worst piece of garbage inflicted on .NET. Its not just bad, it forces everybody to write incredibly bad code.


public class Student : IXmlSerializable
{
public int SSN = 555555555;
public decimal GPA = 0.1;
public bool cool = false;
public DateTime birthday = DateTime.Now;
public long Height = 80;

virtual public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement("Student");
writer.WriteElementString("SSN",this.SSN.ToString());
writer.WriteElementString("GPA",this.GPA);
writer.WriteElementString("cool",cool?"foo":"bar");
writer.WriteElementString("bday",this.birthday.ToString());
writer.WriteElementString("height",this.Height.ToString());
writer.WriteEndElement();
}

virtual public void ReadXml(System.Xml.XmlReader reader)
{
//...etc...
}
}

Which version is cleaner, easier to read, easier to maintain, faster, no generated assemblies, and completely collocates all XML in/out logic?

I’m advocating for years: toss attributes/assembly generation overboard right from the start. It never works. It can’t work by definition on good designs.
Max S
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.