C# Web Api错误地反序列化枚举列表

C# Web Api错误地反序列化枚举列表,c#,asp.net-web-api,json.net,C#,Asp.net Web Api,Json.net,因此,我使用一个web API控制器来接受JSON请求。它映射到包含枚举列表的模型对象。我遇到的问题是,如果JSON包含无效值,那么它似乎没有正确地进行反序列化。我希望将无效值映射到枚举列表中的0值类型,但实际情况并非如此 我已经分离出3种主要情况:如果JSON是 ... "MyEnumList":["IncorrectEnum", "One", "Two"] ... 该值根本没有映射,我只是得到一个包含两个有效值的列表。但是,如果我提供此JSON: ...

因此,我使用一个web API控制器来接受JSON请求。它映射到包含枚举列表的模型对象。我遇到的问题是,如果JSON包含无效值,那么它似乎没有正确地进行反序列化。我希望将无效值映射到枚举列表中的0值类型,但实际情况并非如此

我已经分离出3种主要情况:如果JSON是

    ...
    "MyEnumList":["IncorrectEnum", "One", "Two"]
    ...
该值根本没有映射,我只是得到一个包含两个有效值的列表。但是,如果我提供此JSON:

   ...
   "MyEnumList":["123", "One", "Two"]
   ...
我得到一个包含3个对象的列表,其中第一个对象的类型为MyEnum,值为123,即使在我的枚举中没有定义。如果我提供以下JSON语法,也会发生同样的情况:

   ...
   "MyEnumList":[123, "One", "Two"]
   ...
谁能解释一下这里发生了什么,以及我如何确保值始终映射到有效类型

作为参考,模型对象包含我的枚举列表:

    public class MyClass
    {
       public List<myEnum> MyEnumList { get; set; }
    }
123可以分配给不包含123值的枚举,这一事实并不完全是Json.Net的错。事实证明,C运行时本身允许这种分配。如果您运行此小型演示程序,您可以亲自看到这一点:

class Program
{
    static void Main(string[] args)
    {
        // Direct cast from integer -- no error here
        MyEnum x = (MyEnum)123;
        Console.WriteLine(x);

        // Parsing a numeric string -- no error here either
        MyEnum y = (MyEnum)Enum.Parse(typeof(MyEnum), "456");
        Console.WriteLine(y);
    }

    public enum MyEnum
    {
        Zero = 0,
        One = 1,
        Two = 2
    }
}
因此,Json.Net可能只是在幕后使用Enum.Parse。然而,我不知道为什么你的第一个病例没有例外。当我尝试这样做时,正如我所预料的那样,它失败了

在任何情况下,如果需要对可能错误的枚举值进行严格验证,可以创建一个自定义JsonConverter,强制该值有效,或者可以选择引发异常。这里有一个转换器,可以用于任何类型的枚举。这段代码或许可以改进,但它是有效的

class StrictEnumConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType.BaseType == typeof(Enum));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);

        try
        {
            // We're only interested in integers or strings;
            // all other token types should fall through
            if (token.Type == JTokenType.Integer ||
                token.Type == JTokenType.String)
            {
                // Get the string representation of the token
                // and check if it is numeric
                string s = token.ToString();
                int i;
                if (int.TryParse(s, out i))
                {
                    // If the token is numeric, try to find a matching
                    // name from the enum. If it works, convert it into
                    // the actual enum value; otherwise punt.
                    string name = Enum.GetName(objectType, i);
                    if (name != null)
                        return Enum.Parse(objectType, name);
                }
                else
                {
                    // We've got a non-numeric value, so try to parse it
                    // as is (case insensitive). If this doesn't work,
                    // it will throw an ArgumentException.
                    return Enum.Parse(objectType, s, true);
                }
            }
        }
        catch (ArgumentException)
        {
            // Eat the exception and fall through
        }

        // We got a bad value, so return the first value from the enum as
        // a default. Alternatively, you could throw an exception here to
        // halt the deserialization.
        IEnumerator en = Enum.GetValues(objectType).GetEnumerator();
        en.MoveNext();
        return en.Current;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteValue(value.ToString());
    }
}
下面是一个在大杂烩上使用转换器的演示:

class Program
{
    static void Main(string[] args)
    {
        // The first 12 values should deserialize to correct values;
        // the last 7 should all be forced to MyEnum.Zero.
        string json = @"
        {
            ""MyEnumList"":
            [
                ""Zero"", 
                ""One"", 
                ""Two"", 
                0,
                1,
                2,
                ""zero"",
                ""one"",
                ""two"",
                ""0"",
                ""1"",
                ""2"",
                ""BAD"", 
                ""123"", 
                123, 
                1.0,
                null,
                false,
                true
            ]
        }";

        MyClass obj = JsonConvert.DeserializeObject<MyClass>(json, 
                                                   new StrictEnumConverter());
        foreach (MyEnum e in obj.MyEnumList)
        {
            Console.WriteLine(e.ToString());
        }
    }

    public enum MyEnum
    {
        Zero = 0,
        One = 1,
        Two = 2
    }

    public class MyClass
    {
        public List<MyEnum> MyEnumList { get; set; }
    }
}
顺便说一下,要将此转换器与Web API一起使用,您需要将以下代码添加到Global.asax.cs中的应用程序启动方法中:


测试此功能,如果我发送:。。。MyEnumList:[123,1]。。。我得到了与之前相同的结果,这是一个等于123的枚举值。它没有映射到有效的枚举值。是否已将转换器添加到如上所示的全局配置中?是的,但我相信我发现了问题。我在用[JsonConvertertypeofStringEnumConverter]装饰列表,这似乎是导致问题的原因。是的,这可以解释问题。然后,还有一个问题:有没有一种方法可以将枚举序列化为其字符串值而不是其数值,而不破坏它?
class Program
{
    static void Main(string[] args)
    {
        // The first 12 values should deserialize to correct values;
        // the last 7 should all be forced to MyEnum.Zero.
        string json = @"
        {
            ""MyEnumList"":
            [
                ""Zero"", 
                ""One"", 
                ""Two"", 
                0,
                1,
                2,
                ""zero"",
                ""one"",
                ""two"",
                ""0"",
                ""1"",
                ""2"",
                ""BAD"", 
                ""123"", 
                123, 
                1.0,
                null,
                false,
                true
            ]
        }";

        MyClass obj = JsonConvert.DeserializeObject<MyClass>(json, 
                                                   new StrictEnumConverter());
        foreach (MyEnum e in obj.MyEnumList)
        {
            Console.WriteLine(e.ToString());
        }
    }

    public enum MyEnum
    {
        Zero = 0,
        One = 1,
        Two = 2
    }

    public class MyClass
    {
        public List<MyEnum> MyEnumList { get; set; }
    }
}
Zero
One
Two
Zero
One
Two
Zero
One
Two
Zero
One
Two
Zero
Zero
Zero
Zero
Zero
Zero
Zero
JsonSerializerSettings settings = GlobalConfiguration.Configuration.Formatters 
                                  .JsonFormatter.SerializerSettings;
settings.Converters.Add(new StrictEnumConverter());