How avoid exception deserializing an invalid enum

2019-01-28 02:07发布

问题:

For simplicity purposes here, I will show my sample code using fruit. In actuality I am doing something more meaningful (we hope). Let say we have an enum:

public enum FruitType
{
    Apple,
    Orange,
    Banana
}

And a class:

[Serializable]
public class Fruit
{
    public FruitType FruitType { get; set; }
    public Fruit(FruitType type)
    {
        this.FruitType = type;
    }
}

We can serialize and de-serialize it. Now, lets revise the enum, so that it is now:

public enum FruitType
{
    GreenApple,
    RedApple,
    Orange,
    Banana
}

When de-serializing previously serialized objects, you get a System.InvalidOperation exception as Apple (original enum item) is not valid. The object does not get de-serialized.

One way I was able to resolve this was to give the FruitType property in the Fruit class a different name when it gets serialized as follows:

    [XmlElement(ElementName = "Mode")]
    public FruitType FruitType { get; set; }

Now, during de-serialization the old property gets ignored as it is not found. I would like to know if there is a way to ignore/skip invalid enum items during de-serialization, so that no exception is thrown and the object still gets de-serialized.

回答1:

Leave Apple and mark it with the ObsoleteAttribute. That way, any code using Apple will generate a compiler warning.



回答2:

I've been looking for similar answers myself, and wrote this to catch the exception when the XML contains a value for an enum that is not valid. It removes that element, and tries to deserialize again. If the element is required, you still will get an exception. It's imperfect, but should get you most of the way to where you want to be

    private const string XmlError = "There is an error in XML document ";
    private const string InstanceValidationError = "Instance validation error:";
    private static readonly Regex XmlErrorRegex = new Regex("There is an error in XML document \\((\\d+), (\\d+)\\).");
    private static readonly Regex InstanceValidationErrorRegex = new Regex("Instance validation error: '(\\S+)' is not a valid value for (\\S+).");
    private const string TagFinderString = "\\>{0}\\</(\\S+)\\>";

    /// <summary>
    /// Helper method to deserialize xml message
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="message"></param>
    /// <returns></returns>
    public T Deserialize(string message)
    {
        var result = default(T);
        if (!string.IsNullOrEmpty(message))
        {
            using (var reader = new StringReader(message))
            {
                try
                {
                    result = (T)_serializer.Deserialize(reader);
                }
                catch (InvalidOperationException ex)
                {
                    if (ex.Message.StartsWith(XmlError))
                    {
                        if(ex.InnerException != null && ex.InnerException.Message.StartsWith(InstanceValidationError))
                        {
                            var instanceValidationErrorMatches = InstanceValidationErrorRegex.Matches(ex.InnerException.Message);
                            if (instanceValidationErrorMatches.Count > 0)
                            {
                                var locationMatches = XmlErrorRegex.Matches(ex.Message);
                                var startIndex = GetStartIndex(message, locationMatches);
                                var match = instanceValidationErrorMatches[0];
                                if(match.Groups.Count > 0)
                                {
                                    var toRemove = GetToRemove(message, match, startIndex);

                                    return Deserialize(message.Replace(toRemove, string.Empty));
                                }
                            }
                        }
                    }
                }
            }
        }
        return result;
    }

    private static string GetToRemove(string message, Match match, int startIndex)
    {
        var value = match.Groups[1];
        var tagFinder = new Regex(string.Format(TagFinderString, value));
        var tagFinderMatches = tagFinder.Matches(message.Substring(startIndex));
        var tag = tagFinderMatches[0].Groups[1];

        return string.Format("<{0}>{1}</{0}>", tag, value);
    }

    private static int GetStartIndex(string message, MatchCollection locationMatches)
    {
        var startIndex = 0;
        if (locationMatches.Count > 0)
        {
            var lineNumber = int.Parse(locationMatches[0].Groups[1].Value);
            var charIndex = int.Parse(locationMatches[0].Groups[2].Value);
            using (var locationFinder = new StringReader(message))
            {
                for (var i = 1; i < lineNumber; i++)
                {
                    startIndex += locationFinder.ReadLine().Length;
                }
            }
            startIndex += charIndex;
        }
        return startIndex;
    }


回答3:

I posted a similar question and have not found a simple method to catch the exception thrown when the deserializer encounters Apple in the XML file. I can catch a bunch of other exceptions during deserialiation for missing attributes or elements, but not for an invalid enum value. The invalid enum value (in your case Apple) blows me out of the deserialization.

One possible solution is to implement IXMLSerializable on the Fruit class. When the IXMLSerailizable.ReadXML() method is called by the deserializer, you'll have to see what is being passed to you. When the value is "Apple" set the enum to GreenApple or RedApple based on some logic.