web-dev-qa-db-fra.com

Sérialiser l'énum à la chaîne

J'ai un enum:

public enum Action {
    Remove=1,
    Add=2
}

Et une classe:

[DataContract]
public class Container {
    [DataMember]
    public Action Action {get; set;}
}

Lors de la sérialisation de l'instance de Container sur json, j'obtiens: {Action:1} (dans le cas où Action est Remove).

Je voudrais obtenir: {Action:Remove} (au lieu de int, j'ai besoin de ToString sous forme de l'énum)

Puis-je le faire sans ajouter un autre membre à la classe?

43
Naor

Le formateur JSON a un comportement très spécialisé lorsqu'il utilise des énumérations. les attributs de contrat de données normaux sont ignorés et votre enum est traité comme un nombre, et non comme une chaîne plus lisible que celle attendue avec d'autres formats. Bien que cela facilite le traitement des énumérations de type indicateur, cela rend la plupart des autres types beaucoup plus difficile à utiliser.

De MSDN :

Les valeurs de membre Enumeration sont traitées comme des nombres dans JSON, qui est différent de la façon dont ils sont traités dans les contrats de données, où ils sont inclus comme noms de membre. Pour plus d'informations sur le contrat de données traitement, voir Types de dénombrement dans les contrats de données .

  • Par exemple, si vous avez public enum Color {red, green, blue, yellow, pink}, la sérialisation en jaune produit le nombre 3 et non la chaîne "jaune".

  • Tous les membres enum sont sérialisables. EnumMemberAttribute et le Les attributs NonSerializedAttribute sont ignorés s'ils sont utilisés.

  • Il est possible de désérialiser une valeur enum inexistante - par exemple, la valeur 87 peut être désérialisée dans la même couleur Color précédente bien qu'il n'y ait pas de nom de couleur correspondant défini.

  • Une énumération de drapeaux n'est pas spéciale et est traitée de la même manière que toute autre énumération.

Le seul moyen pratique de résoudre ce problème, pour permettre aux utilisateurs finaux de spécifier une chaîne au lieu d'un nombre, consiste à ne pas utiliser l'énum dans votre contrat. Au lieu de cela, la réponse pratique consiste à remplacer votre enum par une chaîne et à effectuer une validation interne de la valeur de sorte qu'elle puisse être analysée dans l'une des représentations d'énum valides.

Sinon, vous pouvez remplacer le formateur JSON par le vôtre, qui respectera les énumérations de la même manière que les autres formateurs.

21
Paul Turner

Avec Json.Net , vous pouvez définir une StringEnumConverter personnalisée comme 

public class MyStringEnumConverter : Newtonsoft.Json.Converters.StringEnumConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value is Action)
        {
            writer.WriteValue(Enum.GetName(typeof(Action),(Action)value));// or something else
            return;
        }

        base.WriteJson(writer, value, serializer);
    }
}

et sérialiser comme

string json=JsonConvert.SerializeObject(container,new MyStringEnumConverter());
30
L.B

Vous pouvez simplement ajouter l'attribut:

    [Newtonsoft.Json.Converters.JsonConverter(typeof(StringEnumConverter))] 

à la propriété enum qui ne sérialise pas en tant que chaîne.

ou si vous envisagez un formatage plus exotique, vous pouvez utiliser les attributs ci-dessous pour indiquer au sérialiseur JSON de ne sérialiser que la propriété que vous avez formatée à votre guise. Cela dépend un peu du reste de votre implémentation. Il reconnaît également l'attribut DataMember sur une propriété.

[JsonObject(MemberSerialization = MemberSerialization.OptOut)]
public class Container
{
    public Action Action { get; set; }

    [JsonProperty(PropertyName = "Action")]
    public string ActionString
    {
        get
        {
            return Action.ToString();
        }
    }
}
17
GPR

Voici un moyen simple de faire cela:

JsonConvert.SerializeObject(myObject, Formatting.Indented, new StringEnumConverter());
11
ShaTin

J'utilise une très bonne solution de contournement en utilisant une propriété privée auxiliaire pour la sérialisation et la désérialisation qui fonctionne pour la sérialisation par le nom du membre enum ou par la valeur de la variable EnumMemberAttribute.

Les plus grands avantages que je vois sont les suivants:

  • Vous n'avez pas besoin de Tweak avec le sérialiseur
  • Toute la logique de sérialisation est contenue dans l'objet de données
  • Vous pouvez masquer votre propriété auxiliaire en définissant son modificateur d'accessibilité sur privé, car les DataContractSerializers sont capables de Obtenir et définir des propriétés privées.
  • Vous pouvez sérialiser l'énumération en tant que string au lieu de int

Votre classe ressemblera à ceci:

[DataContract]
public class SerializableClass {
    public Shapes Shape {get; set;} //Do not use the DataMemberAttribute in the public property

    [DataMember(Name = "shape")]
    private string ShapeSerialization // Notice the PRIVATE here!
    {
        get { return EnumHelper.Serialize(this.Shape); }
        set { this.Shape = EnumHelper.Deserialize<Shapes>(value); }
    }
}

EnumHelper.cs

/* Available at: https://Gist.github.com/mniak/a4d09264ad1ca40c489178325b98935b */
public static class EnumHelper
{
    public static string Serialize<TEnum>(TEnum value)
    {
        var fallback = Enum.GetName(typeof(TEnum), value);
        var member = typeof(TEnum).GetMember(value.ToString()).FirstOrDefault();
        if (member == null)
            return fallback;
        var enumMemberAttributes = member.GetCustomAttributes(typeof(EnumMemberAttribute), false).Cast<EnumMemberAttribute>().FirstOrDefault();
        if (enumMemberAttributes == null)
            return fallback;
        return enumMemberAttributes.Value;
    }
    public static TEnum Deserialize<TEnum>(string value) where TEnum : struct
    {
        TEnum parsed;
        if (Enum.TryParse<TEnum>(value, out parsed))
            return parsed;

        var found = typeof(TEnum).GetMembers()
            .Select(x => new
            {
                Member = x,
                Attribute = x.GetCustomAttributes(typeof(EnumMemberAttribute), false).OfType<EnumMemberAttribute>().FirstOrDefault()
            })
            .FirstOrDefault(x => x.Attribute?.Value == value);
        if (found != null)
            return (TEnum)Enum.Parse(typeof(TEnum), found.Member.Name);
        return default(TEnum);
    }
}
3
Andre Soares

La solution publiée par Michal B fonctionne bien. Voici un autre exemple. 

Vous devez procéder comme suit car l'attribut Description n'est pas sérialisable.

[DataContract]
public enum ControlSelectionType
{
    [EnumMember(Value = "Not Applicable")]
    NotApplicable = 1,
    [EnumMember(Value = "Single Select Radio Buttons")]
    SingleSelectRadioButtons = 2,
    [EnumMember(Value = "Completely Different Display Text")]
    SingleSelectDropDownList = 3,
}


public static string GetDescriptionFromEnumValue(Enum value)
{
    EnumMemberAttribute attribute = value.GetType()
        .GetField(value.ToString())
        .GetCustomAttributes(typeof(EnumMemberAttribute), false)
        .SingleOrDefault() as EnumMemberAttribute;
    return attribute == null ? value.ToString() : attribute.Value;}
1
Theo Koekemoer

Pour des raisons de sérialisation, si le conteneur ne doit pas contenir de propriétés d'énumération mais est rempli, vous pouvez utiliser la méthode d'extension ci-dessous.

Définition du conteneur

public class Container
{
    public string Action { get; set; }
}

Définition d'énumération

public enum Action {
    Remove=1,
    Add=2
}

Code en vues

@Html.DropDownListFor(model => model.Action, typeof (Action))

Méthode d'extension

/// <summary>
/// Returns an HTML select element for each property in the object that is represented by the specified expression using the given enumeration list items.
/// </summary>
/// <typeparam name="TModel">The type of the model.</typeparam>
/// <typeparam name="TProperty">The type of the value.</typeparam>
/// <param name="htmlHelper">The HTML helper instance that this method extends.</param>
/// <param name="expression">An expression that identifies the object that contains the properties to display.</param>
/// <param name="enumType">The type of the enum that fills the drop box list.</param>
/// <returns>An HTML select element for each property in the object that is represented by the expression.</returns>
public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TProperty>> expression, Type enumType)
{
    var values = from Enum e in Enum.GetValues(enumType)
                    select new { Id = e, Name = e.ToString() };

    return htmlHelper.DropDownListFor(expression, new SelectList(values, "Id", "Name"));
}
0
ZenLulz

Essayez d'utiliser

public enum Action {
    [EnumMember(Value = "Remove")]
    Remove=1,
    [EnumMember(Value = "Add")]
    Add=2
}

Je ne sais pas si cela convient à votre cas, alors je me trompe peut-être.

C'est décrit ici: http://msdn.Microsoft.com/en-us/library/aa347875.aspx

0
Michal B.

J'ai mis une solution à cela en utilisant la bibliothèque Newtonsoft.Json. Il résout le problème d'énumération et améliore considérablement la gestion des erreurs. Il fonctionne dans les services hébergés IIS plutôt que dans ceux hébergés par l'utilisateur. Il ne nécessite aucune modification ni rien d’exceptionnel pour être ajouté à vos classes DataContract. C'est beaucoup de code, vous pouvez donc le trouver sur GitHub ici: https://github.com/jongrant/wcfjsonserializer/blob/master/NewtonsoftJsonFormatter.cs

Vous devez ajouter quelques entrées à votre Web.config pour que cela fonctionne. Vous pouvez voir un exemple de fichier ici: https://github.com/jongrant/wcfjsonserializer/blob/master/Web.config

0
Jon Grant