84

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.

Community
  • 1
  • 1
Espo
  • 41,399
  • 21
  • 132
  • 159

7 Answers7

154

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>
miha
  • 3,287
  • 3
  • 29
  • 44
  • I greatly prefer this as its less work and easier to read / understand. I'd like to see a comparison of this method and the above accepted answer – Allen Rice Jan 26 '10 at 21:00
  • 5
    It is arguable whether this is actually less work than the accepted answer. More enum values = more maintenance. – youwhut Dec 21 '10 at 13:13
  • 6
    +1 This is also my preferred method - why create an unnecessary shim property when a few extra directives will allow you to (de)serialize the Enum properly. – CJM Jun 02 '11 at 11:54
  • 1
    Unless you explicitly define all of the combinations as well. For small flags enums that's an option. – xr280xr Oct 24 '12 at 13:12
  • 3
    +1 Personally I find this to be a more elegant solution than the accepted answer. This way it is also easier to serialize dictionaries where enum values are used as keys. – Alex Jun 06 '13 at 10:41
  • Great answer. Exactly what I was looking for. Simple and clear! – Kelsey May 08 '15 at 15:14
  • 1
    Great stuff. I'd recommend dropping the `Serializable` attribute though as that is not necessary for Xml serialization. – julealgon Sep 19 '17 at 19:30
75

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.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Just so we're clear - what the snippet does is tell the XmlSerializer - IGNORE the MyEnum property. And SERIALIZE the FooInt32 Property, which just casts the MyEnum prop to an Int32 value. This will work perfectly for you. – Cheeso Feb 26 '09 at 10:46
  • This is a good solution when referencing Enum's from 3rd party libraries. – CtrlDot Jan 22 '12 at 15:54
  • It should also be noted that the FooInt32 property is serialised as the name Foo. I like this answer the most because if you've got dozens of values in your enum you only have to do this once. – intrepidis Mar 23 '16 at 08:23
  • I have also used this successfully in a WebHTTP (JSON) service, with the only change being that the [XmlElement("Foo")] attribute is substituted with [DataMember(Name="Foo")] – Dan Parsonson Nov 10 '17 at 12:17
14

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.

Peter McG
  • 18,857
  • 8
  • 45
  • 53
  • The question already notes the [XmlEnum("...")] usage, the XmlSerializer equivalent to [EnumMember("...")] - so I'm not sure this adds anything the OP doesn't already know. – Marc Gravell Feb 03 '09 at 09:57
  • 1
    Besides which - since DataContractSerializer supports private members, the simpler approach would be a *private* shim property that casts between `int` and the enum. – Marc Gravell Feb 03 '09 at 09:58
  • Yes I spotted that re:XmlEnum thanks but as I say I think it's an interesting solution to the question. Which solution is 'simpler' is subjective and ultimately up to the questioner surely. Agreed: with 'shim' approach DataContractSerializer and it's support for private members is the way to go – Peter McG Feb 03 '09 at 10:11
  • 1
    @MarcGravell, @Peter McGrattan: so is there anything wrong here with using `[EnumMember(Value = "1")]` like that? Or we should always go with the "shim" property as suggested by Marc G? – VoodooChild Feb 28 '12 at 02:48
2
using System.Xml.Serialization;

public class Request
{    
    [XmlIgnore()]
    public RequestType request;

    public int RequestTypeValue
    {
      get 
      {
        return (int)request;
      } 
      set
      {
        request=(RequestType)value; 
      }
    }
}

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

The above approach worked for me.

Abhishek
  • 23
  • 2
1

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
geraphl
  • 305
  • 4
  • 10
0

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.

Glenn
  • 7,874
  • 3
  • 29
  • 38
  • 3
    While all true, that doesn't address how to use that transparently during serialization, which is the real issue. – Marc Gravell Feb 03 '09 at 09:14
0

Since you are assigning explicit non-sequential values to the enum options I am assuming you want to be able to specify more than one value at a time (binary flags), then the accepted answer is your only option. Passing in PreBooking | PreBookingConfirmation will have an integer value of 9 and the serializer will not be able to deserialize it, casting it with a shim property however will work well. Or maybe you just missed the 3 value :)

Adriaan Davel
  • 710
  • 1
  • 11
  • 25
  • 2
    I've just found a handy enum friend called [Flags] which allows you to use bit flags and they serialize back to the correct options... – Adriaan Davel Feb 08 '12 at 10:15