JavaScriptSerializer: как сериализовать enum в строку 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.