Почему сериализация enum — это головная боль каждого C# разработчика
Вы когда-нибудь сталкивались с ситуацией, когда ваш аккуратный C# код, работающий с перечислениями (enum), вдруг начинает выдавать странные цифры вместо понятных названий? Если да — вы не одиноки. Вопрос "JavaScriptSerializer - JSON serialization of enum as string" на Stack Overflow собрал тысячи просмотров, а это значит, что проблема знакома многим.
Давайте разберемся, почему JavaScriptSerializer по умолчанию превращает ваши красивые имена enum в безликие числа, и главное — как это исправить раз и навсегда.
Проблема: почему enum превращается в число?
По умолчанию JavaScriptSerializer (класс из пространства имен System.Web.Script.Serialization) сериализует перечисления как их целочисленные значения. Это связано с тем, что в основе каждого enum лежит целое число (int по умолчанию).
public enum Status
{
Active = 1,
Inactive = 2,
Pending = 3
}
public class Order
{
public int Id { get; set; }
public Status OrderStatus { get; set; }
}
// Сериализация
var order = new Order { Id = 1, OrderStatus = Status.Active };
var serializer = new JavaScriptSerializer();
string json = serializer.Serialize(order);
// Результат: {"Id":1,"OrderStatus":1}
Согласитесь, "OrderStatus":1 выглядит неинформативно. Представьте, что такой JSON уходит в API — клиентский код будет гадать, что означает цифра 1.
Топ-5 ошибок при работе с JavaScriptSerializer и enum
Ошибка 1: Использование EnumMemberAttribute без настройки
Многие разработчики пытаются применить [EnumMember] из пространства System.Runtime.Serialization, думая, что это решит проблему. Но JavaScriptSerializer не обрабатывает этот атрибут по умолчанию.
[DataContract]
public enum Status
{
[EnumMember(Value = "active")]
Active = 1,
[EnumMember(Value = "inactive")]
Inactive = 2
}
// JavaScriptSerializer все равно выдаст числа
Ошибка 2: Игнорирование ScriptIgnoreAttribute
Некоторые пытаются скрыть свойство enum, а потом добавить строковое представление отдельно. Это усложняет код и нарушает целостность данных.
Ошибка 3: Ручное преобразование через if/switch
Самый распространенный "костыль" — создание дополнительного строкового свойства и ручное преобразование:
public class Order
{
public int Id { get; set; }
public Status OrderStatus { get; set; }
// Дублирование данных — плохая практика
public string OrderStatusString
{
get { return OrderStatus.ToString(); }
}
}
Проблема: теперь у вас два свойства, которые нужно синхронизировать. Рано или поздно они разойдутся.
Ошибка 4: Забывание про регистр символов
Даже если вы победили сериализацию, клиентский код может ожидать "active", а получит "Active". Разница в регистре приводит к багам на стороне JavaScript.
Ошибка 5: Использование устаревшего JavaScriptSerializer вместо Newtonsoft.Json
Класс JavaScriptSerializer считается устаревшим в современных версиях .NET. Microsoft рекомендует использовать System.Text.Json или Newtonsoft.Json. Но если вы работаете с легаси-проектом или Web Forms, избежать его не получится.
Как правильно сериализовать enum в строку: 3 рабочих решения
Решение 1: Кастомный конвертер (рекомендуемый способ)
Создайте свой класс, наследующий от JavaScriptConverter, и зарегистрируйте его в сериализаторе.
public class EnumToStringConverter<T> : JavaScriptConverter where T : struct
{
public override IEnumerable<Type> SupportedTypes
{
get { return new[] { typeof(T) }; }
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
if (dictionary == null || dictionary.Count == 0)
return default(T);
// Предполагаем, что значение приходит как строка
string value = dictionary.Values.FirstOrDefault()?.ToString();
if (Enum.TryParse<T>(value, true, out T result))
return result;
return default(T);
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
var dict = new Dictionary<string, object>();
if (obj != null)
{
dict["value"] = obj.ToString().ToLower(); // Приводим к нижнему регистру
}
return dict;
}
}
// Использование
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { new EnumToStringConverter<Status>() });
var order = new Order { Id = 1, OrderStatus = Status.Active };
string json = serializer.Serialize(order);
// Результат: {"Id":1,"OrderStatus":{"value":"active"}}
Плюсы: Полный контроль над форматом, возможность переиспользования.
Минусы: Чуть больше кода, результат в виде вложенного объекта.
Решение 2: Использование атрибута ScriptIgnoreAttribute + отдельное свойство
Если вам нужно именно плоское представление (без вложенности), можно сделать так:
public class Order
{
public int Id { get; set; }
[ScriptIgnore]
public Status OrderStatus { get; set; }
// Это свойство будет сериализовано вместо OrderStatus
public string OrderStatusValue
{
get { return OrderStatus.ToString().ToLower(); }
set
{
if (Enum.TryParse<Status>(value, true, out Status result))
OrderStatus = result;
}
}
}
// Сериализация
var serializer = new JavaScriptSerializer();
var order = new Order { Id = 1, OrderStatus = Status.Active };
string json = serializer.Serialize(order);
// Результат: {"Id":1,"OrderStatusValue":"active"}
Плюсы: Плоский JSON, простота.
Минусы: Дублирование данных, риск рассинхронизации.
Решение 3: Полная замена на Newtonsoft.Json (если возможно)
Если проект позволяет, замените JavaScriptSerializer на Newtonsoft.Json — это решит проблему раз и навсегда с помощью встроенного конвертера строк.
// Установите пакет: Install-Package Newtonsoft.Json
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public class Order
{
public int Id { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public Status OrderStatus { get; set; }
}
// Сериализация
string json = JsonConvert.SerializeObject(order, new StringEnumConverter());
// Результат: {"Id":1,"OrderStatus":"Active"}
Плюсы: Минимум кода, поддержка camelCase, встроенная обработка.
Минусы: Требуется установка пакета, не подходит для чистого Web Forms без дополнительных зависимостей.
Что выбрать для вашего проекта?
Чтобы не ошибиться, используйте простую логи