Сериализировать перечисление в строку - PullRequest
45 голосов
/ 06 февраля 2012

У меня есть enum:

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)

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

Ответы [ 9 ]

30 голосов
/ 06 февраля 2012

Используя 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());
21 голосов
/ 06 февраля 2012

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

С MSDN :

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

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

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

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

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

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

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

18 голосов
/ 10 ноября 2014

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

    [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();
        }
    }
}
12 голосов
/ 14 августа 2014

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

JsonConvert.SerializeObject(myObject, Formatting.Indented, new StringEnumConverter());
3 голосов
/ 31 октября 2016

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

НаибольшийЯ вижу следующие преимущества:

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

Решение от 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 голосов
/ 12 июля 2016

Я поставил решение этой проблемы с помощью библиотеки 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

0 голосов
/ 02 июня 2014

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

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

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 голосов
/ 06 февраля 2012

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

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

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

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

...