How do I serialize an enum value as an int?

Viewed 54421

I want to serialize my enum-value as an int, but i only get the name.

Here is my (sample) class and enum:

public class Request {
    public RequestType request;
}

public enum RequestType
{
    Booking = 1,
    Confirmation = 2,
    PreBooking = 4,
    PreBookingConfirmation = 5,
    BookingStatus = 6
}

And the code (just to be sure i'm not doing it wrong)

Request req = new Request();
req.request = RequestType.Confirmation;
XmlSerializer xml = new XmlSerializer(req.GetType());
StringWriter writer = new StringWriter();
xml.Serialize(writer, req);
textBox1.Text = writer.ToString();

This answer (to another question) seems to indicate that enums should serialize to ints as default, but it doesn't seem to do that. Here is my output:

<?xml version="1.0" encoding="utf-16"?>
<Request xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <request>Confirmation</request>
</Request>

I have been able to serialize as the value by putting an "[XmlEnum("X")]" attribute on every value, but this just seems wrong.

7 Answers

The easiest way is to use [XmlEnum] attribute like so:

[Serializable]
public enum EnumToSerialize
{
    [XmlEnum("1")]
    One = 1,
    [XmlEnum("2")]
    Two = 2
}

This will serialize into XML (say that the parent class is CustomClass) like so:

<CustomClass>
  <EnumValue>2</EnumValue>
</CustomClass>

Most of the time, people want names, not ints. You could add a shim property for the purpose?

[XmlIgnore]
public MyEnum Foo {get;set;}

[XmlElement("Foo")]
[EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
public int FooInt32 {
    get {return (int)Foo;}
    set {Foo = (MyEnum)value;}
}

Or you could use IXmlSerializable, but that is lots of work.

Please see the full example Console Application program below for an interesting way to achieve what you're looking for using the DataContractSerializer:

using System;
using System.IO;
using System.Runtime.Serialization;

namespace ConsoleApplication1
{
    [DataContract(Namespace="petermcg.wordpress.com")]
    public class Request
    {
        [DataMember(EmitDefaultValue = false)]
        public RequestType request;
    }

    [DataContract(Namespace = "petermcg.wordpress.com")]
    public enum RequestType
    {
        [EnumMember(Value = "1")]
        Booking = 1,
        [EnumMember(Value = "2")]
        Confirmation = 2,
        [EnumMember(Value = "4")]
        PreBooking = 4,
        [EnumMember(Value = "5")]
        PreBookingConfirmation = 5,
        [EnumMember(Value = "6")]
        BookingStatus = 6
    }

    class Program
    {
        static void Main(string[] args)
        {
            DataContractSerializer serializer = new DataContractSerializer(typeof(Request));

            // Create Request object
            Request req = new Request();
            req.request = RequestType.Confirmation;

            // Serialize to File
            using (FileStream fileStream = new FileStream("request.txt", FileMode.Create))
            {
                serializer.WriteObject(fileStream, req);
            }

            // Reset for testing
            req = null;

            // Deserialize from File
            using (FileStream fileStream = new FileStream("request.txt", FileMode.Open))
            {
                req = serializer.ReadObject(fileStream) as Request;
            }

            // Writes True
            Console.WriteLine(req.request == RequestType.Confirmation);
        }
    }
}

The contents of request.txt are as follows after the call to WriteObject:

<Request xmlns="petermcg.wordpress.com" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <request>2</request>
</Request>

You'll need a reference to the System.Runtime.Serialization.dll assembly for DataContractSerializer.

Take a look at the System.Enum class. The Parse method converts a string or int representation into the Enum object and the ToString method converts the Enum object to a string which can be serialized.

For me these solutions were not that satisfying. I like general solutions where i don't have to adjust them, when adjusting the enum values. So i created the following solution, using the XmlAttributeOverrides.

Solution 1

    public static XmlAttributeOverrides ReflectionAddXmlEnumAttributes(Type baseType, XmlAttributeOverrides overrides = null)
    {
        if (overrides == null) overrides = new XmlAttributeOverrides();
        // traversing all serializable members
        var filteredFields = baseType.GetFields()
            .Where(f =>
                (f.Attributes.HasFlag(FieldAttributes.Public) &&
                 !f.Attributes.HasFlag(FieldAttributes.Static) &&
                 !f.CustomAttributes.Any(
                     a => a.AttributeType.IsAssignableFrom(typeof(XmlIgnoreAttribute)))));
        var filteredProperties = baseType.GetProperties()
            .Where(f =>
                (f.GetMethod?.Attributes.HasFlag(MethodAttributes.Public) ?? false) &&
                !f.GetMethod.Attributes.HasFlag(MethodAttributes.Static) &&
                (f.SetMethod?.Attributes.HasFlag(MethodAttributes.Public) ?? false) &&
                !f.SetMethod.Attributes.HasFlag(MethodAttributes.Static) &&
                !f.CustomAttributes.Any(
                    a => a.AttributeType.IsAssignableFrom(typeof(XmlIgnoreAttribute))));

        var classMemberTypes = filteredFields.Select(f => f.FieldType)
            .Concat(filteredProperties.Select(p => p.PropertyType));

        foreach (var memberType in classMemberTypes)
        {
            // proceed the same way for sub members
            ReflectionAddXmlEnumAttributes(memberType, overrides);
            if (!memberType.IsEnum) continue;
            var enumFields = memberType.GetFields();
            foreach (var enumFieldInfo in enumFields)
            {
                if (!enumFieldInfo.IsLiteral) continue;
                // add attribute-overrides for every enum-literal
                var name = enumFieldInfo.Name;
                if (overrides[memberType, name] != null) continue;
                var integer = enumFieldInfo.GetRawConstantValue();
                var attribute = new XmlAttributes
                {
                    XmlEnum = new XmlEnumAttribute(integer.ToString()),
                };
                overrides.Add(memberType, name, attribute);
            }
        }
        return overrides;
    }

    public static T MyDeserialize<T>(string filePath)
    {
        var overrides = ReflectionAddXmlEnumAttributes(typeof(T));

        var serializer = new XmlSerializer(typeof(T), overrides);
        using (var fileStream = new FileStream(filePath, FileMode.Open, System.IO.FileAccess.Read))
        {
            var deserialized = serializer.Deserialize(fileStream);
            fileStream.Close();
            return (T) deserialized;
        }
    }

    public static void MySerialization<T>(T serializeObject, string filePath)
    {
        var overrides = ReflectionAddXmlEnumAttributes(typeof(T));
        var serializer = new XmlSerializer(typeof(T), overrides);
        using (var writer = new StreamWriter(filePath))
        {
            serializer.Serialize(writer, serializeObject);
            writer.Close();
        }
    }

For me the disadvantage of this solution is, that it's quiet much code and it then can handle only numerical types. I search for a solution, where it is possible to handle different strings for one enum literal, so it is possible to accept the numerical representation as also the name of the enum.

Solution 2

Because my first solution was not long enough, i created another solution, so i have my own serializer class, that can be used for accepting all kinds of serialized enums (string as also the number interpretation).

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Policy;
using System.Xml.Serialization;

namespace MyApp.Serializer
{
    public class XmlExtendedSerializer : XmlSerializer
    {
        public SerializerDirection Mode { get; }
        public XmlAttributeOverrides Overrides { get; }
        public Type BaseType { get; }
        public XmlExtendedSerializer(SerializerDirection mode, Type type, XmlAttributeOverrides overrides, Type[] extraTypes, XmlRootAttribute root, string defaultNamespace, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out _, serializeEnumsAsNumber, overrides), extraTypes, root, defaultNamespace)
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrides;
        }

        public XmlExtendedSerializer(SerializerDirection mode, Type type, XmlRootAttribute root, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out var overrideCreated, serializeEnumsAsNumber), null, root, null)
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrideCreated;
        }

        public XmlExtendedSerializer(SerializerDirection mode, Type type, Type[] extraTypes, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out var overrideCreated, serializeEnumsAsNumber), extraTypes, null, null)
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrideCreated;
        }

        public XmlExtendedSerializer(SerializerDirection mode, Type type, XmlAttributeOverrides overrides, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out var overrideCreated, serializeEnumsAsNumber, overrides))
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrideCreated;
        }

        public XmlExtendedSerializer(XmlTypeMapping xmlTypeMapping)
        {
            throw new NotImplementedException("This method is not supported by this wrapper.");
        }

        public XmlExtendedSerializer(SerializerDirection mode, Type type, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out var overrideCreated, serializeEnumsAsNumber))
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrideCreated;
        }

        public XmlExtendedSerializer(SerializerDirection mode, Type type, string defaultNamespace, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out var overrideCreated, serializeEnumsAsNumber), null, null, defaultNamespace)
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrideCreated;
        }

        public XmlExtendedSerializer(SerializerDirection mode, Type type, XmlAttributeOverrides overrides, Type[] extraTypes, XmlRootAttribute root, string defaultNamespace, string location, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out var overrideCreated, serializeEnumsAsNumber, overrides), extraTypes, root, defaultNamespace, location)
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrideCreated;
        }

        public XmlExtendedSerializer(SerializerDirection mode, Type type, XmlAttributeOverrides overrides, Type[] extraTypes, XmlRootAttribute root, string defaultNamespace, string location, Evidence evidence, bool serializeEnumsAsNumber = true) : base(type, AddXmlEnumAttributes(mode, type, out var overrideCreated, serializeEnumsAsNumber, overrides), extraTypes, root, defaultNamespace, location, evidence)
        {
            BaseType = type;
            Mode = mode;
            Overrides = overrideCreated;
        }

        public new object Deserialize(Stream stream)
        {
            if (Mode != SerializerDirection.Deserialize) throw new NotSupportedException("Wrong mode.");
            UnknownElement += ConvertEnumEvent;
            return base.Deserialize(stream);
        }

        public new void Serialize(TextWriter writer, object o)
        {
            if (Mode != SerializerDirection.Serialize) throw new NotSupportedException("Wrong mode.");
            base.Serialize(writer, o);
        }
        private static XmlAttributeOverrides AddXmlEnumAttributes(SerializerDirection mode, Type baseType, out XmlAttributeOverrides outOverrides, bool serializeEnumsAsNumber = true, XmlAttributeOverrides overrides = null)
        {
            if (overrides == null) overrides = new XmlAttributeOverrides();
            // traversing all serializable members
            var filteredFields = baseType.GetFields()
                .Where(f =>
                    (f.Attributes.HasFlag(FieldAttributes.Public) &&
                     !f.Attributes.HasFlag(FieldAttributes.Static) &&
                     !f.CustomAttributes.Any(
                         a => a.AttributeType.IsAssignableFrom(typeof(XmlIgnoreAttribute)))));
            var filteredProperties = baseType.GetProperties()
                .Where(f =>
                    (f.GetMethod?.Attributes.HasFlag(MethodAttributes.Public) ?? false) &&
                    !f.GetMethod.Attributes.HasFlag(MethodAttributes.Static) &&
                    (f.SetMethod?.Attributes.HasFlag(MethodAttributes.Public) ?? false) &&
                    !f.SetMethod.Attributes.HasFlag(MethodAttributes.Static) &&
                    !f.CustomAttributes.Any(
                        a => a.AttributeType.IsAssignableFrom(typeof(XmlIgnoreAttribute))));

            foreach (var member in filteredFields.Cast<object>().Concat(filteredProperties))
            {
                var memberType = (member as FieldInfo)?.FieldType ?? ((PropertyInfo) member).PropertyType;
                var name = (member as FieldInfo)?.Name ?? ((PropertyInfo)member).Name;

                // proceed the same way for sub members
                AddXmlEnumAttributes(mode, memberType, out _ , serializeEnumsAsNumber, overrides);
                var deepEnumType = Nullable.GetUnderlyingType(memberType);
                var isNullable = deepEnumType != null;
                if (!isNullable) deepEnumType = memberType;

                if (!deepEnumType.IsEnum) continue;
                if (mode == SerializerDirection.Deserialize) // set ignore for enums and mark them for our own deserializer
                {
                    var attributeIgnore = new XmlEnumConvertAttribute // with custom type to detect
                    {
                        XmlIgnore = true, // ignore all enums
                    };
                    overrides.Add(baseType, name, attributeIgnore);
                }
                else if (serializeEnumsAsNumber) // serialize as number
                {
                    var enumFields = deepEnumType.GetFields();
                    foreach (var enumFieldInfo in enumFields)
                    {
                        if (!enumFieldInfo.IsLiteral) continue;
                        // add attribute-overrides for every enum-literal
                        var literalName = enumFieldInfo.Name;
                        if (overrides[memberType, literalName] != null) continue;
                        var integer = enumFieldInfo.GetRawConstantValue();
                        var attribute = new XmlAttributes
                        {
                            XmlEnum = new XmlEnumAttribute(integer.ToString()) // sets the number as output value
                        };
                        overrides.Add(memberType, literalName, attribute);
                    }
                }
            }

            outOverrides = overrides;
            return overrides;
        }

        // will be triggered on unknown xml elements are detected (enums are now marked as not serializable so they are unknown)
        private void ConvertEnumEvent(object sender, XmlElementEventArgs e)
        {
            var memberName = e.Element.Name; // enum property/field name
            var targetObject = e.ObjectBeingDeserialized;
            var baseType = targetObject.GetType(); // type of including class
            if (!(Overrides[baseType, memberName] is XmlEnumConvertAttribute)) return; // tag is really unknown
            var text = e.Element.InnerText; // the value text from xml
            var member = baseType.GetField(memberName);
            var property = baseType.GetProperty(memberName);
            var enumType = member?.FieldType ?? property.PropertyType;
            enumType = Nullable.GetUnderlyingType(enumType) ?? enumType;
            var newValue = string.IsNullOrEmpty(text) ?
                null :
                Enum.Parse(enumType, text);  // enum parser accepts also string type and number-type
            property?.SetValue(targetObject, newValue);
            member?.SetValue(targetObject, newValue);
        }

        // custom type to detect on event, that this property was not ignored intentionally
        private class XmlEnumConvertAttribute : XmlAttributes { }
    }

    public enum SerializerDirection
    {
        Serialize,
        Deserialize
    }
}

With the call like

var serializer = new XmlExtendedSerializer(SerializerDirection.Serialize, typeof(T));
using (var writer = new StreamWriter(path))
{
    serializer.Serialize(writer, objectToSerialize);
    writer.Close();
}

and

var serializer = new XmlExtendedSerializer(SerializerDirection.Deserialize, typeof(T));
using (var fileStream = new FileStream(path, FileMode.Open, System.IO.FileAccess.Read))
{
    var tmpObj = serializer.Deserialize(fileStream);
    fileStream.Close();
    var deserializedObject = (T) tmpObj;
}

The downsides are:

  • nullbale enums are always serialized to the string
  • creating one instance of the serializer can only be used for one mode serialize OR deserialize not for both at the same time
  • not all overloads of Deserialize and Serialize are overridden
  • own class
Related