Avatar ·

JavaScriptSerializer: как сериализовать enum в строку JSON, а не в число

Всем привет! Я только начал осваивать C# и ASP.NET, и столкнулся с проблемой при сериализации объектов в JSON с помощью JavaScriptSerializer. У меня есть такой enum: ```csharp public enum Status { Active = 1, Inactive = 2, Pending = 3 } ``` И класс, который его использует: ```csharp public class User { public string Name { get; set; } public Status UserStatus { get; set; } } ``` Когда я сериализую объект User: ```csharp var user = new User { Name = "John", UserStatus = Status.Active }; var json = new JavaScriptSerializer().Serialize(user); ``` Я получаю JSON вроде: `{"Name":"John","UserStatus":1}`. А мне нужно, чтобы статус был строкой, например: `{"Name":"John","UserStatus":"Active"}`. Проблема в том, что JavaScriptSerializer по умолчанию конвертирует enum в его числовое значение. В проекте много enum'ов, и менять их все вручную — это боль. Я слышал, что в Newtonsoft.Json есть атрибут `[JsonConverter(typeof(StringEnumConverter))]`, но я использую именно `JavaScriptSerializer` из System.Web.Script.Serialization. Как заставить `JavaScriptSerializer` сериализовать enum в строку? Может, есть какой-то глобальный способ или атрибут? Буду благодарен за примеры кода! P.S. Знаю, что JavaScriptSerializer считается устаревшим, но проект старый, и пока нет возможности перейти на Newtonsoft.Json или System.Text.Json. <<>>

Решение: кастомный конвертер для JavaScriptSerializer

Проблема в том, что JavaScriptSerializer не поддерживает атрибуты для enum'ов, как Newtonsoft.Json. Однако вы можете написать свой собственный конвертер, реализовав интерфейс JavaScriptConverter. Это позволит контролировать, как enum'ы (и любые другие типы) сериализуются и десериализуются.

Шаг 1: Создаём конвертер для enum

using System;
using System.Collections.Generic;
using System.Web.Script.Serialization;

public class EnumConverter : JavaScriptConverter
{
    public override IEnumerable<Type> SupportedTypes
    {
        get { return new[] { typeof(Enum) }; }
    }

    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        // Десериализация не требуется для данного примера, 
        // но если нужно, можно реализовать
        throw new NotImplementedException();
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        var enumValue = (Enum)obj;
        var dict = new Dictionary<string, object>();
        // Возвращаем строковое представление enum
        dict[""] = enumValue.ToString();
        return dict;
    }
}

Шаг 2: Регистрируем конвертер

Теперь нужно зарегистрировать конвертер в экземпляре JavaScriptSerializer. Лучше сделать это один раз и использовать этот экземпляр во всём приложении.

var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { new EnumConverter() });

var user = new User { Name = "John", UserStatus = Status.Active };
string json = serializer.Serialize(user);
// Результат: {"Name":"John","UserStatus":"Active"}

Шаг 3: Улучшенная версия с поддержкой вложенных enum

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

public class EnumConverter : JavaScriptConverter
{
    public override IEnumerable<Type> SupportedTypes
    {
        // Поддерживаем все типы, которые являются enum
        get 
        { 
            return new[] { typeof(Enum) }; 
        }
    }

    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        // Для десериализации строки обратно в enum
        if (dictionary.ContainsKey(""))
        {
            string value = dictionary[""] as string;
            if (value != null)
                return Enum.Parse(type, value);
        }
        return null;
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        var enumValue = (Enum)obj;
        var dict = new Dictionary<string, object>();
        dict[""] = enumValue.ToString();
        return dict;
    }
}

Глобальная настройка для всего приложения

Чтобы не регистрировать конвертер каждый раз, можно создать фабрику или статический класс:

public static class JsonHelper
{
    private static readonly JavaScriptSerializer _serializer = new JavaScriptSerializer();

    static JsonHelper()
    {
        _serializer.RegisterConverters(new[] { new EnumConverter() });
    }

    public static string Serialize(object obj)
    {
        return _serializer.Serialize(obj);
    }

    public static T Deserialize<T>(string json)
    {
        return _serializer.Deserialize<T>(json);
    }
}

Важные замечания

  • Производительность: Конвертер регистрируется один раз, поэтому накладные расходы минимальны.
  • Совместимость: Если вы используете FlagsAttribute (enum с флагами), конвертер будет возвращать строку, например "Active, Inactive". Это может быть нежелательно, но легко исправить, переопределив метод Serialize.
  • Альтернатива: Если есть возможность, переходите на System.Text.Json (начиная с .NET Core 3.0) или Newtonsoft.Json — у них есть встроенная поддержка сериализации enum в строку через атрибуты или глобальные настройки.

Этот подход работает и для старых проектов на .NET Framework, и для .NET Core (с пакетом Microsoft.AspNet.WebApi.Client). Попробуйте — он решит вашу проблему без изменения всех enum'ов!

<<>>

Альтернативный подход: использование атрибута ScriptIgnore и кастомного свойства

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

public class User
{
    public string Name { get; set; }

    [ScriptIgnore] // Игнорируем оригинальное свойство
    public Status UserStatus { get; set; }

    // Добавляем строковое представление для сериализации
    public string UserStatusString
    {
        get { return UserStatus.ToString(); }
        set { UserStatus = (Status)Enum.Parse(typeof(Status), value); }
    }
}

Теперь при сериализации будет использоваться UserStatusString:

var json = new JavaScriptSerializer().Serialize(user);
// Результат: {"Name":"John","UserStatusString":"Active"}

Недостатки

  • Для каждого enum'а нужно добавлять отдельное свойство — это нарушает DRY.
  • Имя свойства в JSON будет другим (UserStatusString вместо UserStatus). Это можно исправить, используя ScriptIgnore и ручное переименование, но код станет громоздким.
  • Не подходит для больших проектов с десятками enum'ов.

Этот метод — быстрый "костыль" для маленьких проектов или прототипов. Для серьёзной разработки рекомендую первый вариант с конвертером или переход на более современные библиотеки JSON.