admin管理员组

文章数量:1300174

In our C# application on .NET 4.8, we tried to use a PopulateObject for deserializing data returned from an API. This API is not under our control.

Now the API developer added a new enum value and when we try to map it in our own Enum it crashes with a message like

Error converting value 'x' to 'enum-y'

Now we COULD adjust our enum and go on, but that would only be a temporary fix until it happens again at another part of the code. We searched a little bit and found a specific solution. We added an 'Unknown' value to our enum and then added a converter like this:

public class AllowedMethodConverter : StringEnumConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.String)
        {
            if (Enum.TryParse(reader.Value.ToString(), out Method result)) // Here the Method has to be known
                return result;
            else
                return Method.Unknown;
        }

        return base.ReadJson(reader, objectType, existingValue, serializer);
    }
}

[JsonConverter(typeof(AllowedMethodConverter))]
public enum Method
{
    Unknown,
    Method1,
    Method2
}

This is used in a very simple API method where we get the string from the API and afterwards:

JsonConvert.PopulateObject(response.Content, userSettings);

That works fine so far, but this would require one StringEnumConverter per enum. With roughly 40 enums, it would be a little bit much to create 40 converters, just so I can ignore some values that do not exist.

Does anyone have a better idea how to handle this?

In our C# application on .NET 4.8, we tried to use a PopulateObject for deserializing data returned from an API. This API is not under our control.

Now the API developer added a new enum value and when we try to map it in our own Enum it crashes with a message like

Error converting value 'x' to 'enum-y'

Now we COULD adjust our enum and go on, but that would only be a temporary fix until it happens again at another part of the code. We searched a little bit and found a specific solution. We added an 'Unknown' value to our enum and then added a converter like this:

public class AllowedMethodConverter : StringEnumConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.String)
        {
            if (Enum.TryParse(reader.Value.ToString(), out Method result)) // Here the Method has to be known
                return result;
            else
                return Method.Unknown;
        }

        return base.ReadJson(reader, objectType, existingValue, serializer);
    }
}

[JsonConverter(typeof(AllowedMethodConverter))]
public enum Method
{
    Unknown,
    Method1,
    Method2
}

This is used in a very simple API method where we get the string from the API and afterwards:

JsonConvert.PopulateObject(response.Content, userSettings);

That works fine so far, but this would require one StringEnumConverter per enum. With roughly 40 enums, it would be a little bit much to create 40 converters, just so I can ignore some values that do not exist.

Does anyone have a better idea how to handle this?

Share edited Feb 11 at 15:48 marc_s 755k184 gold badges1.4k silver badges1.5k bronze badges asked Feb 11 at 13:40 Marcel GrügerMarcel Grüger 9441 gold badge11 silver badges26 bronze badges 2
  • "He added a new 'breaking' enum". Not sure why "adding" should break things; the API to enums is their "names" ... across applications. You've established your "working set"; if "additions" are not relevant you ignore them (like "options"). If they imply logic changes, how else are you going to stay aware? – Gerry Schmitz Commented Feb 11 at 19:49
  • Adding a new validation method that we don't implement is one thing, but giving us the info that they provide method-x and my application crashes just out of getting this info, that would be really a bad design. – Marcel Grüger Commented Feb 12 at 7:35
Add a comment  | 

1 Answer 1

Reset to default 1

I created a simple solution to handle those pesky API enum changes without crashing your app.

Instead of writing 40 different converters (yikes!), I made a single, smart converter that works with all your enums. When the API throws a new value at you, it just returns "Unknown" instead of breaking everything - no more emergency fixes needed!

Just add the Unknown value to your enum, slap on the converter attribute, and you're good to go. Super easy to plug into your existing code, and it'll save you tons of headaches down the road.

using Newtonsoft.Json;
using System;

// Interface to mark enums that should handle unknown values
public interface IUnknownValueEnum
{
    bool IsUnknown { get; }
}

// Generic converter that can handle any enum implementing IUnknownValueEnum
public class UnknownValueEnumConverter<T> : JsonConverter<T> where T : Enum
{
    public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.String)
        {
            string enumString = reader.Value?.ToString();
            
            // Try to parse the enum value
            if (!string.IsNullOrEmpty(enumString) && Enum.TryParse(enumString, true, out T result))
            {
                return result;
            }
            
            // If parsing fails, return the Unknown value
            foreach (T value in Enum.GetValues(typeof(T)))
            {
                if (value is IUnknownValueEnum unknownEnum && unknownEnum.IsUnknown)
                {
                    return value;
                }
            }
        }
        
        // If no Unknown value is found, throw exception
        throw new JsonSerializationException($"Cannot convert value to enum {typeof(T).Name}");
    }

    public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer)
    {
        writer.WriteValue(value.ToString());
    }
}

// Base class for enums that need unknown value handling
public abstract class UnknownValueEnumBase : IUnknownValueEnum
{
    public bool IsUnknown => this.ToString().Equals("Unknown", StringComparison.OrdinalIgnoreCase);
}

// Example usage:
[JsonConverter(typeof(UnknownValueEnumConverter<Method>))]
public enum Method : int
{
    Unknown = 0,
    Method1 = 1,
    Method2 = 2
}

// Implementation for the enum
public partial class Method : UnknownValueEnumBase { }

// Example class using the enum
public class Settings
{
    public Method AllowedMethod { get; set; }
}

// Usage example:
public class Example
{
    public void DeserializeExample()
    {
        string json = @"{ ""AllowedMethod"": ""NewMethodAddedByAPI"" }";
        var settings = JsonConvert.DeserializeObject<Settings>(json);
        // settings.AllowedMethod will be Method.Unknown
    }
}

本文标签: cPopulating enums without exceptionStack Overflow