How do I serialize an enum value as an int?

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.

54910 次浏览

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.

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.

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.

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>

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 :)

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.

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