关于c#:将enum序列化为字符串

Serialize enum to string

我有一个枚举:

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

还有一节课:

1
2
3
4
5
[DataContract]
public class Container {
    [DataMember]
    public Action Action {get; set;}
}

将容器的实例序列化到JSON时,我得到:{Action:1}(如果操作是remove)。

我想得到:{Action:Remove}(而不是int,我需要字符串形式的枚举)

我可以在不向该类添加其他成员的情况下执行此操作吗?


使用json.net,可以将自定义StringEnumConverter定义为

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
    }
}

并序列化为

1
string json=JsonConvert.SerializeObject(container,new MyStringEnumConverter());


JSON格式化程序在处理枚举时具有非常特殊的行为;忽略了正常的数据契约属性,它将枚举视为一个数字,而不是其他格式所期望的更具可读性的字符串。虽然这使得处理标志类型枚举变得容易,但它使大多数其他类型更难处理。

来自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.

解决这一问题的唯一实际方法,即允许最终用户指定字符串而不是数字,是不要在合同中使用枚举。相反,实际的答案是用字符串替换枚举,并对该值执行内部验证,以便将其解析为有效的枚举表示形式之一。

或者(尽管不是假装的),您可以用您自己的JSON格式化程序替换它,这与其他格式化程序一样尊重枚举。


您只需添加属性:

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

未序列化为字符串的枚举属性。

或者,如果您有一个更奇特的格式化思想,您可以使用下面的属性告诉JSON序列化程序只序列化您所格式化的属性。稍微依赖于您的其他实现。它还识别属性上的datamember属性。

10


以下是一个简单的方法:

1
JsonConvert.SerializeObject(myObject, Formatting.Indented, new StringEnumConverter());


我一直在使用一个非常好的解决方法,即使用一个辅助私有属性进行序列化和反序列化,该属性可以通过枚举成员名称或EnumMemberAttribute的值进行序列化。

我看到的最大好处是:

  • 你不需要调整序列化程序
  • 所有序列化逻辑都包含在数据对象中
  • 通过将辅助属性的可访问性修饰符设置为private,可以隐藏辅助属性,因为DataContractSerializer能够获取和设置私有属性
  • 您可以将枚举序列化为string,而不是int

您的课程将如下所示:

1
2
3
4
5
6
7
8
9
10
11
[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); }
    }
}

枚举数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* 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);
    }
}

Michal B发布的解决方案效果良好。这是另一个例子。

您需要执行以下操作,因为描述属性不可序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[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;}


我已经使用Newtonsoft.Json库对此提出了解决方案。它修复了枚举问题,并使错误处理变得更好,它在IIS托管服务中工作,而不是在自托管服务中工作。它不需要更改或任何特殊的内容来添加到您的DataContract类中。它有很多代码,所以你可以在Github上找到它:https://github.com/jongrant/wcfjsonserializer/blob/master/newtonsoftjsonformatter.cs

您必须在Web.config中添加一些条目才能使其正常工作,您可以在这里看到一个示例文件:https://github.com/jongrant/wcfjsonserializer/blob/master/web.config


出于序列化目的,如果容器不能包含枚举属性,但已填充,则可以使用下面的扩展方法。

容器定义

1
2
3
4
public class Container
{
    public string Action { get; set; }
}

枚举定义

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

视图中的代码

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

扩展方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// <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"));
}

试用使用

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

不过,我不确定这是否适合你的情况,所以我可能错了。

此处介绍:http://msdn.microsoft.com/en-us/library/aa347875.aspx