JavaScriptSerializer - JSON serialization of enum as string
Asked Answered
B

31

1433

I have a class that contains an enum property, and upon serializing the object using JavaScriptSerializer, my json result contains the integer value of the enumeration rather than its string "name". Is there a way to get the enum as a string in my json without having to create a custom JavaScriptConverter? Perhaps there's an attribute that I could decorate the enum definition, or object property, with?

As an example:

enum Gender { Male, Female }

class Person
{
    int Age { get; set; }
    Gender Gender { get; set; }
}

Desired JSON result:

{ "Age": 35, "Gender": "Male" }

Ideally looking for answer with built-in .NET framework classes, if not possible alternatives (like Json.net) are welcome.

Boren answered 14/3, 2010 at 5:18 Comment(1)
Change to which? The highest upvoted answer doesn't actually answer the question - yes it is useful in other contexts, hence the votes, but it is of no practicable use whatsoever if you are stuck with the MS JavaScriptSerializer, as essentially you are if using page methods and, most importantly, as required by the question. The accepted answer says its not possible. My answer whilst a bit of a hack gets the job done.Cutanddried
S
462

No there is no special attribute you can use. JavaScriptSerializer serializes enums to their numeric values and not their string representation. You would need to use custom serialization to serialize the enum as its name instead of numeric value.


If you can use JSON.Net instead of JavaScriptSerializer than see answer on this question provided by Omer Bokhari: JSON.net covers this use case (via the attribute [JsonConverter(typeof(StringEnumConverter))]) and many others not handled by the built in .net serializers. Here is a link comparing features and functionalities of the serializers.

Segregationist answered 14/3, 2010 at 6:21 Comment(5)
@Fabzter -- your solution worked with me using Newtonsoft's JsonTend
@BornToCode Json.NET is the serializer that ASP.NET uses by default.Gutow
@Gutow - The question was about using JavaScriptSerializer, not Json.NET (and if you look at the revisions history you'll see that there was an edit to clarify that), if you use JavaScriptSerializer the attribute JsonConverter is not going to work.Extremadura
"Json.NET is the serializer that ASP.NET uses by default" -- This was not true when the question was asked or answered. (but the most important thing is the clarity of the answer)Pelkey
[JsonConverter(typeof(StringEnumConverter))] is for newtonsoftHurless
B
2418

I have found that Json.NET provides the exact functionality I'm looking for with a JsonConverter attribute, passing in the built-in StringEnumConverter type:

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

[JsonConverter(typeof(StringEnumConverter))]
public Gender Gender { get; set; }

More details at available on StringEnumConverter documentation.

There are other places to configure this converter more globally:

  • enum itself if you want enum always be serialized/deserialized as string:

      [JsonConverter(typeof(StringEnumConverter))]  
      enum Gender { Male, Female }
    
  • In case anyone wants to avoid attribute decoration, you can add the converter to your JsonSerializer (suggested by Bjørn Egil):

      serializer.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); 
    

and it will work for every enum it sees during that serialization (suggested by Travis).

  • or JsonConverter (suggested by banana):

      JsonConvert.SerializeObject(MyObject, 
          new Newtonsoft.Json.Converters.StringEnumConverter());
    

Additionally you can control casing and whether numbers are still accepted by using StringEnumConverter(NamingStrategy, Boolean) constructor.

Boren answered 20/5, 2010 at 0:58 Comment(10)
Follow by link for description how to use it in asp.net mvc application james.newtonking.com/archive/2008/10/16/…Hilleary
Here is the link to that function: james.newtonking.com/projects/json/help/html/…Hackman
HttpConfiguration config = GlobalConfiguration.Configuration; config.Formatters.JsonFormatter.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented; config.Formatters.JsonFormatter.SerializerSettings.Converters.Add (new Newtonsoft.Json.Converters.StringEnumConverter());Rew
It is useful to note by default ASP.NET MVC doesn't use Json.Net as json serializer and one need to either extend Controller or manually override every serialization.Maupassant
Is it possible to do this with the DataContractJsonSerializer?Socialist
You can customize the converter (say, for camelCase output): new StringEnumConverter { CamelCaseText = true }Chavez
as always Newtonsoft.Json does not disappoint, then i wonder why is XML still used 😌Tendril
The constructor now takes a naming strategy type (the bool param camelCaseText being deprecated), so if you want e.g. camelCase : new StringEnumConverter(typeof(CamelCaseNamingStrategy))Boesch
Omer Bokhari: if you agree with my edit on the answer consider to flag all comments as "other: remove all comments as they are inlined in the post". (Definitely mark this as "no longer needed" when you read it).Operand
The correct library to use here is actually: using System.Text.Json.Serialization;Carliecarlile
S
462

No there is no special attribute you can use. JavaScriptSerializer serializes enums to their numeric values and not their string representation. You would need to use custom serialization to serialize the enum as its name instead of numeric value.


If you can use JSON.Net instead of JavaScriptSerializer than see answer on this question provided by Omer Bokhari: JSON.net covers this use case (via the attribute [JsonConverter(typeof(StringEnumConverter))]) and many others not handled by the built in .net serializers. Here is a link comparing features and functionalities of the serializers.

Segregationist answered 14/3, 2010 at 6:21 Comment(5)
@Fabzter -- your solution worked with me using Newtonsoft's JsonTend
@BornToCode Json.NET is the serializer that ASP.NET uses by default.Gutow
@Gutow - The question was about using JavaScriptSerializer, not Json.NET (and if you look at the revisions history you'll see that there was an edit to clarify that), if you use JavaScriptSerializer the attribute JsonConverter is not going to work.Extremadura
"Json.NET is the serializer that ASP.NET uses by default" -- This was not true when the question was asked or answered. (but the most important thing is the clarity of the answer)Pelkey
[JsonConverter(typeof(StringEnumConverter))] is for newtonsoftHurless
S
193

@Iggy answer sets JSON serialization of c# enum as string only for ASP.NET (Web API and so).

But to make it work also with ad hoc serialization, add following to your start class (like Global.asax Application_Start)

//convert Enums to Strings (instead of Integer) globally
JsonConvert.DefaultSettings = (() =>
{
    var settings = new JsonSerializerSettings();
    settings.Converters.Add(new StringEnumConverter { CamelCaseText = true });
    return settings;
});

More information on the Json.NET page

Additionally, to have your enum member to serialize/deserialize to/from specific text, use the

System.Runtime.Serialization.EnumMember

attribute, like this:

public enum time_zone_enum
{
    [EnumMember(Value = "Europe/London")] 
    EuropeLondon,

    [EnumMember(Value = "US/Alaska")] 
    USAlaska
}
Sweptback answered 24/11, 2014 at 11:24 Comment(3)
Thank you! I was just looking for [EnumMember].Bradfordbradlee
The CamelCaseText property is now marked obsolete. New way to instantiate the converter: new StringEnumConverter(new CamelCaseNamingStrategy())Alis
the JsonConvert you place in ConfigureServices or in Configure in NET CORE 2?Diazo
R
182

Add the below to your global.asax for JSON serialization of c# enum as string

  HttpConfiguration config = GlobalConfiguration.Configuration;
            config.Formatters.JsonFormatter.SerializerSettings.Formatting =
                Newtonsoft.Json.Formatting.Indented;

            config.Formatters.JsonFormatter.SerializerSettings.Converters.Add
                (new Newtonsoft.Json.Converters.StringEnumConverter());
Rew answered 9/8, 2013 at 18:6 Comment(6)
For some reason, I'm not getting this to work. Fiddler shows a stubborn 2 rather than 'Warning', even with this in place. Also - any reason why to change the Formatting to Indented?Vivisect
The third line from this example was added to the App_start/webapiconfig.cs file and did a trick for me in an ASP.NET Web API 2.1 project to return strings for enum values in REST (json fomat) calls.Senzer
Is there a way to Set this property only per Request Scope?Macfarlane
@AnestisKivranoglou just make use a custom json serializer per request with it's own settings.Gutow
the first serializer setting of indented is unrelated to the op question.Canady
You'll need references for System.Net.Http.Formatting, System.Web.Http and System.Web.Http.WebHost.Sabrina
R
73

In .net core 3 this is now possible with the built-in classes in System.Text.Json (edit: System.Text.Json is also available as a NuGet package for .net core 2.0 and .net framework 4.7.2 and later versions according to the docs):

var person = new Person();
// Create and add a converter which will use the string representation instead of the numeric value.
var stringEnumConverter = new System.Text.Json.Serialization.JsonStringEnumConverter();
JsonSerializerOptions opts = new JsonSerializerOptions();
opts.Converters.Add(stringEnumConverter);
// Generate json string.
var json = JsonSerializer.Serialize<Person>(person, opts);

To configure JsonStringEnumConverter with attribute decoration for the specific property:

using System.Text.Json.Serialization;

[JsonConverter(typeof(JsonStringEnumConverter))]
public Gender Gender { get; set; }

If you want to always convert the enum as string, put the attribute at the enum itself.

[JsonConverter(typeof(JsonStringEnumConverter))] 
enum Gender { Male, Female }
Retrieval answered 4/10, 2019 at 10:51 Comment(1)
Nice and clean.Hannus
P
47

I wasn't able to change the source model like in the top answer (of @ob.), and I didn't want to register it globally like @Iggy. So I combined https://mcmap.net/q/45279/-javascriptserializer-json-serialization-of-enum-as-string and @Iggy's https://mcmap.net/q/45279/-javascriptserializer-json-serialization-of-enum-as-string to allow setting up the string enum converter on during the SerializeObject command itself:

Newtonsoft.Json.JsonConvert.SerializeObject(
    objectToSerialize, 
    Newtonsoft.Json.Formatting.None, 
    new Newtonsoft.Json.JsonSerializerSettings()
    {
        Converters = new List<Newtonsoft.Json.JsonConverter> {
            new Newtonsoft.Json.Converters.StringEnumConverter()
        }
    })
Purim answered 18/12, 2013 at 16:39 Comment(2)
this also woks nice if you have a property like this List<someEnumType>Tocantins
As @Tocantins mentioned, this was the fix for me to make a List<AnEnumType> property serialize with the string value of each Enum value instead of the number value.Alroy
G
43

The combination of Omer Bokhari and uri 's answers is alsways my solution since the values that I want to provide is usually different from what I have in my enum specially that I would like to be able to change my enums if I need to.

So if anyone is interested, it is something like this:

public enum Gender
{
   [EnumMember(Value = "male")] 
   Male,
   [EnumMember(Value = "female")] 
   Female
}

class Person
{
    int Age { get; set; }
    [JsonConverter(typeof(StringEnumConverter))]
    Gender Gender { get; set; }
}
Gav answered 16/8, 2017 at 10:52 Comment(1)
I was using JsonPropertyAttribute for enum members and it is working for simple deserializtion tasks. Sadly, during manual tweaks with JTokens it gets ignored. Happilly EnumMemberAttribute works like a charm. Thanks!Wager
C
34

This is easily done by adding a ScriptIgnore attribute to the Gender property, causing it to not be serialised, and adding a GenderString property which does get serialised:

class Person
{
    int Age { get; set; }

    [ScriptIgnore]
    Gender Gender { get; set; }

    string GenderString { get { return Gender.ToString(); } }
}
Cutanddried answered 15/3, 2012 at 17:38 Comment(13)
Let me try to explain. This solution isn't correct according to design paters. You modified the model according to view purpose. But model has to contain data only and doesn't care about presentations. You have to move this functionality on the other layer.Hilleary
Actually, Model is used to pass data from controller, ant it is controller, who does not care about presentation. Introduction of automated property (GenderString here) does not break controller, which still uses Gender property, but provides easy access for a view. Logical solution.Tunesmith
@Hilleary There is nothing wrong with having view-specific properties in the "view model". IMHO the mistake would be not to split the view model from the domain model: blogs.msdn.com/b/simonince/archive/2010/01/26/…Lollop
@RredCat, even if it were incorrect according to some pattern, the OP says nothing about this, so this is indeed a correct answer. (Even if I philosophically may agree with your point.)Jujube
@Stephen Kennedy: Why keep both properties in this case? Why not have string Gender only?Plausive
@Plausive GenderString is read only (no setter). You need to store the value somewhere. It could of course be a private property/field.Cutanddried
@Stephen Kennedy The question still stands: what is the benefit of having both values in a view model if only the string is used?Plausive
@Plausive It's a cheap way to achieve the required serialisation using the built-in JavaScriptSerializer, i.e. it answers the question. Only the string will be serialized so I really don't see the problem. If you want elegance use Json.Net; if you need a quick workaround to the issue posed and want to use asmx or page methods with the built-in serializer, here is the answer.Cutanddried
@Stephen Kennedy How is creating an additional property and using an attribute cheaper than changing property type to string? I mean what is the benefit to store the same data in the same object twice when one of them is ignored? Not to mention that this requires an awkward name to avoid conflict.Plausive
@Plausive The value is only stored once, as an enum, with the rationale that the enum property may be referenced elsewhere in the solution. It is also serialised only once, as a string, which is done on the fly. This solution has worked well for me and 26 upvoters. Feel free to modify the approach e.g. to a string property only if that suits you better (and see also myehman's variation of my answer). Just bear in mind this is a practical workaround for a slightly inadequate piece of MS functionality, not a design principle! I won't be commenting any further. Thanks.Cutanddried
Of course, deserializing using this method will cause problems since the property is readonlyBatsheva
The pedantically absurd bike-shedding in this comment thread is fascinating.Dishonor
The actual answer to @user3285954's question is that the enum is constrained to a limited set of values, while the string is not. Depending on whether you need to pass values back, this may or may not be sufficient.Posticous
K
33

ASP.NET Core way:

public class Startup
{
  public IServiceProvider ConfigureServices(IServiceCollection services)
  {
    services.AddMvc().AddJsonOptions(options =>
    {
      options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
    });
  }
}

https://gist.github.com/regisdiogo/27f62ef83a804668eb0d9d0f63989e3e

Kamkama answered 19/6, 2018 at 8:8 Comment(0)
O
29

This version of Stephen's answer doesn't change the name in the JSON:

[DataContract(
    Namespace = 
       "http://schemas.datacontract.org/2004/07/Whatever")]
class Person
{
    [DataMember]
    int Age { get; set; }

    Gender Gender { get; set; }

    [DataMember(Name = "Gender")]
    string GenderString
    {
        get { return this.Gender.ToString(); }
        set 
        { 
            Gender g; 
            this.Gender = Enum.TryParse(value, true, out g) ? g : Gender.Male; 
        }
    }
}
Onitaonlooker answered 26/4, 2012 at 13:55 Comment(4)
I believe this is valid for the DataContractJsonSerializer not JavaScriptSerializerDanieledaniell
Simple and solves the problem for me using native .NET framework serializers.Tinfoil
best solution for me as I am not allowed to use 3rd party libraries (ISO complience issues)Cloverleaf
This isn't for the type of serialiser in the question of course. JavaScriptSerializer serialises everything which isn't ignored, whereas DataContractJsonSerializer requires DataMember attributes. Thanks for the shout out but please note you spelt my name wrong :)Cutanddried
G
27

Here is the answer for newtonsoft.json

enum Gender { Male, Female }

class Person
{
    int Age { get; set; }

    [JsonConverter(typeof(StringEnumConverter))]
    Gender Gender { get; set; }
}
Gesso answered 21/4, 2015 at 9:47 Comment(1)
Thank you for this answer, helped me a lot! If you want to define your enums in PascalCase, but you want it to be serialized in camelCase then you need to add true to your JsonConverter type like this: [JsonConverter(typeof(StringEnumConverter), true)]Deck
A
27

Asp.Net Core 3 with System.Text.Json

public void ConfigureServices(IServiceCollection services)
{

    services
        .AddControllers()
        .AddJsonOptions(options => 
           options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())
        );

    //...
 }
Alleris answered 11/11, 2019 at 0:31 Comment(1)
This is exactly what I was looking for and couldn't seem to find anywhere else.Cheshvan
V
24

For .NET 6.0 if you want to use the built-in JsonSerializer (System.Text.Json)

Then, it comes out-of-the-box, you just need to use the built-in JsonStringEnumConverter attribute. For example:

[JsonConverter(typeof(JsonStringEnumConverter))]
public SomeEnumType EnumProperty { get; set; }

And that's it, BUT make sure your SomeEnumType contains values with the exact string values, otherwise it will throw an exception. Casing seems to be insensitive.

Reference: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-customize-properties?pivots=dotnet-6-0#enums-as-strings

Vyner answered 15/2, 2022 at 12:43 Comment(4)
We have it wired in the Program class for all conversions like this: services.AddControllers().AddJsonOptions(a => a.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())). What do you think?Kerato
How can we have this behavior without having to decorate every property with this attribute?Enamor
works also with full framework 4.8Cline
Thx, the Newtonsoft solution didnt work in my case. "using System.Text.Json.Serialization;" and it worked!Solace
A
20

You can also add a converter to your JsonSerializer if you don't want to use JsonConverter attribute:

string SerializedResponse = JsonConvert.SerializeObject(
     objToSerialize, 
     new Newtonsoft.Json.Converters.StringEnumConverter()
); 

It will work for every enum it sees during that serialization.

Aby answered 5/4, 2016 at 11:15 Comment(0)
B
17

For ASP.Net core Just add the following to your Startup Class:

JsonConvert.DefaultSettings = (() =>
        {
            var settings = new JsonSerializerSettings();
            settings.Converters.Add(new StringEnumConverter { AllowIntegerValues = false });
            return settings;
        });
Bergman answered 17/10, 2018 at 5:49 Comment(0)
Z
16

Here is a simple solution that serializes a server-side C# enum to JSON and uses the result to populate a client-side <select> element. This works for both simple enums and bitflag enums.

I have included the end-to-end solution because I think most people wanting to serialize a C# enum to JSON will also probably be using it to fill a <select> drop-down.

Here goes:

Example Enum

public enum Role
{
    None = Permission.None,
    Guest = Permission.Browse,
    Reader = Permission.Browse| Permission.Help ,
    Manager = Permission.Browse | Permission.Help | Permission.Customise
}

A complex enum that uses bitwise ORs to generate a permissions system. So you can't rely on the simple index [0,1,2..] for the integer value of the enum.

Server Side - C#

Get["/roles"] = _ =>
{
    var type = typeof(Role);
    var data = Enum
        .GetNames(type)
        .Select(name => new 
            {
                Id = (int)Enum.Parse(type, name), 
                Name = name 
            })
        .ToArray();

    return Response.AsJson(data);
};

The code above uses the NancyFX framework to handle the Get request. It uses Nancy's Response.AsJson() helper method - but don't worry, you can use any standard JSON formatter as the enum has already been projected into a simple anonymous type ready for serialization.

Generated JSON

[
    {"Id":0,"Name":"None"},
    {"Id":2097155,"Name":"Guest"},
    {"Id":2916367,"Name":"Reader"},
    {"Id":4186095,"Name":"Manager"}
]

Client Side - CoffeeScript

fillSelect=(id, url, selectedValue=0)->
    $select = $ id
    $option = (item)-> $ "<option/>", 
        {
            value:"#{item.Id}"
            html:"#{item.Name}"
            selected:"selected" if item.Id is selectedValue
        }
    $.getJSON(url).done (data)->$option(item).appendTo $select for item in data

$ ->
    fillSelect "#role", "/roles", 2916367

HTML Before

<select id="role" name="role"></select>

HTML After

<select id="role" name="role">
    <option value="0">None</option>
    <option value="2097155">Guest</option>
    <option value="2916367" selected="selected">Reader</option>
    <option value="4186095">Manager</option>
</select>
Zebedee answered 10/4, 2013 at 10:6 Comment(0)
I
13

You can create JsonSerializerSettings with the call to JsonConverter.SerializeObject as below:

var result = JsonConvert.SerializeObject
            (
                dataObject,
                new JsonSerializerSettings
                {
                    Converters = new [] {new StringEnumConverter()}
                }
            );
Intelligencer answered 17/6, 2015 at 12:29 Comment(0)
U
13

Noticed that there is no answer for serialization when there is a Description attribute.

Here is my implementation that supports the Description attribute.

public class CustomStringEnumConverter : Newtonsoft.Json.Converters.StringEnumConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Type type = value.GetType() as Type;

        if (!type.IsEnum) throw new InvalidOperationException("Only type Enum is supported");
        foreach (var field in type.GetFields())
        {
            if (field.Name == value.ToString())
            {
                var attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
                writer.WriteValue(attribute != null ? attribute.Description : field.Name);

                return;
            }
        }

        throw new ArgumentException("Enum not found");
    }
}

Enum:

public enum FooEnum
{
    // Will be serialized as "Not Applicable"
    [Description("Not Applicable")]
    NotApplicable,

    // Will be serialized as "Applicable"
    Applicable
}

Usage:

[JsonConverter(typeof(CustomStringEnumConverter))]
public FooEnum test { get; set; }
Unchurch answered 5/3, 2016 at 8:29 Comment(0)
G
10

For .Net Core :-

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddJsonFormatters(f => f.Converters.Add(new StringEnumConverter()));
    ...
}
Gout answered 30/4, 2017 at 22:47 Comment(1)
If this is the one from the Microsoft.AspNetCore.Mvc.Formatters.Json NuGet package, it seems to only be an extension method on IMvcCoreBuilder, not IMvcBuilder. So it's used like services.AddMvcCore().AddJsonFormatters(f => f.Converters.Add(new StringEnumConverter()));.Antonantone
B
10

Use this:

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

[Serializable]
[JsonConverter(typeof(StringEnumConverter))]
public enum Gender { Male, Female }
Bernitabernj answered 25/8, 2021 at 14:10 Comment(0)
E
8

Just in case anybody finds the above insufficient, I ended up settling with this overload:

JsonConvert.SerializeObject(objToSerialize, Formatting.Indented, new Newtonsoft.Json.Converters.StringEnumConverter())
Elurd answered 27/6, 2016 at 8:35 Comment(1)
This is a good solution for a current use case of mine: I don't want to change the serializers defaults and I have problems using attributes, because my properties are of type IList<EnumType>.Cle
L
5

This is an old question but I thought I'd contribute just in case. In my projects I use separate models for any Json requests. A model would typically have same name as domain object with "Json" prefix. Models are mapped using AutoMapper. By having the json model declare a string property that is an enum on domain class, AutoMapper will resolve to it's string presentation.

In case you are wondering, I need separate models for Json serialized classes because inbuilt serializer comes up with circular references otherwise.

Hope this helps someone.

Lucarne answered 27/3, 2012 at 15:58 Comment(2)
Nice to learn that feature of Automapper ;-) [ScriptIgnore] attribute will remove circular referencesHardwood
Oh. Didn't know about the attribute. Thanks! Would you use that on your Pocos? I've resorted to using MetadataType definitions for any Poco attributes just to keep them clean. Would the attribute still work via meta data?Lucarne
A
4

The namespace System.Text.Json.Serialization has JsonStringEnumConverter which can be used like below. [JsonConverter(typeof(JsonStringEnumConverter))]

Aprylapse answered 6/2, 2023 at 10:10 Comment(0)
F
3

You can actually use a JavaScriptConverter to accomplish this with the built-in JavaScriptSerializer. By converting your enum to a Uri you can encode it as a string.

I've described how to do this for dates but it can be used for enums as well. Custom DateTime JSON Format for .NET JavaScriptSerializer.

Fezzan answered 28/12, 2011 at 10:29 Comment(0)
M
2

A slightly more future-proof option

Facing the same question, we determined that we needed a custom version of StringEnumConverter to make sure that our enum values could expand over time without breaking catastrophically on the deserializing side (see background below). Using the SafeEnumConverter below allows deserialization to finish even if the payload contains a value for the enum that does not have a named definition, closer to how int-to-enum conversion would work.

Usage:

[SafeEnumConverter]
public enum Colors
{
    Red,
    Green,
    Blue,
    Unsupported = -1
}

or

[SafeEnumConverter((int) Colors.Blue)]
public enum Colors
{
    Red,
    Green,
    Blue
}

Source:

public class SafeEnumConverter : StringEnumConverter
{
    private readonly int _defaultValue;

    public SafeEnumConverter()
    {
        // if you've been careful to *always* create enums with `0` reserved
        // as an unknown/default value (which you should), you could use 0 here. 
        _defaultValue = -1;
    }

    public SafeEnumConverter(int defaultValue)
    {
        _defaultValue = defaultValue;
    }

    /// <summary>
    /// Reads the provided JSON and attempts to convert using StringEnumConverter. If that fails set the value to the default value.
    /// </summary>
    /// <returns>The deserialized value of the enum if it exists or the default value if it does not.</returns>
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        try
        {
            return base.ReadJson(reader, objectType, existingValue, serializer);
        }
        catch
        {
            return Enum.Parse(objectType, $"{_defaultValue}");
        }
    }

    public override bool CanConvert(Type objectType)
    {
        return base.CanConvert(objectType) && objectType.GetTypeInfo().IsEnum;
    }
}

Background

When we looked at using the StringEnumConverter, the problem we had is that we also needed passivity for cases when a new enum value was added, but not every client was immediately aware of the new value. In these cases, the StringEnumConverter packaged with Newtonsoft JSON throws a JsonSerializationException similar to "Error converting value SomeString to type EnumType" and then the whole deserialization process fails. This was a deal breaker for us, because even if the client planned on ignoring/discarding the property value that it didn't understand, it still needed to be capable of deserializing the rest of the payload!

Morose answered 6/8, 2019 at 18:46 Comment(2)
still relatively new to .NET but I've been told to avoid try catch expressions for performance reasons. Wouldn't a serializer be a bad place to use one if that is the case?Perishing
@Perishing The try...catch itself isn't going to impact performance, but if exception is raised, there's performance overhead for that. The point of this implementation is fault tolerance ... not allowing a single unknown enum value to prevent your entire JSON payload from deserializing. Compare the base StringEnumConveter: the exception would still be raised, but it will cause the whole deserialization process to fail (and its likely caught somewhere farther up the stack). Whether you need this kind of fault tolerance is an artifact of your use case(s).Morose
K
1

Not sure if this is still relevant but I had to write straight to a json file and I came up with the following piecing several stackoverflow answers together

public class LowercaseJsonSerializer
{
    private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
    {
        ContractResolver = new LowercaseContractResolver()
    };

    public static void Serialize(TextWriter file, object o)
    {
        JsonSerializer serializer = new JsonSerializer()
        {
            ContractResolver = new LowercaseContractResolver(),
            Formatting = Formatting.Indented,
            NullValueHandling = NullValueHandling.Ignore
        };
        serializer.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
        serializer.Serialize(file, o);
    }

    public class LowercaseContractResolver : DefaultContractResolver
    {
        protected override string ResolvePropertyName(string propertyName)
        {
            return Char.ToLowerInvariant(propertyName[0]) + propertyName.Substring(1);
        }
    }
}

It assures all my json keys are lowercase starting according to json "rules". Formats it cleanly indented and ignores nulls in the output. Aslo by adding a StringEnumConverter it prints enums with their string value.

Personally I find this the cleanest I could come up with, without having to dirty the model with annotations.

usage:

    internal void SaveJson(string fileName)
    {
        // serialize JSON directly to a file
        using (StreamWriter file = File.CreateText(@fileName))
        {
            LowercaseJsonSerializer.Serialize(file, jsonobject);
        }
    }
Kriemhild answered 19/12, 2018 at 8:28 Comment(0)
K
1

And for VB.net I found the following works:

Dim sec = New Newtonsoft.Json.Converters.StringEnumConverter()
sec.NamingStrategy() = New Serialization.CamelCaseNamingStrategy

Dim JSON_s As New JsonSerializer
JSON_s.Converters.Add(sec)

Dim jsonObject As JObject
jsonObject = JObject.FromObject(SomeObject, JSON_s)
Dim text = jsonObject.ToString

IO.File.WriteAllText(filePath, text)
Kerb answered 15/5, 2019 at 21:44 Comment(0)
V
0

I have put together all of the pieces of this solution using the Newtonsoft.Json library. It fixes the enum issue and also makes the error handling much better, and it works in IIS hosted services. It's quite a lot of code, so you can find it on GitHub here: https://github.com/jongrant/wcfjsonserializer/blob/master/NewtonsoftJsonFormatter.cs

You have to add some entries to your Web.config to get it to work, you can see an example file here: https://github.com/jongrant/wcfjsonserializer/blob/master/Web.config

Varden answered 12/7, 2016 at 10:45 Comment(0)
E
-1

For anyone needing a solution in May of '22 for .NET 6 and still using Newtonsoft, you can register the converter globally like this:

var builder = WebApplication.CreateBuilder(args);
...
builder.Services.AddControllers(options => options.RespectBrowserAcceptHeader = true)
.AddNewtonsoftJson(opt =>
{
    opt.SerializerSettings.ContractResolver = new DefaultContractResolver();
    opt.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
})
.AddXmlSerializerFormatters()
.AddXmlDataContractSerializerFormatters();
Energetic answered 10/5, 2022 at 13:11 Comment(0)
M
-4
        Person p = new Person();
        p.Age = 35;
        p.Gender = Gender.Male;
        //1.  male="Male";
        string male = Gender.Male.ToString();

        p.Gender = Gender.Female;

        //2.  female="Female";
        string female = Enum.GetName(typeof(Gender), p.Gender);

        JObject jobj = new JObject();
        jobj["Age"] = p.Age;
        jobj["Gender"] = male;
        jobj["Gender2"] = female;

        //you result:  josn= {"Age": 35,"Gender": "Male","Gender2": "Female"}
        string json = jobj.ToString();
Missal answered 26/12, 2019 at 3:17 Comment(0)
E
-5
new JavaScriptSerializer().Serialize(  
    (from p   
    in (new List<Person>() {  
        new Person()  
        {  
            Age = 35,  
            Gender = Gender.Male  
        }  
    })  
    select new { Age =p.Age, Gender=p.Gender.ToString() }  
    ).ToArray()[0]  
);
Epigenesis answered 19/4, 2016 at 14:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.