You can solve this problem with a custom JsonConverter. Here is one I put together using a few pieces from the StringEnumConverter class that comes from Json.Net. It should give you the flexibility to handle things whatever way you decide. Here's how it works:
- If the value found in the JSON matches the enum (either as a string or an integer), that value is used. (If the value is integer and there are multiple possible matches, the first of those is used.)
- Otherwise if the enum type is nullable, then the value is set to null.
- Otherwise if the enum has a value called "Unknown", then that value is used.
- Otherwise the first value of the enum is used.
Here is the code. Feel free to change it to meet your needs.
class TolerantEnumConverter : JsonConverter { public override bool CanConvert(Type objectType) { Type type = IsNullableType(objectType) ? Nullable.GetUnderlyingType(objectType) : objectType; return type.IsEnum; } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { bool isNullable = IsNullableType(objectType); Type enumType = isNullable ? Nullable.GetUnderlyingType(objectType) : objectType; string[] names = Enum.GetNames(enumType); if (reader.TokenType == JsonToken.String) { string enumText = reader.Value.ToString(); if (!string.IsNullOrEmpty(enumText)) { string match = names .Where(n => string.Equals(n, enumText, StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(); if (match != null) { return Enum.Parse(enumType, match); } } } else if (reader.TokenType == JsonToken.Integer) { int enumVal = Convert.ToInt32(reader.Value); int[] values = (int[])Enum.GetValues(enumType); if (values.Contains(enumVal)) { return Enum.Parse(enumType, enumVal.ToString()); } } if (!isNullable) { string defaultName = names .Where(n => string.Equals(n, "Unknown", StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(); if (defaultName == null) { defaultName = names.First(); } return Enum.Parse(enumType, defaultName); } return null; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { writer.WriteValue(value.ToString()); } private bool IsNullableType(Type t) { return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)); } }
Here is a demo which puts it the converter through its paces using a couple of different enums (one has an "Unknown" value, and the other does not):
[JsonConverter(typeof(TolerantEnumConverter))] enum Status { Ready = 1, Set = 2, Go = 3 } [JsonConverter(typeof(TolerantEnumConverter))] enum Color { Red = 1, Yellow = 2, Green = 3, Unknown = 99 } class Foo { public Status NonNullableStatusWithValidStringValue { get; set; } public Status NonNullableStatusWithValidIntValue { get; set; } public Status NonNullableStatusWithInvalidStringValue { get; set; } public Status NonNullableStatusWithInvalidIntValue { get; set; } public Status NonNullableStatusWithNullValue { get; set; } public Status? NullableStatusWithValidStringValue { get; set; } public Status? NullableStatusWithValidIntValue { get; set; } public Status? NullableStatusWithInvalidStringValue { get; set; } public Status? NullableStatusWithInvalidIntValue { get; set; } public Status? NullableStatusWithNullValue { get; set; } public Color NonNullableColorWithValidStringValue { get; set; } public Color NonNullableColorWithValidIntValue { get; set; } public Color NonNullableColorWithInvalidStringValue { get; set; } public Color NonNullableColorWithInvalidIntValue { get; set; } public Color NonNullableColorWithNullValue { get; set; } public Color? NullableColorWithValidStringValue { get; set; } public Color? NullableColorWithValidIntValue { get; set; } public Color? NullableColorWithInvalidStringValue { get; set; } public Color? NullableColorWithInvalidIntValue { get; set; } public Color? NullableColorWithNullValue { get; set; } } class Program { static void Main(string[] args) { string json = @" { ""NonNullableStatusWithValidStringValue"" : ""Set"", ""NonNullableStatusWithValidIntValue"" : 2, ""NonNullableStatusWithInvalidStringValue"" : ""Blah"", ""NonNullableStatusWithInvalidIntValue"" : 9, ""NonNullableStatusWithNullValue"" : null, ""NullableStatusWithValidStringValue"" : ""Go"", ""NullableStatusWithValidIntValue"" : 3, ""NullableStatusWithNullValue"" : null, ""NullableStatusWithInvalidStringValue"" : ""Blah"", ""NullableStatusWithInvalidIntValue"" : 9, ""NonNullableColorWithValidStringValue"" : ""Green"", ""NonNullableColorWithValidIntValue"" : 3, ""NonNullableColorWithInvalidStringValue"" : ""Blah"", ""NonNullableColorWithInvalidIntValue"" : 0, ""NonNullableColorWithNullValue"" : null, ""NullableColorWithValidStringValue"" : ""Yellow"", ""NullableColorWithValidIntValue"" : 2, ""NullableColorWithNullValue"" : null, ""NullableColorWithInvalidStringValue"" : ""Blah"", ""NullableColorWithInvalidIntValue"" : 0, }"; Foo foo = JsonConvert.DeserializeObject<Foo>(json); foreach (PropertyInfo prop in typeof(Foo).GetProperties()) { object val = prop.GetValue(foo, null); Console.WriteLine(prop.Name + ": " + (val == null ? "(null)" : val.ToString())); } } }
Output:
NonNullableStatusWithValidStringValue: Set NonNullableStatusWithValidIntValue: Set NonNullableStatusWithInvalidStringValue: Ready NonNullableStatusWithInvalidIntValue: Ready NonNullableStatusWithNullValue: Ready NullableStatusWithValidStringValue: Go NullableStatusWithValidIntValue: Go NullableStatusWithInvalidStringValue: (null) NullableStatusWithInvalidIntValue: (null) NullableStatusWithNullValue: (null) NonNullableColorWithValidStringValue: Green NonNullableColorWithValidIntValue: Green NonNullableColorWithInvalidStringValue: Unknown NonNullableColorWithInvalidIntValue: Unknown NonNullableColorWithNullValue: Unknown NullableColorWithValidStringValue: Yellow NullableColorWithValidIntValue: Yellow NullableColorWithInvalidStringValue: (null) NullableColorWithInvalidIntValue: (null) NullableColorWithNullValue: (null)