7

I have an enum:

public enum FilterOperator
    {
        [EnumMember(Value = "eq")]
        Equals,
        [EnumMember(Value = "gt")]
        GreaterThan,
        [EnumMember(Value = "lt")]
        LessThan,
        [EnumMember(Value = "in")]
        In,
        [EnumMember(Value = "like")]
        Like
    }

and a class that includes an enum property:

public class GridFilter
{
    [JsonProperty("operator")]
    [JsonConverter(typeof(StringEnumConverter))]
    public FilterOperator Operator { get; set; }
}

The object is passed in via a WebAPI action and deserializes as expected for "like" and "in" but it doesn't for "lg" or "gt". Any idea why?

UPDATE: Well the reason "like" and "in" work is that they match the enum name. Renaming GreaterThan to Gt (etc) works. So the real issue is why isn't StringEnumConverter being used?

Supergibbs
  • 1,421
  • 3
  • 14
  • 21
  • Have you tried decorating `FilterOperator` itself with `[JsonConverter(typeof(StringEnumConverter))]`? – haim770 Jan 28 '15 at 07:56
  • Hmm, didn't realize one could set it up like that. Sadly it didn't fix the problem but didn't make it worse either – Supergibbs Jan 29 '15 at 01:08
  • Given that 'like' and 'in' are spelled the same as their decorator, I suspect that your decorator isn't actually doing anything for them either. – Derek Tomes Jan 29 '15 at 01:20
  • You are correct, I removed [JsonConverter(typeof(StringEnumConverter))] and it still worked the same. So am I misusing StringEnumConverter and EnumMember? – Supergibbs Jan 29 '15 at 01:30
  • Is this a duplicate of this question? http://stackoverflow.com/questions/8999731/how-to-customize-deserialization-of-a-json-enum-in-net – Derek Tomes Jan 29 '15 at 01:45

3 Answers3

10

Well, you must place the [JsonConverter(typeof(StringEnumConverter))] attribute directly on the enum declaration instead of the Operator property of GridFilter if you want it to be used when deserializing outside the context of the class GridFilter:

[JsonConverter(typeof(StringEnumConverter))] // Add this
public enum FilterOperator
{
    [EnumMember(Value = "eq")]
    Equals,
    [EnumMember(Value = "gt")]
    GreaterThan,
    [EnumMember(Value = "lt")]
    LessThan,
    [EnumMember(Value = "in")]
    In,
    [EnumMember(Value = "like")]
    Like
}

public class GridFilter
{
    [JsonProperty("operator")]
    //[JsonConverter(typeof(StringEnumConverter")] // Remove this
    public FilterOperator Operator { get; set; }
}
Maxime Rossini
  • 3,612
  • 4
  • 31
  • 47
4

Thanks for everyone's help! I realized what I've done. Sadly it's pretty dumb, I apologize in advanced for the run around.

Since I am using GET I am sending the parameters as url query parameters so WebAPI is using the normal ModelBinder to map names and not JSON.NET. I'm not actually sending JSON so this make total sense. This question helped me realize this: Complex type is getting null in a ApiController parameter

My choices are create a custom model binder that handles the enum correctly or change to a POST and send the data with JSON.stringify().

Community
  • 1
  • 1
Supergibbs
  • 1,421
  • 3
  • 14
  • 21
2

This is just a guess, and I haven't tested it.

I looked at the documentation for EnumMemberAttribute, and it says:

To use EnumMemberAttribute, create an enumeration and apply the DataContractAttribute attribute to the enumeration. Then apply the EnumMemberAttribute attribute to each member that needs to be in the serialization stream.

That's for the DataContractSerializer, of course, but I'm thinking perhaps JSON.net takes that same rule into account?

I'd try applying [DataContract] to the enum.

[DataContract]
public enum FilterOperator
{
    [EnumMember(Value = "eq")]
    Equals,
    [EnumMember(Value = "gt")]
    GreaterThan,
    [EnumMember(Value = "lt")]
    LessThan,
    [EnumMember(Value = "in")]
    In,
    [EnumMember(Value = "like")]
    Like
}

It seems arbitrary, and redundant. And I know JSON.net doesn't typically depend on that sort of thing, but maybe in this case it does?

I'm also noticing that it appears the DataContractSerializer ignores elements without [EnumMember] if [DataContract] is present, so it might have to be this way for backwards compatibility. Again, not super logical. But that's all I've got.


Edit: In true developer fashion, rather than just testing this, I went into the source code. The part that reads the EnumMemberAttribute can be found here on line 55, and it does this:

n2 = f.GetCustomAttributes(typeof(EnumMemberAttribute), true) 
      .Cast<EnumMemberAttribute>() 
      .Select(a => a.Value) 
      .SingleOrDefault() ?? f.Name; 

That makes me think that what you've got should be working.


Edit 2:

Alright, so this is odd. I just tried it myself and found it working.

public enum FilterOperator
{
    [EnumMember(Value = "eq")]
    Equals,
    [EnumMember(Value = "gt")]
    GreaterThan,
    [EnumMember(Value = "lt")]
    LessThan,
    [EnumMember(Value = "in")]
    In,
    [EnumMember(Value = "like")]
    Like
}
public class GridFilter
{
    [JsonProperty("operator")]
    [JsonConverter(typeof(StringEnumConverter))]
    public FilterOperator Operator { get; set; }
}


[TestMethod]
public void enumTest()
{
    GridFilter gf = new GridFilter()
    {
        Operator = FilterOperator.GreaterThan
    };

    var json = JsonConvert.SerializeObject(gf);

    // json yields {"operator":"gt"}

    var ret = JsonConvert.DeserializeObject<GridFilter>(json);
    // ret.Operator yields FilterOperator.GreaterThan
}
Matthew Haugen
  • 12,916
  • 5
  • 38
  • 54
  • Good thought but it didn't make a difference. It seems like the StringEnumConverter is being ignored since it works with or without it present – Supergibbs Jan 29 '15 at 01:35
  • @Supergibbs that's my guess as well. I don't know if you saw my edit at the bottom there, but the source code is pretty clear to say it should be working. What if you manually add ([How to tell Json.Net globally to apply the StringEnumConverter to all enums](http://stackoverflow.com/questions/7427909/how-to-tell-json-net-globally-to-apply-the-stringenumconverter-to-all-enums)) the converter, rather than depending on that attribute? I know, not ideal, but if it's easy, let's see if that works, then we can at least know that. – Matthew Haugen Jan 29 '15 at 01:37
  • Tried the global option, no luck – Supergibbs Jan 29 '15 at 02:12
  • @Supergibbs I'm not sure if that surprises me or not, but that's good to know, either way. Hmm. I just edited my post, but basically I tried it and it worked for me. So there must be something else going on. Or rather, can you confirm that what I got, is your expected behavior? I assume so, but just to double-check, that's what *should* be happening, right? – Matthew Haugen Jan 29 '15 at 02:25
  • Well I am actually having an issue with deserialization. I did the same thing as you though and it seems to work in a test. Maybe something with my project setup, it's gotten pretty big and complex. I'll try making a new bare bones one to test. Thanks for all your help! – Supergibbs Jan 29 '15 at 02:37
  • @Supergibbs No problem, this is definitely a weird one. And yeah, I just added on deserialization in my test (`JsonConvert.DeserializeObject(json)`) and it works, so there must be something else at play. My guess would be something in the configuration of Web Api, granted I'm not sure what. – Matthew Haugen Jan 29 '15 at 02:44