28

I have an enum:

public enum Animal 
{ 
    Dog, 
    Cat, 
    BlackBear 
}

I need to send it to a third-party API. This API requires that the enum values I send be lower case and occasionally require underscores. In general, the names they require don't match the enum naming convention I use.

Using the example provided at https://gooddevbaddev.wordpress.com/2013/08/26/deserializing-c-enums-using-json-net/, I tried to use a custom JsonConverter:

public class AnimalConverter : JsonConverter {
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
        var animal = (Animal)value;
        switch (animal)
        {
            case Animal.Dog:
            {
                writer.WriteValue("dog");
                break;
            }
            case Animal.Cat:
            {
                writer.WriteValue("cat");
                break;
            }
            case Animal.BlackBear:
            {
                writer.WriteValue("black_bear");
                break;
            }
        }
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
        var enumString = (string)reader.Value;
        Animal? animal = null;
        switch (enumString)
        {
            case "cat":
            {
                animal = Animal.Cat;
                break;
            }
            case "dog":
            {
                animal = Animal.Dog;
                break;
            }
            case "black_bear":
            {
                animal = Animal.BlackBear;
                break;
            }
        }
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(string);
    }
}

Back in the properties of a class, I put the attributes on the Animal as so:

[JsonProperty("animal")]
[JsonConverter(typeof(AnimalConverter))]
public Animal ZooAnimals { get; set; }

When I run the program though, it seems to completely ignore the JsonConverter and rather than seeing expected values like "black_bear" or "dog", I see "BlackBear" and "Dog". How can I get the JsonConverter to actually do the conversion from the name of the enum value to the string I specify to replace that value with?

Thanks!

Whit Waldo
  • 4,806
  • 4
  • 48
  • 70
  • Can you produce a short but complete program, like for instance for [LINQPad](http://linqpad.net) that demonstrates the problem? I wrote a small LINQPad program to test and it uses the converter here. – Lasse V. Karlsen Nov 04 '13 at 12:48
  • You refer to `Animal` and `Animals` in your code sample; can you clarify these both refer to your enumeration? For what it's worth, I managed to get your code working using the following snippet: `Zoo zoo = new Zoo { ZooAnimals = Animal.Cat }; string json = JsonConvert.SerializeObject(zoo);` – robyaw Nov 04 '13 at 12:52

3 Answers3

80

You don't need to write your own converter. Json.NET's StringEnumConverter will read the EnumMember attribute. If you change your enum to this, it will serialize from and to the values you want.

[JsonConverter(typeof(StringEnumConverter))]
public enum Animals 
{
    [EnumMember(Value = "dog")]
    Dog, 
    [EnumMember(Value = "cat")]
    Cat, 
    [EnumMember(Value = "black_bear")]
    BlackBear 
}

(As a minor note, since Animals isn't a flags enum, it should be singular: Animal. You should consider changing it to this.)

Tim S.
  • 55,448
  • 7
  • 96
  • 122
  • Hi Tim, how can i use EnumMember in .Net 2.0? – Ricky Jiao Mar 24 '14 at 06:36
  • @RickyJiao I think I'd create my own converter, based on `StringEnumConverter` ([see source](https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs)), and make it work with my own `EnumMemberAttribute` class, based on `EnumMemberAttribute` (just needs the one property and constructor). – Tim S. Mar 24 '14 at 11:49
  • Do you know why the `StringEnumConverter` uses `EnumMember` and not the Json.NET `JsonProperty`? Because I use `JsonProperty` everywhere else, but appareantly doesn't work with enums.. – Kapé Mar 30 '15 at 21:24
  • 1
    @Kapé I can't say that I really do; it does seem odd for it to use a built-in type instead of a custom one, as is usually done elsewhere. Here's my guess: Enum members do not represent properties (i.e. the key in the key/value pair), but values, so it doesn't make sense to use `JsonProperty` to describe it. – Tim S. Mar 30 '15 at 22:10
  • 2
    Seems like you need to give the enum the DataContract attribute for EnumMember to work, since the above here didn't work for me. Like this `[DataContract] public enum Animals....` – aup Aug 18 '15 at 16:33
  • Perfect solution. Worked for me! – Aster Veigas Feb 09 '17 at 07:33
  • Doesn't work with parameter bindings. GET api/animals/black_bear/children isn't working. – Raj May 10 '17 at 02:00
  • This is not System.Net.JSON but Newtonsoft.JSON. This answer is outdated. – Tvde1 Feb 22 '23 at 09:03
0
// Might return null, better to use try catch
public static Animals GetEnum(string val)
{
    return (Animals)Enum.Parse(typeof(Animals), val, true);
}

public static string GetName(Animals an)
{
    return Enum.GetName(typeof(Animals), an);
}

public static string GetReplace(Animals an)
{
    var get = GetName(an);
    var tempstr = "";
    int getch = 0;
    foreach (var chr in get.ToCharArray())
    {
        if (chr == chr.ToUpper())
        {
            getch++;
            // Second up value char
            if (getch == 2)
            {
                tempstr += "_" + chr;
            }
            else
            {
                tempstr += chr;
            }
        }
        else
        {
             tempstr += chr;
        }
    }
    return tempstr;
}
111WARLOCK111
  • 857
  • 2
  • 7
  • 14
  • Can't use that since the name of the enum value must occasionally change. For example, given an enum value of "BlackBear", it must convert to show a value of "black_bear". – Whit Waldo Nov 04 '13 at 12:46
  • @Xaniff `GetEnum(val.Replace("_", "").Replace(" ", ""));` And see the edited, use this function: `GetReplace(animal);` – 111WARLOCK111 Nov 04 '13 at 12:50
  • @Xaniff If you can't use the uppervalues, Make the final string `GetReplace(animal).ToLower()' – 111WARLOCK111 Nov 04 '13 at 12:58
  • 1
    I guess I'm really looking for a more generalized solution so I can specify via an attribute what value I want returned for the enum rather than have to write a conversion like this every time (especially seeing as the third-party API is rather inconsistent in their naming rules). But thanks! – Whit Waldo Nov 04 '13 at 13:47
0

I think your ConConvert() implementation is not correct. It should be:

public override bool CanConvert(Type objectType)
{
    return objectType == typeof(Animals);
}
Reda
  • 2,289
  • 17
  • 19
  • Tried flipping that from string to Animals and it still returns "BlackBear" rather than "black_bear". But thanks! – Whit Waldo Nov 04 '13 at 13:15