Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON.Net serializing Enums to strings in dictionaries by default - how to make it serialize to int?

Tags:

c#

json.net

Why does my serialized JSON end up as

{"Gender":1,"Dictionary":{"Male":100,"Female":200}}

i.e. why do the enums serialize to their value, but when they form they key to the dictionary they are converted to their key?

How do I make them be ints in the dictionary, and why isn't this the default behaviour?

I'd expect the following output

{"Gender":1,"Dictionary":{"0":100,"1":200}}

My code:

    public void foo()
    {
        var testClass = new TestClass();
        testClass.Gender = Gender.Female;
        testClass.Dictionary.Add(Gender.Male, 100);
        testClass.Dictionary.Add(Gender.Female, 200);

        var serializeObject = JsonConvert.SerializeObject(testClass);

        // serializeObject == {"Gender":1,"Dictionary":{"Male":100,"Female":200}}
    }

    public enum Gender
    {
        Male = 0,
        Female = 1
    }

    public class TestClass
    {
        public Gender Gender { get; set; }
        public IDictionary<Gender, int> Dictionary { get; set; }

        public TestClass()
        {
            this.Dictionary = new Dictionary<Gender, int>();
        }
    }
}
like image 548
Andrew Bullock Avatar asked Aug 08 '14 10:08

Andrew Bullock


People also ask

How do you serialize a dictionary to a JSON string?

you can use the json. dumps(dict) to convert the dictionary to string.

Can JSON serialize enums?

In C#, JSON serialization very often needs to deal with enum objects. By default, enums are serialized in their integer form.

How does the JsonProperty attribute affect JSON serialization?

JsonPropertyAttribute indicates that a property should be serialized when member serialization is set to opt-in. It includes non-public properties in serialization and deserialization. It can be used to customize type name, reference, null, and default value handling for the property value.

Can JSON serialize a list?

Json.NET has excellent support for serializing and deserializing collections of objects. To serialize a collection - a generic list, array, dictionary, or your own custom collection - simply call the serializer with the object you want to get JSON for.


2 Answers

The reason why Gender enum is serialized to its value when used as property value, but it is serialized to its string representation when used as dictionary key is the following:

  • When used as property value JSON.NET serializer first writes the property name and after that the property value. For the example you posted, JSON.NET will write "Gender" as property name (notice that it writes a string), than will try to resolve the value of the property. The value of the property is of type enum which JSON.NET handles as Int32 and it writes the number representation of the enum

  • When serializing the dictionary, the keys are written as property names, so the JSON.NET serializer writes the string representation of the enum. If you switch the types of the keys and values in the dictionary (Dictionary<int, Gender> instead of Dictionary<Gender, int>, you'll verify that the enum will be serialized with its Int32 representation.

To achieve what you want with the example you posted, you'll need to write custom JsonConverter for the Dictionary property. Something like this:

public class DictionaryConverter : JsonConverter
{

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var dictionary = (Dictionary<Gender, int>) value;

        writer.WriteStartObject();

        foreach (KeyValuePair<Gender, int> pair in dictionary)
        {
            writer.WritePropertyName(((int)pair.Key).ToString());
            writer.WriteValue(pair.Value);
        }

        writer.WriteEndObject();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var jObject = JObject.Load(reader);

        var maleValue = int.Parse(jObject[((int) Gender.Male).ToString()].ToString());
        var femaleValue = int.Parse(jObject[((int)Gender.Female).ToString()].ToString());

        (existingValue as Dictionary<Gender, int>).Add(Gender.Male, maleValue);
        (existingValue as Dictionary<Gender, int>).Add(Gender.Female, femaleValue);

        return existingValue;
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof (IDictionary<Gender, int>) == objectType;
    }
}

and decorate the property in the TestClass:

public class TestClass
{
    public Gender Gender { get; set; }
    [JsonConverter(typeof(DictionaryConverter))]
    public IDictionary<Gender, int> Dictionary { get; set; }

    public TestClass()
    {
        this.Dictionary = new Dictionary<Gender, int>();
    }
}

When calling the following line for serialization:

var serializeObject = JsonConvert.SerializeObject(testClass);

you'll get the desired output:

{"Gender":1,"Dictionary":{"0":100,"1":200}}
like image 20
Ilija Dimov Avatar answered Oct 20 '22 18:10

Ilija Dimov


I often find myself facing this issue so I did a JsonConverter that can handle any kind of dictionnary with an Enum type as key:

public class DictionaryWithEnumKeyConverter<T, U> : JsonConverter where T : System.Enum
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var dictionary = (Dictionary<T, U>)value;

        writer.WriteStartObject();

        foreach (KeyValuePair<T, U> pair in dictionary)
        {
            writer.WritePropertyName(Convert.ToInt32(pair.Key).ToString());
            serializer.Serialize(writer, pair.Value);
        }

        writer.WriteEndObject();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var result = new Dictionary<T, U>();
        var jObject = JObject.Load(reader);

        foreach (var x in jObject)
        {
            T key = (T) (object) int.Parse(x.Key); // A bit of boxing here but hey
            U value = (U) x.Value.ToObject(typeof(U));
            result.Add(key, value);
        }

        return result;
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(IDictionary<T, U>) == objectType;
    }
}

NB: This will not handle Dictionnary<Enum, Dictionnary<Enum, T>

like image 151
deKajoo Avatar answered Oct 20 '22 19:10

deKajoo