Serialize enum to string
Asked Answered
H

10

60

I have an enum:

public enum Action {
    Remove=1,
    Add=2
}

And a class:

[DataContract]
public class Container {
    [DataMember]
    public Action Action {get; set;}
}

When serialize instance of Container to json I get: {Action:1} (in case Action is Remove).

I would like to get: {Action:Remove} (instead of int I need to ToString form of the enum)

Can I do it without adding another member to the class?

Hoarsen answered 6/2, 2012 at 7:21 Comment(4)
https://mcmap.net/q/45279/-javascriptserializer-json-serialization-of-enum-as-stringPathfinder
Does the decoder on the other side support decoding strings into enums? And why would you need this?Talus
there has one posible solution how do it #2121510Badger
possible duplicate of JSON serialization of c# enum as stringTrish
S
23

The JSON formatter has very specialized behaviour when working with enumerations; the normal Data Contract attributes are ignored and it treats your enum as a number, not the more human-readable string you'd expect with other formats. Whilst this makes it easy to deal with flag-type enumerations, it makes most other types much harder to work with.

From MSDN:

Enumeration member values are treated as numbers in JSON, which is different from how they are treated in data contracts, where they are included as member names. For more information about the data contract treatment, see Enumeration Types in Data Contracts.

  • For example, if you have public enum Color {red, green, blue, yellow, pink}, serializing yellow produces the number 3 and not the string "yellow".

  • All enum members are serializable. The EnumMemberAttribute and the NonSerializedAttribute attributes are ignored if used.

  • It is possible to deserialize a nonexistent enum value - for example, the value 87 can be deserialized into the previous Color enum even though there is no corresponding color name defined.

  • A flags enum is not special and is treated the same as any other enum.

The only practical way to resolve this, to allow end-users to specify a string instead of a number, is to not use the enum in your contract. Instead the practical answer is to replace your enum with a string and perform internal validation on the value such that it can be parsed into one of the valid enum representations.

Alternatively (though not for the feint of heart), you could replace the JSON formatter with your own, which would respect enumerations in the same way as other formatters.

Spearmint answered 6/2, 2012 at 7:41 Comment(0)
S
39

You can just add the attribute:

    [Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))] 

to the enum property that is not serializing as a string.

or if you have a more exotic formatting in mind you could use the attributes as below to tell the JSON serializer to serialise only the property that you have formatted as you wish. Depends a bit on the rest of your implementation. It recognises the DataMember attribute on a property as well.

[JsonObject(MemberSerialization = MemberSerialization.OptOut)]
public class Container
{
    public Action Action { get; set; }

    [JsonProperty(PropertyName = "Action")]
    public string ActionString
    {
        get
        {
            return Action.ToString();
        }
    }
}
Supercool answered 10/11, 2014 at 15:45 Comment(1)
FYI The namespace has changed slightly: [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]Astrosphere
C
36

Using Json.Net, you can define a custom StringEnumConverter as

public class MyStringEnumConverter : Newtonsoft.Json.Converters.StringEnumConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value is Action)
        {
            writer.WriteValue(Enum.GetName(typeof(Action),(Action)value));// or something else
            return;
        }

        base.WriteJson(writer, value, serializer);
    }
}

and serialize as

string json=JsonConvert.SerializeObject(container,new MyStringEnumConverter());
Cavanagh answered 6/2, 2012 at 11:58 Comment(4)
Nice one. We needed to send around the enum values through JSON to the client side and back around to the server without the user being able to make selections. Custom formatter was the way to go. Thanks L.B +1Blemish
You can do for every enum, not only yours: if (value.GetType().IsEnum) { writer.WriteValue(Enum.GetName(value.GetType(), value)); return; }World
Actually... you don't even need to write this class -- you can just pass in "new Newtonsoft.Json.Converters.StringEnumConverter()" -- which has the added benefit of working on all enums, and deserializing them properly as well (no idea why, but the above class does not deserialize the enums properly, even though, it's just the base class method getting called).Darbee
Combining @World and the original answer, it works perfectly for my use case where I have multiple XML docs parsing.Stonefish
A
30

Here's a simple way to do this:

JsonConvert.SerializeObject(myObject, Formatting.Indented, new StringEnumConverter());
Atabrine answered 14/8, 2014 at 2:56 Comment(1)
This is probably the simplest solution so far. Thanks!Caveat
S
23

The JSON formatter has very specialized behaviour when working with enumerations; the normal Data Contract attributes are ignored and it treats your enum as a number, not the more human-readable string you'd expect with other formats. Whilst this makes it easy to deal with flag-type enumerations, it makes most other types much harder to work with.

From MSDN:

Enumeration member values are treated as numbers in JSON, which is different from how they are treated in data contracts, where they are included as member names. For more information about the data contract treatment, see Enumeration Types in Data Contracts.

  • For example, if you have public enum Color {red, green, blue, yellow, pink}, serializing yellow produces the number 3 and not the string "yellow".

  • All enum members are serializable. The EnumMemberAttribute and the NonSerializedAttribute attributes are ignored if used.

  • It is possible to deserialize a nonexistent enum value - for example, the value 87 can be deserialized into the previous Color enum even though there is no corresponding color name defined.

  • A flags enum is not special and is treated the same as any other enum.

The only practical way to resolve this, to allow end-users to specify a string instead of a number, is to not use the enum in your contract. Instead the practical answer is to replace your enum with a string and perform internal validation on the value such that it can be parsed into one of the valid enum representations.

Alternatively (though not for the feint of heart), you could replace the JSON formatter with your own, which would respect enumerations in the same way as other formatters.

Spearmint answered 6/2, 2012 at 7:41 Comment(0)
A
4

I've been using a very good workaround by using an auxiliary private property for serialization and deserialization that works either for serialization by the enum member name or by the value of the EnumMemberAttribute.

The greatest advantages I see, are that:

  • You don't need to tweak with the serializer
  • All the serialization logic is contained in the Data Object
  • You can hide your auxiliary property by setting it's accessibility modifier to private, since the DataContractSerializers are able to get and set private properties
  • You are able to serialize the enum as a string instead of an int

Your class will look like this:

[DataContract]
public class SerializableClass {
    public Shapes Shape {get; set;} //Do not use the DataMemberAttribute in the public property

    [DataMember(Name = "shape")]
    private string ShapeSerialization // Notice the PRIVATE here!
    {
        get { return EnumHelper.Serialize(this.Shape); }
        set { this.Shape = EnumHelper.Deserialize<Shapes>(value); }
    }
}

EnumHelper.cs

/* Available at: https://gist.github.com/mniak/a4d09264ad1ca40c489178325b98935b */
public static class EnumHelper
{
    public static string Serialize<TEnum>(TEnum value)
    {
        var fallback = Enum.GetName(typeof(TEnum), value);
        var member = typeof(TEnum).GetMember(value.ToString()).FirstOrDefault();
        if (member == null)
            return fallback;
        var enumMemberAttributes = member.GetCustomAttributes(typeof(EnumMemberAttribute), false).Cast<EnumMemberAttribute>().FirstOrDefault();
        if (enumMemberAttributes == null)
            return fallback;
        return enumMemberAttributes.Value;
    }
    public static TEnum Deserialize<TEnum>(string value) where TEnum : struct
    {
        TEnum parsed;
        if (Enum.TryParse<TEnum>(value, out parsed))
            return parsed;

        var found = typeof(TEnum).GetMembers()
            .Select(x => new
            {
                Member = x,
                Attribute = x.GetCustomAttributes(typeof(EnumMemberAttribute), false).OfType<EnumMemberAttribute>().FirstOrDefault()
            })
            .FirstOrDefault(x => x.Attribute?.Value == value);
        if (found != null)
            return (TEnum)Enum.Parse(typeof(TEnum), found.Member.Name);
        return default(TEnum);
    }
}
Aeon answered 31/10, 2016 at 12:15 Comment(0)
C
2

If you are using .Net native json serializer i.e. System.Text.Json.Serialization, then you can add an attribute on enum so that it converts enum to string and not int.

You should add following attributes to enum which you want as a string

[JsonConverter(typeof(JsonStringEnumConverter))]

Collier answered 19/4, 2021 at 5:47 Comment(0)
F
0

Try using

public enum Action {
    [EnumMember(Value = "Remove")]
    Remove=1,
    [EnumMember(Value = "Add")]
    Add=2
}

I am not sure if this suits your case though, so I might be wrong.

It's described here: http://msdn.microsoft.com/en-us/library/aa347875.aspx

Fierce answered 6/2, 2012 at 7:41 Comment(2)
This is documented as not being supported by the JSON formatter: msdn.microsoft.com/en-us/library/bb412170.aspxSpearmint
worked for me in the Web Api 2.1 project together with Newtonsoft.Json.Converters.StringEnumConverterSapient
B
0

The solution posted by Michal B works good. Here is another example.

You would need to do the Following as the Description Attribute is not serializable.

[DataContract]
public enum ControlSelectionType
{
    [EnumMember(Value = "Not Applicable")]
    NotApplicable = 1,
    [EnumMember(Value = "Single Select Radio Buttons")]
    SingleSelectRadioButtons = 2,
    [EnumMember(Value = "Completely Different Display Text")]
    SingleSelectDropDownList = 3,
}


public static string GetDescriptionFromEnumValue(Enum value)
{
    EnumMemberAttribute attribute = value.GetType()
        .GetField(value.ToString())
        .GetCustomAttributes(typeof(EnumMemberAttribute), false)
        .SingleOrDefault() as EnumMemberAttribute;
    return attribute == null ? value.ToString() : attribute.Value;}
Bossuet answered 18/1, 2013 at 8:14 Comment(0)
R
0

For serialization purpose, if the container must not contain enumeration properties but are filled with, you can use the extension method below.

Container definition

public class Container
{
    public string Action { get; set; }
}

Enumeration definition

public enum Action {
    Remove=1,
    Add=2
}

Code in views

@Html.DropDownListFor(model => model.Action, typeof (Action))

Extension method

/// <summary>
/// Returns an HTML select element for each property in the object that is represented by the specified expression using the given enumeration list items.
/// </summary>
/// <typeparam name="TModel">The type of the model.</typeparam>
/// <typeparam name="TProperty">The type of the value.</typeparam>
/// <param name="htmlHelper">The HTML helper instance that this method extends.</param>
/// <param name="expression">An expression that identifies the object that contains the properties to display.</param>
/// <param name="enumType">The type of the enum that fills the drop box list.</param>
/// <returns>An HTML select element for each property in the object that is represented by the expression.</returns>
public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TProperty>> expression, Type enumType)
{
    var values = from Enum e in Enum.GetValues(enumType)
                    select new { Id = e, Name = e.ToString() };

    return htmlHelper.DropDownListFor(expression, new SelectList(values, "Id", "Name"));
}
Reiff answered 2/6, 2014 at 13:51 Comment(0)
W
0

I have put a solution to this 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 rather than self-hosted ones. It requires no changes or anything special to be added to your DataContract classes. 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

Wakerly answered 12/7, 2016 at 10:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.