JavaScriptSerializer - JSON-сериализация перечисления в виде строки

У меня есть класс, который содержит свойство enum, и после сериализации объекта с использованием JavaScriptSerializer мой результат json содержит целочисленное значение перечисления, а не его string "имя". Есть ли способ получить enum как string в моем json, не создавая пользовательский JavaScriptConverter? Возможно, есть атрибут, которым я мог бы украсить определение enum, или свойство объекта, с помощью?

Как пример:

enum Gender { Male, Female }

class Person
{
    int Age { get; set; }
    Gender Gender { get; set; }
}

Желаемый результат JSON:

{ "Age": 35, "Gender": "Male" }

В идеале ищите ответ со встроенными классами .NET Framework, если не возможны альтернативы (например, Json.net).

Ответы

Ответ 1

Нет, нет специального атрибута, который вы можете использовать. JavaScriptSerializer сериализует enums в их числовые значения, а не в их строковое представление. Вам потребуется использовать пользовательскую сериализацию для сериализации enum в качестве его имени вместо числового значения.


Если вы можете использовать JSON.Net вместо JavaScriptSerializer, см. ответ на этот вопрос, предоставленный OmerBakhari: JSON.net охватывает этот вариант использования (через атрибут [JsonConverter(typeof(StringEnumConverter))]) и многие другие не обрабатываются встроенными сериализаторами .net. Вот ссылка, сравнивающая функции и возможности сериализаторов.

Ответ 2

Я обнаружил, что Json.NET предоставляет именно ту функцию, которую я ищу, с атрибутом StringEnumConverter:

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

[JsonConverter(typeof(StringEnumConverter))]
public Gender Gender { get; set; }

Более подробную информацию можно найти в документации StringEnumConverter.

Есть другие места для настройки этого конвертера более глобально:

  • сам enum, если вы хотите, чтобы enum всегда был сериализован/десериализован как строка:

    [JsonConverter(typeof(StringEnumConverter))]  
    enum Gender { Male, Female }
    
  • Если кто-то хочет избежать украшения атрибутов, вы можете добавить конвертер в свой JsonSerializer (предложено Бьёрном Эгилем):

    serializer.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); 
    

    и он будет работать для каждого перечисления, которое он видит во время этой сериализации (предложено Трэвисом).

  • или JsonConverter (предложено бананом):

    JsonConvert.SerializeObject(MyObject, 
        new Newtonsoft.Json.Converters.StringEnumConverter());
    

Кроме того, вы можете контролировать регистр и принимать ли числа по-прежнему с помощью конструктора StringEnumConverter (NamingStrategy, Boolean).

Ответ 3

Добавьте ниже к вашему global.asax для сериализации JSON С# enum как строку

  HttpConfiguration config = GlobalConfiguration.Configuration;
            config.Formatters.JsonFormatter.SerializerSettings.Formatting =
                Newtonsoft.Json.Formatting.Indented;

            config.Formatters.JsonFormatter.SerializerSettings.Converters.Add
                (new Newtonsoft.Json.Converters.StringEnumConverter());

Ответ 4

@Игровый набор ответов JSON-сериализация С# enum как строка только для ASP.NET(веб-API и т.д.).

Но чтобы он работал и с сериализацией ad hoc, добавьте следующее к вашему стартовому классу (например, Global.asax Application_Start)

//convert Enums to Strings (instead of Integer) globally
JsonConvert.DefaultSettings = (() =>
{
    var settings = new JsonSerializerSettings();
    settings.Converters.Add(new StringEnumConverter { CamelCaseText = true });
    return settings;
});

Дополнительная информация на странице Json.NET

Кроме того, чтобы ваш член перечисления выполнял сериализацию/десериализацию в/из определенного текста, используйте

System.Runtime.Serialization.EnumMember

например:

public enum time_zone_enum
{
    [EnumMember(Value = "Europe/London")] 
    EuropeLondon,

    [EnumMember(Value = "US/Alaska")] 
    USAlaska
}

Ответ 5

Мне не удалось изменить исходную модель, как в верхнем ответе (@ob.), и я не хотел регистрировать ее по всему миру, как @Iggy. Поэтому я объединил fooobar.com/questions/12365/... и @Iggy fooobar.com/questions/12365/..., чтобы разрешить настройку конвертера перечислений строк во время сама команда SerializeObject:

Newtonsoft.Json.JsonConvert.SerializeObject(
    objectToSerialize, 
    Newtonsoft.Json.Formatting.None, 
    new Newtonsoft.Json.JsonSerializerSettings()
    {
        Converters = new List<Newtonsoft.Json.JsonConverter> {
            new Newtonsoft.Json.Converters.StringEnumConverter()
        }
    })

Ответ 6

Комбинация ответов Омера Бохари и ури - это мое решение, так как значения, которые я хочу предоставить, обычно отличаются от того, что у меня есть в моем перечислении специально, что я хотел бы иметь возможность изменять свои перечисления, если мне это нужно.

Итак, если кто-то заинтересован, это примерно так:

public enum Gender
{
   [EnumMember(Value = "male")] 
   Male,
   [EnumMember(Value = "female")] 
   Female
}

class Person
{
    int Age { get; set; }
    [JsonConverter(typeof(StringEnumConverter))]
    Gender Gender { get; set; }
}

Ответ 7

Это легко сделать, добавив атрибут ScriptIgnore в свойство Gender, в результате чего он не будет сериализован и GenderString свойство GenderString которое сериализуется:

class Person
{
    int Age { get; set; }

    [ScriptIgnore]
    Gender Gender { get; set; }

    string GenderString { get { return Gender.ToString(); } }
}

Ответ 8

Эта версия Stephen answer не изменяет имя в JSON:

[DataContract(
    Namespace = 
       "http://schemas.datacontract.org/2004/07/Whatever")]
class Person
{
    [DataMember]
    int Age { get; set; }

    Gender Gender { get; set; }

    [DataMember(Name = "Gender")]
    string GenderString
    {
        get { return this.Gender.ToString(); }
        set 
        { 
            Gender g; 
            this.Gender = Enum.TryParse(value, true, out g) ? g : Gender.Male; 
        }
    }
}

Ответ 9

Вот ответ для newtonsoft.json

enum Gender { Male, Female }

class Person
{
    int Age { get; set; }

    [JsonConverter(typeof(StringEnumConverter))]
    Gender Gender { get; set; }
}

Ответ 10

Основной способ ASP.NET:

public class Startup
{
  public IServiceProvider ConfigureServices(IServiceCollection services)
  {
    services.AddMvc().AddJsonOptions(options =>
    {
      options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
    });
  }
}

https://gist.github.com/regisdiogo/27f62ef83a804668eb0d9d0f63989e3e

Ответ 11

Вот простое решение, которое сериализует перечисление С# на стороне сервера для JSON и использует результат для заполнения клиентского элемента <select>. Это работает как для простых перечислений, так и для битфлаговых перечислений.

Я включил комплексное решение, потому что я думаю, что большинство людей, желающих сериализовать перечисление С# на JSON, также, вероятно, будут использовать его для заполнения раскрывающегося списка <select>.

Здесь:

Пример перечисления

public enum Role
{
    None = Permission.None,
    Guest = Permission.Browse,
    Reader = Permission.Browse| Permission.Help ,
    Manager = Permission.Browse | Permission.Help | Permission.Customise
}

Комплексное перечисление, которое использует побитовые ORs для создания системы разрешений. Таким образом, вы не можете полагаться на простой индекс [0,1,2..] для целочисленного значения перечисления.

Серверная сторона - С#

Get["/roles"] = _ =>
{
    var type = typeof(Role);
    var data = Enum
        .GetNames(type)
        .Select(name => new 
            {
                Id = (int)Enum.Parse(type, name), 
                Name = name 
            })
        .ToArray();

    return Response.AsJson(data);
};

В приведенном выше коде используется структура NancyFX для обработки запроса Get. Он использует вспомогательный метод Nancy Response.AsJson() - но не беспокойтесь, вы можете использовать любой стандартный форматировщик JSON, поскольку перечисление уже проецировано в простой анонимный тип, готовый к сериализации.

Сгенерированный JSON

[
    {"Id":0,"Name":"None"},
    {"Id":2097155,"Name":"Guest"},
    {"Id":2916367,"Name":"Reader"},
    {"Id":4186095,"Name":"Manager"}
]

Клиентская сторона - CoffeeScript

fillSelect=(id, url, selectedValue=0)->
    $select = $ id
    $option = (item)-> $ "<option/>", 
        {
            value:"#{item.Id}"
            html:"#{item.Name}"
            selected:"selected" if item.Id is selectedValue
        }
    $.getJSON(url).done (data)->$option(item).appendTo $select for item in data

$ ->
    fillSelect "#role", "/roles", 2916367

HTML до

<select id="role" name="role"></select>

HTML

<select id="role" name="role">
    <option value="0">None</option>
    <option value="2097155">Guest</option>
    <option value="2916367" selected="selected">Reader</option>
    <option value="4186095">Manager</option>
</select>

Ответ 12

Вы также можете добавить конвертер к вашему JsonSerializer, если вы не хотите использовать атрибут JsonConverter:

string SerializedResponse = JsonConvert.SerializeObject(
     objToSerialize, 
     new Newtonsoft.Json.Converters.StringEnumConverter()
); 

Он будет работать для каждого enum, который он видит во время этой сериализации.

Ответ 13

Для ядра ASP.Net Просто добавьте следующее в свой класс запуска:

JsonConvert.DefaultSettings = (() =>
        {
            var settings = new JsonSerializerSettings();
            settings.Converters.Add(new StringEnumConverter { AllowIntegerValues = false });
            return settings;
        });

Ответ 14

Вы можете создать JsonSerializerSettings с вызовом JsonConverter.SerializeObject, как показано ниже:

var result = JsonConvert.SerializeObject
            (
                dataObject,
                new JsonSerializerSettings
                {
                    Converters = new [] {new StringEnumConverter()}
                }
            );

Ответ 15

Для .Net Core: -

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddJsonFormatters(f => f.Converters.Add(new StringEnumConverter()));
    ...
}

Ответ 16

Отмечено, что нет ответа на сериализацию при наличии атрибута Description.

Вот моя реализация, которая поддерживает атрибут Description.

public class CustomStringEnumConverter : Newtonsoft.Json.Converters.StringEnumConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Type type = value.GetType() as Type;

        if (!type.IsEnum) throw new InvalidOperationException("Only type Enum is supported");
        foreach (var field in type.GetFields())
        {
            if (field.Name == value.ToString())
            {
                var attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
                writer.WriteValue(attribute != null ? attribute.Description : field.Name);

                return;
            }
        }

        throw new ArgumentException("Enum not found");
    }
}

Перечисление:

public enum FooEnum
{
    // Will be serialized as "Not Applicable"
    [Description("Not Applicable")]
    NotApplicable,

    // Will be serialized as "Applicable"
    Applicable
}

Использование:

[JsonConverter(typeof(CustomStringEnumConverter))]
public FooEnum test { get; set; }

Ответ 17

Это старый вопрос, но я думал, что буду способствовать на всякий случай. В моих проектах я использую отдельные модели для любых запросов Json. Модель обычно имеет то же имя, что и объект домена с префиксом "Json". Модели отображаются с помощью AutoMapper. Благодаря тому, что модель json объявляет свойство string, которое является перечислением в классе домена, AutoMapper разрешит ему строковое представление.

В случае, если вам интересно, мне нужны отдельные модели для сериализованных классов Json, потому что встроенный сериализатор в свою очередь предлагает круговые ссылки.

Надеюсь, это поможет кому-то.

Ответ 18

На всякий случай, если кто-либо обнаружит, что это недостаточно, я закончил с этой перегрузкой:

JsonConvert.SerializeObject(objToSerialize, Formatting.Indented, new Newtonsoft.Json.Converters.StringEnumConverter())

Ответ 19

На самом деле вы можете использовать JavaScriptConverter для достижения этой цели с помощью встроенного JavaScriptSerializer. Преобразовав ваше перечисление в Uri, вы можете закодировать его как строку.

Я описал, как сделать это для дат, но это может быть использовано и для перечислений.   Пользовательский формат DateTime JSON для .NET JavaScriptSerializer.

Ответ 20

Не уверен, что это все еще актуально, но мне пришлось писать прямо в файл json, и я придумал следующий набор из нескольких ответов stackoverflow

public class LowercaseJsonSerializer
{
    private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
    {
        ContractResolver = new LowercaseContractResolver()
    };

    public static void Serialize(TextWriter file, object o)
    {
        JsonSerializer serializer = new JsonSerializer()
        {
            ContractResolver = new LowercaseContractResolver(),
            Formatting = Formatting.Indented,
            NullValueHandling = NullValueHandling.Ignore
        };
        serializer.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
        serializer.Serialize(file, o);
    }

    public class LowercaseContractResolver : DefaultContractResolver
    {
        protected override string ResolvePropertyName(string propertyName)
        {
            return Char.ToLowerInvariant(propertyName[0]) + propertyName.Substring(1);
        }
    }
}

Это гарантирует, что все мои ключи JSON начинаются со строчной буквы в соответствии с "правилами" JSON. Форматирует его с чистым отступом и игнорирует нули в выходных данных. Также, добавив StringEnumConverter, он печатает перечисления с их строковым значением.

Лично я считаю, что это самое чистое, что я мог придумать, не пачкая модель аннотациями.

использование:

    internal void SaveJson(string fileName)
    {
        // serialize JSON directly to a file
        using (StreamWriter file = File.CreateText(@fileName))
        {
            LowercaseJsonSerializer.Serialize(file, jsonobject);
        }
    }

Ответ 21

Я собрал все части этого решения, используя библиотеку Newtonsoft.Json. Он исправляет проблему перечисления, а также делает обработку ошибок намного лучше, и она работает в службах IIS. Это довольно много кода, поэтому вы можете найти его на GitHub здесь: https://github.com/jongrant/wcfjsonserializer/blob/master/NewtonsoftJsonFormatter.cs

Вы должны добавить некоторые записи в свой Web.config, чтобы заставить его работать, вы можете увидеть пример файла здесь: https://github.com/jongrant/wcfjsonserializer/blob/master/Web.config

Ответ 22

А для VB.net я нашел следующие работы:

Dim sec = New Newtonsoft.Json.Converters.StringEnumConverter()
sec.NamingStrategy() = New Serialization.CamelCaseNamingStrategy

Dim JSON_s As New JsonSerializer
JSON_s.Converters.Add(sec)

Dim jsonObject As JObject
jsonObject = JObject.FromObject(SomeObject, JSON_s)
Dim text = jsonObject.ToString

IO.File.WriteAllText(filePath, text)

Ответ 23

Чуть более перспективный вариант

Столкнувшись с тем же вопросом, мы определили, что нам нужна специальная версия StringEnumConverter, чтобы убедиться, что наши значения enum могут со временем увеличиваться без катастрофического разрушения на стороне десериализации (см. фон ниже). Использование SafeEnumConverter ниже позволяет завершить десериализацию, даже если полезная нагрузка содержит значение для перечисления, у которого нет именованного определения, ближе к тому, как будет работать преобразование int-в-перечисление.

Использование:

[SafeEnumConverter]
public enum Colors
{
    Red,
    Green,
    Blue,
    Unsupported = -1
}

или

[SafeEnumConverter((int) Colors.Blue)]
public enum Colors
{
    Red,
    Green,
    Blue
}

Источник:

public class SafeEnumConverter : StringEnumConverter
{
    private readonly int _defaultValue;

    public SafeEnumConverter()
    {
        // if you've been careful to *always* create enums with '0' reserved
        // as an unknown/default value (which you should), you could use 0 here. 
        _defaultValue = -1;
    }

    public SafeEnumConverter(int defaultValue)
    {
        _defaultValue = defaultValue;
    }

    /// <summary>
    /// Reads the provided JSON and attempts to convert using StringEnumConverter. If that fails set the value to the default value.
    /// </summary>
    /// <returns>The deserialized value of the enum if it exists or the default value if it does not.</returns>
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        try
        {
            return base.ReadJson(reader, objectType, existingValue, serializer);
        }
        catch
        {
            return Enum.Parse(objectType, $"{_defaultValue}");
        }
    }

    public override bool CanConvert(Type objectType)
    {
        return base.CanConvert(objectType) && objectType.GetTypeInfo().IsEnum;
    }
}

фона

Когда мы смотрели на использование StringEnumConverter, у нас была проблема в том, что нам также требовалась пассивность для случаев, когда добавлялось новое значение перечисления, но не каждый клиент сразу знал о новом значении. В этих случаях StringEnumConverter, упакованный с Newtonsoft JSON, выдает JsonSerializationException, похожий на "Ошибка преобразования значения SomeString в тип EnumType", и тогда весь процесс десериализации завершается неудачей. Это стало для нас преградой, потому что даже если клиент планировал игнорировать/отбрасывать значение свойства, которое он не понимал, он все равно должен был быть способен десериализовать остальную часть полезной нагрузки!

Ответ 24

В .net core 3 это теперь возможно с помощью встроенных классов в System.Text.Json:

var person = new Person();
// Create and add a converter which will use the string representation instead of the numeric value.
var stringEnumConverter = new System.Text.Json.Serialization.JsonStringEnumConverter();
JsonSerializerOptions opts = new JsonSerializerOptions();
opts.Converters.Add(stringEnumConverter);
// Generate json string.
var json = JsonSerializer.Serialize<Person>(person, opts); 

Ответ 25

new JavaScriptSerializer().Serialize(  
    (from p   
    in (new List<Person>() {  
        new Person()  
        {  
            Age = 35,  
            Gender = Gender.Male  
        }  
    })  
    select new { Age =p.Age, Gender=p.Gender.ToString() }  
    ).ToArray()[0]  
);