My question is simple, but a little more specific than other questions related to serializing enumerated types as strings.
Consider the following piece of code:
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public enum MyEnum
{
TypeOne,
TypeTwo,
TypeThree
}
public class Foo
{
[JsonConverter(typeof(StringEnumConverter))]
public MyEnum Types { get; set; }
}
When the Web API controller sends serialized Foo objects, they may look something like this:
{
"Type" : "TypeTwo"
}
My Question: is it possible to send serialized enums as strings with spaces before each capital letter? Such a solution would produce JSON like this:
{
"Type" : "Type Two"
}
Let me know if there's any additional information needed to solve my problem. Thanks!
EDIT:
It's preferable if the enums are only converted to strings with spaces while serializing them to JSON. I'd like to exclude spaces while using MyEnum.ToString() on the backend.
Try adding EnumMember as shown below,
[JsonConverter(typeof(StringEnumConverter))]
public enum MyEnum
{
[EnumMember(Value = "Type One")]
TypeOne,
[EnumMember(Value = "Type Two")]
TypeTwo,
[EnumMember(Value = "Type Three")]
TypeThree
}
You may need to install a package called System.Runtime.Serialization.Primitives from Microsoft to use that.
Related
I need to customize the way Newtonsoft.Json serializes an object, in particular about Enum types.
Given a sample class like this:
public class TestEnumClass
{
public Enum ReferencedEnum { get; set; }
public string OtherProperty { get; set; }
public StringSplitOptions OtherEnum { get; set; }
}
The default serialization will happen this way:
var testEnumClass = new TestEnumClass
{
ReferencedEnum = StringComparison.OrdinalIgnoreCase,
OtherProperty = "Something",
OtherEnum = StringSplitOptions.None
};
var serialized = JsonConvert.SerializeObject(testEnumClass, Formatting.Indented);
And the serialized string will be:
{
"ReferencedEnum": 5,
"OtherProperty": "Something",
"OtherEnum": 0
}
Here I have 2 problems:
I cannot guarantee that the order of the Enums will remain the same (here I am using Enums included in the framework, but my project has other Enums that derive from ": Enum"), so I cannot keep the number as the serialized value of the Enum.
Secondly, and more important, is the fact that the "ReferencedEnum" field is declared as "Enum", and any kind of Enum can be written in that field (ReferencedEnum = AnyEnum.AnyEnumValue).
This leads to the fact that when deserializing the value, I need to know the original Enum type (in the example is StringComparison).
I was thinking of using a Converter (deriving from ": JsonConverter") and manipulating what is written and read. The result I was thinking was something like this:
{
"ReferencedEnum": {
"EnumType": "StringComparison",
"EnumValue": "OrdinalIgnoreCase"
},
"OtherProperty": "Something",
"OtherEnum": "StringSplitOptions.None"
}
I this way the deserializer would know:
for "Enum" properties, the original type and the string value.
for "typed Enum" (specific enum) properties, the full type and value.
What I cannot absolutely add is a reference to the converter in the model class like this:
[JsonConverter(typeof(EnumConverter))]
public Enum ReferencedEnum { get; set; }
And I also would avoid to have the "$type" field in the serialized string (except if this is the only solution).
By the way, I can add a generic attribute like this:
[IsEnum]
public Enum ReferencedEnum { get; set; }
Does somebody have any idea of how can I get the result needed?
Thank you!
I've been in the very same issue and developed a nuget package named StringTypeEnumConverter, that solves it.
You can check the project here.
The usage will be as simple as any other converter.
This converter derives from an already existing "StringEnumConverter", that writes the string value of the enum, instead of its numeric counterpart.
I added the support to writing the type name too.
The result will be like: "StringSplitOptions.None" (this is a string value, not an object).
Note that this converter should be applied for both writing and reading, as the resulting json would not be compatible with other readers not including this converter.
You should consider using this package only if you cannot avoid using enums in your model.
I would also suggest you to spend time to check if (custom) enums could be transformed to classes.
J.
I have an enum:
public enum IdleDelayBreakMode
{
Repeat,
ShowNext
}
I'm using NewtonSoft.Json to convert to json objects containing properties of that enum's type. What's the best solution to serialize values of that enum to arbitrary integers? Ideally i would like to do something like on the snippet below and i want to know if maybe there's a built-in solution for that:
public enum IdleDelayBreakMode
{
[JsonValue(100)]
Repeat, // When serializing will be converted to 100
[JsonValue(200)]
ShowNext // When serializing will be converted to 200
}
You can just set enum values like that:
public enum IdleDelayBreakMode
{
Repeat = 100,
ShowNext = 200
}
Newtonsoft.Json will use enum values. There is no need to set attributes. This way it will be consistent across the system whether you need to serialize it to JSON, save it in the database using Entity Framework etc.
Are you using your enum as integer constants storage?
In this case you might want to inherit it from int:
public enum IdleDelayBreakMode : int
Assuming you don't want to modify the underlying enum values (as is shown in the other answers), you can decorate your enum vales with [EnumMember(Value = "Name")] attributes and use the alternate numeric values as the name strings:
[JsonConverter(typeof(StringEnumConverter))]
[DataContract]
public enum IdleDelayBreakMode
{
[EnumMember(Value = "100")]
Repeat,
[EnumMember(Value = "200")]
ShowNext
}
You will also need to serialize using StringEnumConverter, either by adding [JsonConverter(typeof(StringEnumConverter))] directly to the enum or by applying it globally according to this answer.
This works with [Flags] as well. Serializing both values of the following:
[Flags]
[DataContract]
[JsonConverter(typeof(StringEnumConverter))]
public enum IdleDelayBreakModeFlags
{
[EnumMember(Value = "100")]
Repeat = (1 << 0),
[EnumMember(Value = "200")]
ShowNext = (1 << 1),
}
produces "100, 200".
Adding these attributes will cause DataContractSerializer as well as Json.NET to use these alternate name strings. If you would prefer not to affect the behavior of the data contract serializer, remove [DataContract] but keep the [EnumMember] attributes.
Just set the integer values in your enum items directly instead of using an attribute:
public enum IdleDelayBreakMode
{
Repeat = 100,
ShowNext = 200
}
JSON.Net will them use the integer values when serializing/deserializing a property of type IdleDelayBreakMode
I'm trying to serialize a list of objects to JSON using Newtonsoft's JsonConvert. My Marker class includes an enum, and I'm trying to serialize it into a camelCase string. Based on other Stackoverflow questions, I'm trying to use the StringEnumConverter:
public enum MarkerType
{
None = 0,
Bookmark = 1,
Highlight = 2
}
public class Marker
{
[JsonConverter(typeof(StringEnumConverter)]
public MarkerType MarkerType { get; set; }
}
This partially works, but my MarkerType string is PascalCase when I call:
var json = JsonConvert.SerializeObject(markers, Formatting.None);
Result:
{
...,
"MarkerType":"Bookmark"
}
What I'm really looking for is:
{
...,
"MarkerType":"bookmark"
}
The StringEnumConverter docs mention a CamelCaseText property, but I'm not sure how to pass that using the JsonConverterAttribute. The following code fails:
[JsonConverter(typeof(StringEnumConverter), new object[] { "camelCaseText" }]
How do I specify the CamelCaseText property for the StringEnumConverter in a JsonConverterAttribute?
JsonConverterAttribute has two constructors, one of which takes a parameter list (Object[]). This maps to the constructor of the type from the first parameter.
StringEnumConverter can handle this with most of its non-default constructors.
The first one is obsolete in JSON.net 12+
The second one allows you to specify a NamingStrategy Type; the CamelCaseNamingStrategy does the trick. Actually, this is true for three out of the six constructors provided.
Note: one other constructor breaks the mold, asking for an instance of a NamingStrategy instead of a type.
It would look like this:
[JsonConverter(typeof(StringEnumConverter), typeof(CamelCaseNamingStrategy))]
public MarkerType MarkerType { get; set; }
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?
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; }
}
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().
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
}
This is a hang over from my Delphi days where I was able to do something as follows:
type
TCars = (Ford, Nissan, Toyota, Honda);
const
CAR_MODELS = array[TCars] of string = ('Falcon','Sentra','Camry','Civic');
which allowed me to an enumeration declartively with some associated data. In this case a string but it could have been a record structure or similair. It meant that if I added a member to TCars and forgot to update the CAR_MODELS array I would get a compile time error.
What is the C# approach to this? I have tried:
public enum ReportFileGeneratorFileType
{
Excel,
Pdf,
Word
}
string[ReportFileGeneratorFileType] myArray = {"application/vnd.ms-excel", "application/pdf", "application/vnd.ms-word"};
but that does not appear to compile.
You should use Dictionary<key, value> as
var myArray = new Dictionary<ReportFileGeneratorFileType, string>();
myArray[ReportFileGeneratorFileType.Excel] = "application/vnd.ms-excel";
You could use an attribute (custom or built-int, such as DisplayNameAttribute) over the enum values to give them associated names/values. Example custom attribute follows:
public class ContentTypeAttribute : Attribute
{
public string ContentType { get; set; }
public ContentTypeAttribute(string contentType)
{
ContentType = contentType;
}
}
public enum ReportFileGeneratorFileType
{
[ContentType("application/vnd.ms-excel")]
Excel,
[ContentType("application/pdf")]
Pdf,
[ContentType("application/vnd.ms-word")]
Word
}
To retrieve the content type based on an enum value, use:
...
ReportFileGeneratorFileType myType = ...
string contentType = myType.GetType()
.GetCustomAttributes(typeof(ContentTypeAttribute))
.Cast<ContentTypeAttribute>()
.Single()
.ContentType;
...
This may seem kind of complicated, but lets you keep content types (or whatever data) right on the enum itself, so you're not likely to forget adding it after adding a new type.
In c# you can use Attributes, using this namespace
using System.ComponentModel;
You can add Description attribute to your enum elements
public enum ReportFileGeneratorFileType
{
[Description("application/vnd.ms-excel")]
Excel,
[Description("application/pdf")]
Pdf,
[Description("application/vnd.ms-word")]
Word
}
And using these methods you can "extract" a Dictionary<ReportFileGeneratorFileType, string> from your enum and use it on your code.