54

I consume an API which returns the string values like this:

some-enum-value

I try to put these values in an enum , since the default StringEnumConverter doesn't do what I want, which is to to decorate this Converter with some additional logic.

How can I be sure that the values are deserialized correctly ?

The following code is my tryout to get this job done.
However the line

reader = new JsonTextReader(new StringReader(cleaned));

breaks the whole thing since the base.ReadJson can't recognize the string as a JSON.

Is there a better way to do this without having to implement all the existing logic in a StringEnumConverter?
How could I fix my approach?

public class BkStringEnumConverter : StringEnumConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.String)
        {
            var enumString = reader.Value.ToString();
            if (enumString.Contains("-"))
            {
                var cleaned = enumString.Split('-').Select(FirstToUpper).Aggregate((a, b) => a + b);
                reader = new JsonTextReader(new StringReader(cleaned));
            }
        }
        return base.ReadJson(reader, objectType, existingValue, serializer);
    }

    private static string FirstToUpper(string input)
    {
        var firstLetter = input.ToCharArray().First().ToString().ToUpper();
        return string.IsNullOrEmpty(input)
            ? input
            : firstLetter + string.Join("", input.ToCharArray().Skip(1));
    }
}
Community
  • 1
  • 1
Marco
  • 4,817
  • 5
  • 34
  • 75

4 Answers4

102

I solved the issue by adding EnumMember attributes on my enum values. The Json.NET default StringEnumConverter perfectly deals with these attributes.

Example:

public enum MyEnum
{
    [EnumMember(Value = "some-enum-value")]
    SomeEnumValue,
    Value,
    [EnumMember(Value = "some-other-value")]
    SomeOtherValue
}

Please note that you only have to specify the attributes in case of dashes or other special chars you can't use in your enum. The uppercase lowercase is dealt with by the StringEnumConverter. So if the service returns a value like someenumvalue you should use it like this in the enum Someenumvalue. If you prefer SomeEnumValue you should use the EnumMember attribute. In case the service returns it like this someEnumValue you can just use it like this SomeEnumValue (It works out of the box when you use the CamelCaseText property).

You can easily specify your converters and other settings in the JsonSerializerSettings.

Here is an example of the settings I use myself.

new JsonSerializerSettings
{
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
    Converters = new List<JsonConverter> { new StringEnumConverter { CamelCaseText = true } },
    NullValueHandling = NullValueHandling.Ignore
};
Kepboy
  • 3,733
  • 2
  • 30
  • 43
Marco
  • 4,817
  • 5
  • 34
  • 75
  • Hi, please can you provide sample project because it doesn't work for me. Thank you. – Jakub Krampl May 22 '14 at 13:29
  • just provide the JsonSerializerSettings to your JsonConverter – Marco Jul 21 '14 at 14:55
  • 2
    Small comment. If you don't use the StringEnumConverter, but the default from json.net, the enumMember won't work. – dampee Jul 14 '15 at 15:00
  • 2
    Do you need to add `[JsonConverter(typeof(StringEnumConverter))]` above the `enum` definition? – JDawg Nov 05 '15 at 01:07
  • 1
    @JDawg not if you provide the JsonSerializerSettings. That way you don't have to spoil those attributes all over the place and you can easily change them in one single place – Marco Nov 06 '15 at 11:51
  • Also it might seems not working because the browser caches the page and if you invoke the api methods from browser you will no see the changes of EnumMember – Alexander Knoth Mar 01 '17 at 11:55
  • 1
    How can you do this on classes you don't own, i.e. an NLog LogLevelInfo class on property Level? – Brandon Slezak Jun 05 '19 at 05:48
41

You can also use this code:

[JsonConverter(typeof(StringEnumConverter))]
public enum ResposeStatus
{
    [EnumMember(Value = "success value")]
    Success,
    [EnumMember(Value = "fail value")]
    Fail,
    [EnumMember(Value = "error value")]
    Error
};

When serializing JsonConvert.Serialize(), will use the text inside the EnumMember.

A-Sharabiani
  • 17,750
  • 17
  • 113
  • 128
  • Yes you can, but as stated in my last comment on the accepted answer, it is easier to use the serializeSettings. That way you don't have to spoil these attributes all over the place. – Marco Jun 27 '16 at 10:02
  • this does not work if ResposeStatus exists inside another class. the generated Json will be in this format "IO.Swagger.Models.Order+StatusEnum": {...} I have StatusEnum in Order class. the + is what im trying to solve for my current issue. – Dawit Apr 20 '23 at 03:48
14

This has been made easier in Json.NET 12.0.1 with the addition of NamingStrategy to StringEnumConverter:

New feature - Added support for NamingStrategy to StringEnumConverter

And Json.NET 12.0.3 adds KebabCaseNamingStrategy for hyphen-separated kebab casing like some-enum-value:

New feature - Added KebabCaseNamingStrategy

No annotations at all are required for MyEnum with this approach.

Specifically, in 12.0.3 and later you can pass KebabCaseNamingStrategy into any of several of the constructors for StringEnumConverter when constructing and adding converters to JsonSerializerSettings.Converters:

var settings = new JsonSerializerSettings
{
    Converters = { new StringEnumConverter(new KebabCaseNamingStrategy()) },
};
var json = JsonConvert.SerializeObject(MyEnum.SomeEnumValue, settings);

Assert.IsTrue(json == "\"some-enum-value\""); // Passes successfully

Having done so, your serialized enum values will now be kebab-cased. Demo fiddle #1 here.

In Json.NET 12.0.1 and 12.0.2 you can define your own kebab case naming strategy by subclassing SnakeCaseNamingStrategy:

public class KebabCaseNamingStrategy : SnakeCaseNamingStrategy
{
    protected override string ResolvePropertyName(string name)
    {
        return base.ResolvePropertyName(name).Replace('_', '-');
    }
}

Demo fiddle #2 here.

dbc
  • 104,963
  • 20
  • 228
  • 340
0

Also u can use this methods:

public static string GetDescription(this Enum member)
        {
            if (member.GetType().IsEnum == false)
                throw new ArgumentOutOfRangeException(nameof(member), "member is not enum");

            var fieldInfo = member.GetType().GetField(member.ToString());

            if (fieldInfo == null)
                return null;

            var attributes = fieldInfo.GetCustomAttributes<DescriptionAttribute>(false).ToList();

            return attributes.Any() ? attributes.FirstOrDefault()?.Description : member.ToString();
        }

or

public static string GetDescription(this object member)
        {
            var type = member.GetType();

            var attributes = type.GetCustomAttributes<DescriptionAttribute>(false).ToList();

            return attributes.Any() ? attributes.FirstOrDefault()?.Description : member.GetType().Name;
        }

and enum should have desctription attribute. Like this:

public enum MyEnum
    {
        [Description("some-enum-value")]
        And,
        [Description("some-enum-value")]
        Or

    }

And than you can use your enum like this:

MyEnum.GetDescription(); //return "some-enum-value"
Maksym Labutin
  • 561
  • 1
  • 8
  • 17