6

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.

2
  • 1
    Is the CurrentStatus property actually an array? Also, how is StatusEnum, which is of type System.Type, being serialized to an object that looks like that? Commented Sep 3, 2014 at 11:55
  • 1
    Sorry good catch. I missed that I had mixed up statusEnum and currentStatus on the output, fixed now. statusEnum should represent the status enum type with all its values while currentStatus should represent the single enum value that has been "selected" Commented Sep 3, 2014 at 12:05

2 Answers 2

8

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

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks mate, this was what I was looking for! Any suggestion on why break lines gets \r\n ?
@ArneHB: Do you mean in the generated JSON or within property values? You can remove line breaks by removing the Indented formatting option.
@AndrewWhitetaker Sorry mate my bad, I was a bit too trigger happy on the comment before I realized I tried to serialize it twice. Thanks for the help, its clean and nice solution! I moved the Converters so it's now working globaly on all JSON serializing
1

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"}
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.