JavaScriptSerializer и enum в строку: топ ошибок и как их избежать

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему сериализация 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 без дополнительных зависимостей.

Что выбрать для вашего проекта?

Чтобы не ошибиться, используйте простую логи

Блоги

Рекомендаций по книгам