Handling Invalid Enum values in a DataContractSerializer

This all started when I was trying to serialize a CodeDom graph (actually a CodeNamespace object) using a DataContractSerializer. I hit the dreaded “Enum value ‘20482’ is invalid for type…”

image

(Note: this error was the showstopper after many other errors)

The problem: System.CodeDom.MemberAttributes should have the [Flags] attribute, but it does not. the value of 20482 is the bitwise OR of MemberAttributes.Final (2) and MemberAttributes.Private (20480). One solution was to go through all object in the CodeDom tree with attributes and force it to a single value – this is not really acceptable. Unfortunately, since we are dealing with a .NET Framework type, there is really no way to fix the type itself.

The solution: Fix the serializer… Read on for details.

One of the constructors for the DataContractSerializer takes an IDataContractSurrogate as a parameter. Turns out Microsoft has a handy article explaining how to use Data Contract Surrogates to remap one data type to another during serialization (note: this document is better than the interface’s example)

 

I decided to create a general-purpose implementation of the IDataContractSurrogate that will convert an Enum into an int during serialization, and back to the appropriate Enum during deserialization.

To use the provided class, add it to your project, then use something like the following to create the DataContractSerializer:

 

            Type[] knownTypesList = new Type[] { typeof(CodeTypeDeclaration),
                                        typeof(CodeNamespaceImport),
                                        typeof(CodeAttributeDeclaration),
                                        typeof(CodeTypeReference),
                                        typeof(System.Runtime.Serialization.DataContractAttribute),
                                        typeof(CodeMemberField),
                                        typeof(CodeMemberProperty),
                                        typeof(MemberAttributes),
                                        typeof(CodeFieldReferenceExpression),
                                        typeof(CodeThisReferenceExpression),
                                        typeof(CodeMethodReturnStatement),
                                        typeof(CodeAssignStatement),
                                        typeof(CodeArgumentReferenceExpression),
                                        typeof(System.Reflection.TypeAttributes)
            };

            var dcs = new System.Runtime.Serialization.DataContractSerializer(typeof(CodeNamespace), knownTypesList, int.MaxValue, true, true, new InvalidEnumContractSurrogate(typeof(MemberAttributes)));

(There is probably a better way to get all the necessary types) 

 

Here is the Data Contract Surrogate. When you create a new instance of it, you specify the Enum Type (or optionally the list of Enum Types) that need to be converted (Eg new InvalidEnumContractSurrogate(typeof(MemberAttributes)) )

 

    /// <summary>
    /// IDataContractSurrogate to map Enum to int for handling invalid values
    /// </summary>
    public class InvalidEnumContractSurrogate : IDataContractSurrogate
    {
        private HashSet<Type> typelist;

        /// <summary>
        /// Create new Data Contract Surrogate to handle the specified Enum type
        /// </summary>
        /// <param name="type">Enum Type</param>
        public InvalidEnumContractSurrogate(Type type)
        {
            typelist = new HashSet<Type>();
            if (!type.IsEnum) throw new ArgumentException(type.Name + " is not an enum","type");
            typelist.Add(type);
        }

        /// <summary>
        /// Create new Data Contract Surrogate to handle the specified Enum types
        /// </summary>
        /// <param name="types">IEnumerable of Enum Types</param>
        public InvalidEnumContractSurrogate(IEnumerable<Type> types)
        {
            typelist = new HashSet<Type>();
            foreach (var type in types)
            {
                if (!type.IsEnum) throw new ArgumentException(type.Name + " is not an enum", "type");
                typelist.Add(type);
            }
        }

        #region Interface Implementation

        public Type GetDataContractType(Type type)
        {
            //If the provided type is in the list, tell the serializer it is an int
            if (typelist.Contains(type)) return typeof(int);
            return type;
        }

        public object GetObjectToSerialize(object obj, Type targetType)
        {
            //If the type of the object being serialized is in the list, case it to an int
            if (typelist.Contains(obj.GetType())) return (int)obj;
            return obj;
        }

        public object GetDeserializedObject(object obj, Type targetType)
        {
            //If the target type is in the list, convert the value (we are assuming it to be int) to the enum
            if (typelist.Contains(targetType)) return Enum.ToObject(targetType, obj);
            return obj;
        }

        public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
        {
            //not used
            return;
        }

        public object GetCustomDataToExport(Type clrType, Type dataContractType)
        {
            //Not used
            return null;
        }

        public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
        {
            //not used
            return null;
        }

        public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
        {
            //not used
            return null;
        }

        public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
        {
            //not used
            return typeDeclaration;
        }

        #endregion
    }

 

Hopefully this helps somebody else!

Leave a Reply