Controlling JSON serialization in .Net Core Web API (Serialize enum values as strings, not integers)
.Net Core Web API uses Newtonsoft's Json.NET to do JSON serialization and for other cases where you wanted to control Json.NET options you would do something like
In my particular case I wanted to serialize enums as strings, not as integers. To do that, you need to use the StringEnumConverter class. For example if you wanted to serialize the Gender property of a person as a string you could have defined the entity like this:
In order to do this globally, add the converter to the settings converter list:
Note that in this case, I also instructed the converter to use camel case. The result of the serialization ends up as:
JsonConvert.DefaultSettings = (() =>, but in this case it doesn't work. The way to do it is to use the fluent interface method and hook yourself in the ConfigureServices(IServiceCollection services) method, after the call to .AddMvc(), like this:
{
var settings = new JsonSerializerSettings();
// do something with settings
return settings;
});
services
.AddMvc()
.AddJsonOptions(options =>
{
var settings=options.SerializerSettings;
// do something with settings
});
In my particular case I wanted to serialize enums as strings, not as integers. To do that, you need to use the StringEnumConverter class. For example if you wanted to serialize the Gender property of a person as a string you could have defined the entity like this:
public class Person
{
public string Name { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public GenderEnum Gender { get; set; }
}
In order to do this globally, add the converter to the settings converter list:
services
.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new StringEnumConverter {
CamelCaseText = true
});
});
Note that in this case, I also instructed the converter to use camel case. The result of the serialization ends up as:
{"name":"James Carpenter","age":51,"gender":"male"}
Comments
I have been all over Google for about 6-8 hours and tried every global solution I could find, but something about our serialization process was keeping them all from working, this was the only method that worked. Thank you SO much!
Sam SchneiderNo that's not what I'm doing. Don't worry about it, I'm obviously not able to articulate my issue very well. Thanks for the replies though and great blog.
GaryThen I don't see the problem. You always send the UTC date to the server and the transformation is done on the client. You display the date or you interpret the user input based on local application settings. There is no need to use custom serialization in JSON, since the data you would serialize is already in the correct format.
SideriteI use UTC to store dates and times, my user has a claim on it which they set as their timezone. Then when I return information back to them I convert it into the timezone they have configured on their claims principal not on their local machine which allows them to see dates and times in their preferred time zone regardless of where in the world they are.
GaryYou should never use local datetime in your software. Even if the user sees them as local, you should convert them to UTC before passing them to the API. Timezone is not a property of the user, but of the local environment. I can change my date and time settings at any time on my machine, the date that arrives on the server must take that into account. One possible trick is to always send the local time with the request. Then you know what the real timezone offset is by comparing with the server time.
SideriteYeah sorry, my requirement is to use a custom converter to serialize a DateTime property into ISO format. I can do this task okay but additionally to this as part of the conversion I need to append a time offset to the ISO formatted date. Unfortunately the time offset info is a property on the current user logged in, this is registered in the container at startup. What ideally would have worked would be if I could have injected my user object into the custom converter constructor, but obviously that is not possible as far as I know. I hope that is a bit clearer. Pretty much could I inject anything into the converter somehow?
GaryYou have to be more explicit. What is your current task? This blog post discusses JSON serialization of a C# object. You as talking about getting data from the user. I see no relation between these two cases.
SideriteHi, I have a similar requirement but my custom converter needs to read a value from the current user. Do you know of any way I could achieve this? I currently have an interface for my current user registered with my dependency container which requires the IHttpContextAccessor in the constructor method but I can't think of a way to use this, not sure if it is even possible.
GaryFirst I recommend installing PostMan, which will allow to query your API to see what the output is. This will make debugging much easier. Then, the result I got from the API call you wrote above was [ { "id": 1, "name": "student1" }, { "id": 2, "name": "student2" }, { "id": 3, "name": "student3" } ] Note the fact that the property names have been "jsonized", so element.Name will not cut it, you need to use "element.name". I've also used your Jquery code and it worked just fine (provided you use the right property name). To see what you get in the data, use the Javascript "console.log(data);" to see the object in the debug console of the browser.
SideriteHi, I am trying to get the json data from the controller using jquery ajax call and binding it to dropdown in asp.net core project. I am not getting the data. here is the code: public class HomeController : Controller { public IActionResult Index() { return View(); } [HttpPost] public IActionResult getStudents() { var students = new List<student>(); students.Add(new Student { ID = 1, Name = "student1" }); students.Add(new Student { ID = 2, Name = "student2" }); students.Add(new Student { ID = 3, Name = "student3" }); return Json(students); } } Here is my script <input type="submit" id="btnSubmit" value="Add Student"/> <select id="studentlist"></select> <script type="text/javascript"> $(document).ready(function () { $('#btnSubmit').click(function(event) { $.ajax({ type: "POST", url: "/Home/getStudents", // mvc controller returns data as json contentType:'application/json; charset=utf-8', datatype: "json", success: function (data) { $("#studentlist").empty(); data.forEach(function (element) { $("#studentlist").append("<option>" + element.Name + "</option>"); }); }, error: function () { alert("Error occured!!") } }); }); }); </script> Should I include any dependencies to make this wotk ?
sumaI just want to say that all the information you have given here is awesome...great and nice blog thanks sharing..
Sharon SandyArgh ... it was the default to preserve the case of the original object, (pre MVC6) then it wasn't (.net core 1.0) then it was again (.net core 1.1). Thanks, for showing the explicit way to set this.
rabbit envYou have provided an nice article, Thank you very much for this one.
john stanyGreat article! Thanks, this worked for me.
RemoWell, the source for the converter can be found here: https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Converters/StringEnumConverter.cs As I see it, it depends entirely of the JsonReader that it gets as a parameter, so if the JSON is invalid, you should look there. If not, it tries to parse the string values with EnumUtils.ParseEnumName and if it fails with an exception, it is caught in the try catch and another exception thrown. I don't see how this could lead to the behavior you mention, but you might try to create your own converter that uses TryParse and no exception catching and see if it goes any faster.
SideriteGreat info. I'm struggling still however trying to control more of the deserialization process. In the case of above all works great if you use values "male" or "female". But if you pass an invalid enum value, the service always immediately times out. If I can get it to not time out, it takes 30 seconds to respond with a full stack trace dump of why the enum value wasn't acceptable. I've been searching high and low for the means to quickly respond with 406 response indicating the property is invalid.
Nathan Risto