Подтвердить что ты не робот

Сериализовать перечисление в строку

У меня есть перечисление:

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

И класс:

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

При сериализации экземпляра контейнера в json я получаю: {Action:1} (в случае, если Action is Remove).

Я хотел бы получить: {Action:Remove} (вместо int мне нужна ToString форма перечисления)

Могу ли я это сделать, не добавляя в класс другого члена?

4b9b3361

Ответ 1

Форматирование JSON имеет очень специализированное поведение при работе с перечислениями; нормальные атрибуты Data Contract игнорируются, и он обрабатывает ваше перечисление как число, а не более удобочитаемую строку, которую вы ожидаете в других форматах. Хотя это упрощает работу с перечислениями типов флагов, с ними гораздо труднее работать с большинством других типов.

От MSDN:

Значения элемента перечисления рассматриваются как числа в JSON, что отличные от того, как они обрабатываются в контрактах с данными, где они включены как имена участников. Для получения дополнительной информации о контракте данных обратитесь к Типы перечислений в контрактах данных.

  • Например, если у вас public enum Color {red, green, blue, yellow, pink}, сериализация желтого цвета вызывает номер 3, а не строку "Желтый".

  • Все члены перечисления являются сериализуемыми. EnumMemberAttribute и Атрибуты NonSerializedAttribute игнорируются, если они используются.

  • Можно десериализовать несуществующее значение перечисления - например, значение 87 может быть десериализовано в предыдущее цветное перечисление хотя соответствующее имя цвета не определено.

  • Перечень флагов не является особым и обрабатывается так же, как и любое другое перечисление.

Единственный практический способ разрешить это, чтобы конечные пользователи могли указать строку вместо числа, заключается в том, чтобы не использовать перечисление в вашем контракте. Вместо этого практический ответ заключается в том, чтобы заменить ваше перечисление на строку и выполнить внутреннюю проверку на значение, чтобы его можно было проанализировать в одном из действительных представлений enum.

В качестве альтернативы (хотя и не для финта сердца) вы можете заменить форматировщик JSON своим собственным, что будет уважать перечисления так же, как и другие форматы.

Ответ 2

Используя 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());

Ответ 3

Вы можете просто добавить атрибут:

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

для свойства enum, которое не сериализуется как строка.

или если у вас есть более экзотическая форма, вы можете использовать атрибуты, как показано ниже, чтобы сообщить сериализатору JSON сериализовать только свойство, которое вы отформатировали по своему усмотрению. Зависит от остальной части вашей реализации. Он также распознает атрибут DataMember для свойства.

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

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

Ответ 4

Вот простой способ сделать это:

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

Ответ 5

Решение, опубликованное Michal B, работает хорошо. Вот еще один пример.

Вам нужно будет сделать следующее, поскольку атрибут описания не может быть сериализуем.

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

Ответ 6

Я использую очень хорошее обходное решение, используя вспомогательное частное свойство для сериализации и десериализации, которое работает либо для сериализации по имени члена перечисления, либо по значению EnumMemberAttribute.

Самые большие преимущества, которые я вижу, заключаются в следующем:

  • Вам не нужно настраивать сериализатор
  • Вся логика сериализации содержится в объекте данных
  • Вы можете скрыть свое вспомогательное свойство, установив его модификатор доступности на закрытый, поскольку DataContractSerializers могут получить и установить частные свойства
  • Вы можете сериализовать enum как 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);
    }
}

Ответ 7

Попробуйте использовать

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

Я не уверен, что это соответствует вашему делу, поэтому я могу ошибаться.

Здесь описано: http://msdn.microsoft.com/en-us/library/aa347875.aspx

Ответ 8

Для целей сериализации, если контейнер не должен содержать свойства перечисления, но заполняется, вы можете использовать метод расширения ниже.

Определение контейнера

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

Ответ 9

Я применил это решение, используя библиотеку 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