10

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?

5
  • Have you tried decorating FilterOperator itself with [JsonConverter(typeof(StringEnumConverter))]? Commented Jan 28, 2015 at 7: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 Commented Jan 29, 2015 at 1: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. Commented Jan 29, 2015 at 1:20
  • You are correct, I removed [JsonConverter(typeof(StringEnumConverter))] and it still worked the same. So am I misusing StringEnumConverter and EnumMember? Commented Jan 29, 2015 at 1:30
  • Is this a duplicate of this question? stackoverflow.com/questions/8999731/… Commented Jan 29, 2015 at 1:45

3 Answers 3

14

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; } } 
Sign up to request clarification or add additional context in comments.

Comments

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().

Comments

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 } 

6 Comments

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 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) 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.
Tried the global option, no luck
@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?
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!
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.