Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Json.NET custom serialization of a enum type with data annotation

I want to serialize an enum type, so that it returns a array with the enums as a object that contains both the "value", "name" and a data annotation value. I need help with the serialization. Here's what I've done so far: The enum:

public enum Status
{
    [Display(Name="Active status")]
    Active = 1,
    [Display(Name = "Deactive status")]
    Deactive = 2,
    [Display(Name = "Pending status")]
    Pending = 3
}

The DTO Object that should be serialized:

public class ProjectDto
{
    public Type StatusEnum { get; set; }

    public Status CurrentStatus { get; set; }
}

Assigning of values:

var project = new ProjectDto
{
    CurrentStatus = Status.Active, 
    StatusEnum = typeof (Status)
};
var output = JsonConvert.SerializeObject(project);

To get the values from the enum I use:

Enum.GetNames(typeof(Status)) //To get the names in the enum
Enum.GetValues(typeof(Status)) //To get the values in the enum

To get the data annotation name value is a bit trickier but I found help in this article: http://geeksharp.com/2011/11/02/power-up-your-enumerations/ They've created a helper method that will get the value written in the data annotation using:

public static string GetAttributeValue<T>(this Enum e,
    Func<T, object> selector) where T : Attribute
{
    var output = e.ToString();
    var member = e.GetType().GetMember(output).First();
    var attributes = member.GetCustomAttributes(typeof(T), false);

    if (attributes.Length > 0)
    {
        var firstAttr = (T)attributes[0];
        var str = selector(firstAttr).ToString();
        output = string.IsNullOrWhiteSpace(str) ? output : str;
    }

    return output;
}

And you can get the value using:

.GetAttributeValue<DisplayAttribute>(y => y.Name)

Output should be something like

{
    statusEnum: [
        { "value": "1", "name": "Active", "label": "Active status" },
        { "value": "2", "name": "Deactive", "label": "Deactive status" },
        { "value": "3", "name": "Pending", "label": "Pending status" }
    ],
    currentStatus: { "value": "1", "name": "Active", "label": "Active status" }
}

As mentioned I need help creating the custom Json.NET serialize and deserialize to get the desired output. Any help would be apriciated.

like image 535
Arne H. Bitubekk Avatar asked Sep 03 '14 11:09

Arne H. Bitubekk


People also ask

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. This often causes a lack of interoperability with consumer applications because they need prior knowledge of what those numbers actually mean.

How do you serialize an enum?

To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant's name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java. lang. Enum.

How can we send enum value in JSON?

All you have to do is create a static method annotated with @JsonCreator in your enum. This should accept a parameter (the enum value) and return the corresponding enum. This method overrides the default mapping of Enum name to a json attribute .


2 Answers

Ok, this can probably be cleaned up a bit, but I would write two custom converters: one for the Enum type, and another for the enum value:

I created a custom class to serialize into the end result that you want:

public class EnumValue
{
    public int Value { get; set; }

    public string Name { get; set; }

    public string Label { get; set; }
}

As well as a static class that does some of the legwork for creating instances of that type from Enums and enum values:

public static class EnumHelpers
{
    public static EnumValue GetEnumValue(object value, Type enumType)
    {
        MemberInfo member = enumType.GetMember(value.ToString())[0];

        DisplayAttribute attribute = 
            member.GetCustomAttribute<DisplayAttribute>();

        return new EnumValue
        {
            Value = (int)value,
            Name = Enum.GetName(enumType, value),
            Label = attribute.Name
        };
    }

    public static EnumValue[] GetEnumValues(Type enumType)
    {
        Array values = Enum.GetValues(enumType);

        EnumValue[] result = new EnumValue[values.Length];

        for (int i = 0; i < values.Length; i++)
        {
            result[i] = GetEnumValue(
                values.GetValue(i),
                enumType);
        }

        return result;
    }
}

Then there are two converter classes. This first one serializes System.Type into the object you wanted:

public class EnumTypeConverter : JsonConverter
{
    public override void WriteJson(
        JsonWriter writer,
        object value,
        JsonSerializer serializer)
    {
        if (value == null)
        {
            writer.WriteNull();
            return;
        }

        EnumValue[] values = EnumHelpers.GetEnumValues((Type)value);

        serializer.Serialize(writer, values);
    }

    public override object ReadJson(
        JsonReader reader,
        Type objectType,
        object existingValue,
        JsonSerializer serializer)
    {
        throw new NotSupportedException();
    }

    public override bool CanRead { get { return false; } }

    public override bool CanConvert(Type objectType)
    {

        return typeof(Type).IsAssignableFrom(objectType);
    }
}

And then there's the one that serializes the actual enum value:

public class EnumValueConverter : JsonConverter
{
    public override void WriteJson(
        JsonWriter writer,
        object value,
        JsonSerializer serializer)
    {
        if (value == null)
        {
            writer.WriteNull();
            return;
        }

        EnumValue result = EnumHelpers.GetEnumValue(value, value.GetType());

        serializer.Serialize(writer, result);
    }

    public override object ReadJson(
        JsonReader reader,
        Type objectType,
        object existingValue,
        JsonSerializer serializer)
    {
        throw new NotSupportedException();
    }

    public override bool CanRead { get { return false; } }

    public override bool CanConvert(Type objectType)
    {

        return objectType.IsEnum;
    }
}

Here's how you would use all of it:

var pr = new ProjectDto();
pr.CurrentStatus = Status.Active;
pr.StatusEnum = typeof(Status);

var settings = new JsonSerializerSettings();
settings.Converters = new JsonConverter[] 
{
    new EnumTypeConverter(),
    new EnumValueConverter()
};
settings.Formatting = Newtonsoft.Json.Formatting.Indented;

string serialized = JsonConvert.SerializeObject(pr, settings);

Example: https://dotnetfiddle.net/BVp7a2

like image 154
Andrew Whitaker Avatar answered Nov 14 '22 23:11

Andrew Whitaker


Here's the approach I often take when faced with the same problem. (Here's a fiddle, if you want to jump straight to a working example)

Setting up the enums

I often find myself needing alternative values for enums. For this reason, I like to create an attribute to store these alternative values. For example:

[AttributeUsage(AttributeTargets.Field)]
public class AlternativeValueAttribute : Attribute
{
    public string JsonValue { get; set; }
    public string DbValue { get; set; }
    // and any other kind of alternative value you need...
}

(Note, the DbValue property is irrelevant for the purpose of this demonstration... It's just to demonstrate holding multiple alternative values.)

And when building my enum object's, I use this attribute on each value that requires an alternative value. For example:

public enum ObjectState
{
    [AlternativeValue(DbValue = "-1", JsonValue="is-unknown")]
    Unknown,

    [AlternativeValue(DbValue = "1", JsonValue="is-active")]
    Active, 

    [AlternativeValue(DbValue = "0", JsonValue="is-inactive")]
    Inactive
    // ...
}

Creating the converter

Now we need to create a converter to be able to utilise the alternative value. In this case, we're serialising/deserialising Json, so we'll create a JsonConverter:

public class AlternativeValueJsonConverter<TEnum> : JsonConverter where TEnum : struct, IConvertible, IComparable, IFormattable
{
    public override bool CanConvert( Type objectType )
    {
        // we can only convert if the type of object matches the generic type specified
        return objectType == typeof( TEnum );
    }

    public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
    {
        if( objectType == typeof(TEnum) )
        {
            // cycle through the enum values
            foreach(var item in (TEnum[])Enum.GetValues( typeof( TEnum ) ) )
            {
                // get the AlternativeValueAttribute, if it exists
                var attr = item.GetType().GetTypeInfo().GetRuntimeField( item.ToString() )
                    .GetCustomAttribute<AlternativeValueAttribute>();

                // if the JsonValue property matches the incoming value, 
                // return this enum value
                if (attr != null && attr.JsonValue == reader.Value.ToString())
                {
                    return item;
                }
            }
        }
        return null;
    }

    public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
    {
        if( value.GetType() == typeof( TEnum ) )
        {
            // cycle through the enum values
            foreach( var item in (TEnum[])Enum.GetValues( typeof( TEnum ) ) )
            {
                // if we've found the right enum value
                if (item.ToString() == value.ToString() )
                {
                    // get the attribute from the enum value
                    var attr = item.GetType().GetTypeInfo().GetRuntimeField( item.ToString() )
                        .GetCustomAttribute<AlternativeValueAttribute>();

                    if( attr != null)
                    {
                        // write out the JsonValue property's value
                        serializer.Serialize( writer, attr.JsonValue );
                    }
                }
            }
        }
    }
}

Usage

Lastly, to use this JsonConverter, we need to decorate our enum object with it. So the ObjectState enum we've already declared should be updated to use the converter. For example:

[JsonConverter(typeof(AlternativeValueJsonConverter<ObjectState>))]
public enum ObjectState
{
    [AlternativeValue(DbValue = "-1", JsonValue="is-unknown")]
    Unknown,

    [AlternativeValue(DbValue = "1", JsonValue="is-active")]
    Active, 

    [AlternativeValue(DbValue = "0", JsonValue="is-inactive")]
    Inactive
    // ...
}

Now, for demonstration purposes, we'll create a simple POCO that contains the ObjectState enum and convert it to Json to make sure we get the expected results:

public class DemoPoco
{
    public ObjectState MyObjectState { get; set; }
}

public static void Main( string[] args )
{
    DemoPoco demo = new DemoPoco { MyObjectState = ObjectState.Active };

    var json = JsonConvert.SerializeObject( demo );

    Console.WriteLine(json); // output: {"MyObjectState":"is-active"}
}
like image 35
devklick Avatar answered Nov 14 '22 23:11

devklick