JavaScriptSerializer: как сериализовать enum в строку JSON, а не в число?
Решение: Создайте кастомный конвертер для JavaScriptSerializer
Да, JavaScriptSerializer из System.Web.Script.Serialization сам по себе не поддерживает строковую сериализацию enum. Но у него есть механизм расширения через интерфейс JavaScriptConverter. Мы напишем свой конвертер, который будет преобразовывать enum в строку.
Вот пошаговое решение:
using System;
using System.Collections.Generic;
using System.Web.Script.Serialization;
public class StringEnumConverter : JavaScriptConverter
{
public override IEnumerable<Type> SupportedTypes
{
get { return new[] { typeof(Enum) }; }
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
// Этот метод вызывается при десериализации (из JSON в объект)
// Для простоты примера мы не будем его реализовывать,
// так как вопрос только про сериализацию.
throw new NotImplementedException();
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
// obj — это значение enum (например, UserRole.Admin)
var enumValue = (Enum)obj;
string enumString = enumValue.ToString(); // "Admin"
// Возвращаем словарь с одним ключом, но для простых типов
// можно вернуть пустой словарь, а значение задать через return
// Но JavaScriptSerializer ожидает именно IDictionary.
// Используем хитрость: возвращаем словарь, где ключ — это имя свойства,
// но мы не знаем его. Поэтому лучше использовать другой подход.
// ПРАВИЛЬНЫЙ ПОДХОД: используем JavaScriptConverter для простых типов
// через переопределение Serialize для объекта целиком.
// Но проще всего — использовать атрибут [ScriptIgnore] + свойство-обертку.
}
}
На самом деле, JavaScriptConverter предназначен для сложных объектов, а не для простых значений. Для enum есть более простое решение — использовать свойство-обертку с атрибутом ScriptIgnore:
using System.Web.Script.Serialization;
public class User
{
public string Name { get; set; }
// Исходное свойство для enum (не будет сериализовано)
[ScriptIgnore]
public UserRole Role { get; set; }
// Свойство-обертка для сериализации в строку
public string RoleString
{
get { return Role.ToString(); }
set { Role = (UserRole)Enum.Parse(typeof(UserRole), value); }
}
}
Теперь при сериализации:
var user = new User { Name = "Иван", Role = UserRole.Admin };
var serializer = new JavaScriptSerializer();
string json = serializer.Serialize(user);
// Результат: {"Name":"Иван","RoleString":"Admin"}
Недостатки такого подхода:
- Приходится менять модель данных, добавлять дополнительные свойства.
- Имя свойства в JSON будет
RoleString, а неRole. - Если у вас много enum-свойств, придется для каждого писать обертку.
Более продвинутый вариант: Собственный JavaScriptConverter
Чтобы сохранить имя свойства Role и не менять модель, можно написать конвертер, который обрабатывает весь объект:
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Web.Script.Serialization;
public class EnumAsStringConverter : JavaScriptConverter
{
public override IEnumerable<Type> SupportedTypes
{
// Применяем ко всем типам (но можно ограничить конкретными)
get { return new[] { typeof(object) }; }
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
var result = new Dictionary<string, object>();
if (obj == null) return result;
Type type = obj.GetType();
foreach (PropertyInfo prop in type.GetProperties())
{
object value = prop.GetValue(obj);
if (value != null && prop.PropertyType.IsEnum)
{
// Сериализуем enum как строку
result[prop.Name] = value.ToString();
}
else if (value != null && !prop.PropertyType.IsPrimitive && prop.PropertyType != typeof(string))
{
// Для сложных типов рекурсивно вызываем сериализацию
result[prop.Name] = serializer.Serialize(value);
}
else
{
result[prop.Name] = value;
}
}
return result;
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
// Для десериализации потребуется отдельная логика
throw new NotImplementedException();
}
}
Затем регистрируем конвертер:
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { new EnumAsStringConverter() });
string json = serializer.Serialize(user);
// Результат: {"Name":"Иван","Role":"Admin"}
Итог: Самый простой и надежный способ для JavaScriptSerializer — использовать свойство-обертку с [ScriptIgnore]. Если нужно автоматическое решение для всех enum — пишите кастомный конвертер, но учтите, что он может не обработать вложенные объекты корректно.
Альтернативный подход: Использование Json.NET с сохранением JavaScriptSerializer
Если вы не можете полностью отказаться от JavaScriptSerializer, но можете добавить дополнительную библиотеку (например, Newtonsoft.Json), то можно сделать гибридный вариант:
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Web.Script.Serialization;
// Сериализуем через Json.NET, но возвращаем строку
public static class JsonHelper
{
private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
Converters = { new StringEnumConverter() },
Formatting = Formatting.None
};
public static string SerializeWithEnumAsString(object obj)
{
return JsonConvert.SerializeObject(obj, Settings);
}
}
// Использование:
string json = JsonHelper.SerializeWithEnumAsString(user);
// Результат: {"Name":"Иван","Role":"Admin"}
Этот подход не требует изменения модели данных или написания сложных конвертеров. Вы просто используете StringEnumConverter из Json.NET, который делает именно то, что нужно.
Почему это может быть приемлемо? Даже если проект "legacy", добавление NuGet-пакета Newtonsoft.Json обычно безопасно и не ломает существующий код. Вы не заменяете JavaScriptSerializer, а используете его для других целей, а для сериализации enum — новый метод.
Важно: Убедитесь, что в вашем проекте нет конфликтов версий. В крайнем случае, можно использовать System.Text.Json (доступен в .NET Framework 4.7+ через NuGet), у которого есть JsonStringEnumConverter.