Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Json.NET serializes enum as string even with default settings

Tags:

json

c#

json.net

I am using Json.NET 7.0.1.

The documentation says that

Enum [is serialized as] Integer (can be the enum value name with StringEnumConverter)

In my Global.asax.cs, I specify the default settings as follows:

JsonConvert.DefaultSettings = (() =>
{
    var settings = new JsonSerializerSettings();
    settings.Converters.Add(new StringEnumConverter());
    return settings;
});

However, in certain situations, I want Enums to be serialized as integers, for instance when I build a JSON which is to be stored in the database.

Here is how I went about it:

public class JsonSerializedType<T> : IUserType where T : class
{
    private static readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings();

    public void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        cmd.Parameters[index].Value = JsonConvert.SerializeObject(value as T, serializerSettings);
    }
}

However, even in this case Enums are serialized as strings. I checked that serializerSettings has no Converters and its ContractResolver is null.

Why is that so?

like image 720
twoflower Avatar asked Jul 08 '15 15:07

twoflower


2 Answers

This is happening because JsonConvert.SerializeObject applies the incoming settings on top of the default settings, by internally calling JsonSerializer.CreateDefault(JsonSerializerSettings settings). I don't know if/where this behavior is documented, but it's visible in the source code. Thus the default list of converters will be used in addition to the empty list of locally specified converters, meaning the default StringEnumConverter will get used.

You have a couple options to work around this:

  1. Construct the JsonSerializer yourself without using the default settings:

    public static class JsonExtensions
    {
        public static string SerializeObjectNoDefaultSettings(object value, Formatting formatting, JsonSerializerSettings settings)
        {
            var jsonSerializer = JsonSerializer.Create(settings);
            jsonSerializer.Formatting = formatting;
    
            StringBuilder sb = new StringBuilder(256);
            StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture);
            using (JsonTextWriter jsonWriter = new JsonTextWriter(sw))
            {
                jsonWriter.Formatting = jsonSerializer.Formatting;
                jsonSerializer.Serialize(jsonWriter, value);
            }
    
            return sw.ToString(); 
        }
    }
    

    And then use it like:

    var json = JsonExtensions.SerializeObjectNoDefaultSettings(value, Formatting.None, new JsonSerializerSettings());
    
  2. Supersede the default StringEnumConverter with one that does not serialize enums as strings, for instance:

    public class IntegerEnumConverter : StringEnumConverter
    {
        public override bool CanRead { get { return false; } }
    
        public override bool CanWrite { get { return false; } }
    }
    

    And then use it like:

    var json = JsonConvert.SerializeObject(value, Formatting.None, new JsonSerializerSettings { Converters = new JsonConverter[] { new IntegerEnumConverter() } });
    

    The locally specified converter will be picked up in preference to the default converter.

Temporarily nulling out the JsonConvert.DefaultSettings would be a bad idea since it would not be thread-safe.

like image 166
dbc Avatar answered Oct 26 '22 22:10

dbc


REASON

The behavior we see here is by design, passed settings in JsonConvert methods are getting merged with provided DefaultSettings, Here is a small part of Json.Net source code from JsonSerializer class to demystify the situation.

 private static void ApplySerializerSettings(JsonSerializer serializer, JsonSerializerSettings settings)
    {
        if (!CollectionUtils.IsNullOrEmpty(settings.Converters))
        {
            // insert settings converters at the beginning so they take precedence
            // if user wants to remove one of the default converters they will have to do it manually
            for (int i = 0; i < settings.Converters.Count; i++)
            {
                serializer.Converters.Insert(i, settings.Converters[i]);
            }
        }

As we can see Converters are merged with provided Default ones albeit with higher precedence.

Solution

We can take advantage of higher precedence of recently merged converters and prevent StringEnumConverter conversion.

class PreventStringEnumConverter : StringEnumConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var type = value.GetType();
        var undertype = Enum.GetUnderlyingType(type);
        var converted=Convert.ChangeType(value, undertype);
        writer.WriteValue(converted);
    }
}

and the usage would be like:

var json = JsonConvert.SerializeObject(obj,
        new JsonSerializerSettings
        {
            Converters = new List<JsonConverter> {new PreventStringEnumConverter()}
        });
like image 37
user3473830 Avatar answered Oct 27 '22 00:10

user3473830