将枚举类型序列化为字符串

59

我有一个枚举:

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

还有一个类:

[DataContract]
public class Container {
    [DataMember]
    public Action Action {get; set;}
}
当将 Container 的实例序列化为 json 时,我得到了 {Action:1}(如果 Action 是 Remove)。
我想要得到的是:{Action:Remove}(代替 int,我需要枚举类型的 ToString 表示形式)。
我能否在不向类中添加另一个成员的情况下完成这个操作?

8
https://dev59.com/tnE95IYBdhLWcg3wI6bt#2870420 - skink
另一端的解码器是否支持将字符串解码为枚举类型?为什么需要这样做? - CodeCaster
有一个可能的解决方案,如何做到这一点: https://dev59.com/YHI95IYBdhLWcg3w2h56#2150498 - Alexander Farashutdinov
可能是将C#枚举序列化为字符串的JSON序列化的重复问题。 - nawfal
10个回答

37

您只需要添加属性:

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

对于无法序列化为字符串的枚举属性,可以使用以下属性将其序列化。

或者,如果您有更奇特的格式要求,您可以使用以下属性告诉JSON序列化器仅序列化您希望格式化的属性。这在某种程度上取决于其余实现。它也会识别属性上的DataMember属性。

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

    [JsonProperty(PropertyName = "Action")]
    public string ActionString
    {
        get
        {
            return Action.ToString();
        }
    }
}

5
请注意,命名空间略有更改:[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] - Thomas Schreiter

36

使用Json.Net,您可以定义一个自定义的StringEnumConverter,方法如下:

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);
    }
}

并将其序列化为

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

1
很好。我们需要通过JSON将枚举值发送到客户端,然后再发送回服务器,而用户无法进行选择。自定义格式化程序是解决问题的方法。感谢 L.B +1 - Nope
3
你可以对每个枚举类型都使用这段代码,不仅限于你自己的枚举类型:if (value.GetType().IsEnum) { writer.WriteValue(Enum.GetName(value.GetType(), value)); return; } - Gman
12
实际上... 你甚至不需要编写这个类 - 你可以直接传入 "new Newtonsoft.Json.Converters.StringEnumConverter()" - 这样的好处是它可以处理所有枚举类型并正确反序列化它们(不知道为什么,但是上述类不能正确反序列化枚举类型,尽管只是调用了基类方法)。 - BrainSlugs83
结合@Gman和原始答案,对于我的用例,在解析多个XML文档时它完美地工作。 - display name

28

这是一个简单的方法:

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

这可能是迄今为止最简单的解决方案。谢谢! - Nathan
最简单的解决方案。谢谢! - abhaybhatia

23
JSON格式化程序在处理枚举时具有非常专业的行为;普通数据合同属性被忽略,它将您的枚举视为数字,而不是其他格式中所期望的更易读的字符串。虽然这使得处理标志类型的枚举变得容易,但对于大多数其他类型来说,它使工作变得更加困难。
来自 MSDN
枚举成员值在JSON中被视为数字,这与它们在数据契约中的处理方式不同,在数据契约中,它们作为成员名称包含在内。有关数据契约处理的更多信息,请参见数据合同中的枚举类型
例如,如果您有public enum Color {red, green, blue, yellow,pink},则序列化yellow会产生数字3而不是字符串"yellow"。
所有枚举成员都是可序列化的。如果使用EnumMemberAttribute和NonSerializedAttribute属性,则会被忽略。
可以对不存在的枚举值进行反序列化——例如,值87可以反序列化为先前的Color枚举,即使没有定义相应的颜色名称。
标志枚举并不特别,与任何其他枚举一样处理。
“解决这个问题的唯一实用方法,允许最终用户指定字符串而不是数字,就是不在你的合约中使用枚举。相反,实际答案是用字符串替换枚举,并对值进行内部验证,以使其可以解析为有效的枚举表示之一。”
“或者(虽然不适合懦弱者),您可以用自己的JSON格式化程序替换它,这将以与其他格式化程序相同的方式尊重枚举。”

4
我一直使用一个很好的解决方法,通过使用辅助私有属性进行序列化和反序列化,该方法可以根据枚举成员名称或EnumMemberAttribute的值进行序列化。最大的优点是:
  • 您不需要调整序列化器
  • 所有序列化逻辑都包含在数据对象中
  • 您可以将辅助属性的可访问性修改为私有,因为DataContractSerializers能够获取和设置私有属性
  • 您可以将枚举序列化为string而不是int
您的类将如下所示:
[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);
    }
}

2
如果您正在使用.Net本机JSON序列化程序即System.Text.Json.Serialization,则可以在枚举上添加属性,以便将枚举转换为字符串而不是整数。
您应该向您想要作为字符串的枚举添加以下属性
[JsonConverter(typeof(JsonStringEnumConverter))]

0

4
这被记录为 JSON 格式化程序不支持的内容:http://msdn.microsoft.com/en-us/library/bb412170.aspx - Paul Turner
2
在Web Api 2.1项目中,与Newtonsoft.Json.Converters.StringEnumConverter一起使用对我很有效。 - Greg Z.

0
为了序列化的目的,如果容器不能包含枚举属性但又被填充了,您可以使用下面的扩展方法。
容器定义:
public class Container
{
    public string Action { get; set; }
}

枚举定义

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

视图中的代码

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

扩展方法

/// <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"));
}

0
Michal B发布的解决方案很好。这里是另一个例子。
由于Description属性不可序列化,您需要执行以下操作。
[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;}

0

我已经使用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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接