Skip to content

Instantly share code, notes, and snippets.

@gubenkoved
Last active July 12, 2025 07:35
Show Gist options
  • Save gubenkoved/999eb73e227b7063a67a50401578c3a7 to your computer and use it in GitHub Desktop.
Save gubenkoved/999eb73e227b7063a67a50401578c3a7 to your computer and use it in GitHub Desktop.
Tolerant JSON.NET enum converter
/// <summary>
/// Tolerant Enum converter. Based on code in the StackOverflow post below, but adds EnumMember attribute support.
/// http://stackoverflow.com/questions/22752075/how-can-i-ignore-unknown-enum-values-during-json-deserialization
/// </summary>
public class TolerantEnumConverter : JsonConverter
{
[ThreadStatic]
private static Dictionary<Type, Dictionary<string, object>> _fromValueMap; // string representation to Enum value map
[ThreadStatic]
private static Dictionary<Type, Dictionary<object, string>> _toValueMap; // Enum value to string map
public string UnknownValue { get; set; } = "Unknown";
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;
InitMap(enumType);
if (reader.TokenType == JsonToken.String)
{
string enumText = reader.Value.ToString();
object val = FromValue(enumType, enumText);
if (val != null)
return val;
}
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[] names = Enum.GetNames(enumType);
string unknownName = names
.Where(n => string.Equals(n, UnknownValue, StringComparison.OrdinalIgnoreCase))
.FirstOrDefault();
if (unknownName == null)
{
throw new JsonSerializationException($"Unable to parse '{reader.Value}' to enum {enumType}. Consider adding Unknown as fail-back value.");
}
return Enum.Parse(enumType, unknownName);
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Type enumType = value.GetType();
InitMap(enumType);
string val = ToValue(enumType, value);
writer.WriteValue(val);
}
#region Private methods
private bool IsNullableType(Type t)
{
return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
}
private void InitMap(Type enumType)
{
if (_fromValueMap == null)
_fromValueMap = new Dictionary<Type, Dictionary<string, object>>();
if (_toValueMap == null)
_toValueMap = new Dictionary<Type, Dictionary<object, string>>();
if (_fromValueMap.ContainsKey(enumType))
return; // already initialized
Dictionary<string, object> fromMap = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
Dictionary<object, string> toMap = new Dictionary<object, string>();
FieldInfo[] fields = enumType.GetFields(BindingFlags.Static | BindingFlags.Public);
foreach (FieldInfo field in fields)
{
string name = field.Name;
object enumValue = Enum.Parse(enumType, name);
// use EnumMember attribute if exists
EnumMemberAttribute enumMemberAttrbiute = field.GetCustomAttribute<EnumMemberAttribute>();
if (enumMemberAttrbiute != null)
{
string enumMemberValue = enumMemberAttrbiute.Value;
fromMap[enumMemberValue] = enumValue;
toMap[enumValue] = enumMemberValue;
}
else
{
toMap[enumValue] = name;
}
fromMap[name] = enumValue;
}
_fromValueMap[enumType] = fromMap;
_toValueMap[enumType] = toMap;
}
private string ToValue(Type enumType, object obj)
{
Dictionary<object, string> map = _toValueMap[enumType];
return map[obj];
}
private object FromValue(Type enumType, string value)
{
Dictionary<string, object> map = _fromValueMap[enumType];
if (!map.ContainsKey(value))
return null;
return map[value];
}
#endregion
}
@fgather
Copy link

fgather commented Sep 4, 2020

Hi @gubenkoved,

thanks for the nice idea, this is very useful. Can you add some license remark, so that we can use that code in a project?

@gubenkoved
Copy link
Author

@fgather, feel free to use it anywhere!

@sebastianp-charactr
Copy link

sebastianp-charactr commented Nov 5, 2020

@gubenkoved Thanks for your work! I'll be using this ;-)

I propose you add empty and single string constructor for UnknownValue injection.

So we can use it like e.g.:

[JsonConverter(typeof(TolerantEnumConverter), "None")]

@hardingray-unity
Copy link

This may be an easier solution (I can't get the code block to work):


public class DefaultingStringEnumConverter : StringEnumConverter
{
object DefaultValue;
public DefaultingStringEnumConverter(object defaultVal)
{
DefaultValue = defaultVal;
}

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        try
        {
            return base.ReadJson(reader, objectType, existingValue, serializer);
        }
        catch
        {
            return DefaultValue;
        }
    }
}

Usage:
[JsonConverter(typeof(DefaultingStringEnumConverter), MatterStatus.Unknown)]

@morcibacsi
Copy link

A little late to the show, and it depends on your use-case, but for enums with flags this won't work. However the default implementation handles that scenario. So you might want to change the CanConvert method to the following to ignore flagged enums:

        public override bool CanConvert(Type objectType)
        {
            Type type = IsNullableType(objectType) ? Nullable.GetUnderlyingType(objectType) : objectType;
            return type.IsEnum && !type.IsDefined(typeof(FlagsAttribute), false);
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment