DataContractJsonSerializer et Enums

quand je sérialise une valeur enum en utilisant DataContractJsonSerializer, il sérialise la valeur numérique de l'enum, pas le nom de la chaîne.

c'est à dire:

enum foo
{
   bar,
   baz
}

sérialisant une valeur de foo.bar renvoie "0", pas de "bar".

je préférerais l'inverse, y a-t-il un moyen de passer outre?

Edit:

parce que je ne voulais pas changer le sérialiseur, j'ai utilisé un simple contournement du piratage.

j'ai exposé une propriété dans la classe à serialize qui appelle ToString sur la valeur, ie:

// Old
[DataMember]
public EnumType Foo
{
    get { return _foo; }
    set { _foo = value; }
}

// New, I still kept the EnumType but I only serialize the string version

public EnumType Foo
{
    get { return _foo; }
    set { _foo = value; }
}

[DataMember]
public string FooType
{
    get { return _foo.ToString(); }
    private set {}
}
37
demandé sur FlySwat 2009-04-27 23:03:11

5 réponses

il semble que ce soit par conception et ce comportement ne peut pas être changé:

les valeurs des membres D'énumération sont traitées comme les nombres dans JSON, qui est différent de la façon dont ils sont traités dans les données contrats, lorsqu'ils sont inclus les noms des membres.

voici un exemple en utilisant une alternative (et IMO meilleur et plus extensible) serializer qui atteint ce qui vous recherchez:

using System;
using Newtonsoft.Json;

class Program
{
    static void Main(string[] args)
    {
        var baz = Foo.Baz;
        var serializer = new JsonSerializer();
        serializer.Converters.Add(new JsonEnumTypeConverter());
        serializer.Serialize(Console.Out, baz);
        Console.WriteLine();
    }
}

enum Foo
{
    Bar,
    Baz
}

public class JsonEnumTypeConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Foo);
    }
    public override void WriteJson(JsonWriter writer, object value)
    {
        writer.WriteValue(((Foo)value).ToString());
    }

    public override object ReadJson(JsonReader reader, Type objectType)
    {
        return Enum.Parse(typeof(Foo), reader.Value.ToString());
    }
}
25
répondu Darin Dimitrov 2009-04-27 19:47:07

pour obtenir la sérialisation à 2 voies / déserilisation pour wcf json vous pouvez ajouter une deuxième propriété get set de type string, lorsque vous construisez votre objet json en javascript utilisez la propriété string named, du côté du serveur utilisez la version fortement dactylographiée enum: par exemple

public class DTOSearchCriteria
{
    public int? ManufacturerID { get; set; }
    public int? ModelID { get; set; }


    private SortBy _sort;


    public SortBy SortType
    {
        get
        {
            return _sort;
        }
        set
        {
            _sort = value;
        }
    }

    public String Sort 
    {
        get
        {
            return _sort.ToString();
        }
        set
        {
            _sort = (SortBy) Enum.Parse(typeof(SortBy), value);
        }
    }





    public int PageSize { get; set; }
    public int PageNumber { get; set; }
}


public enum SortBy
{
    PriceDescending,
    PriceAscending
}
10
répondu Jason Roberts 2010-02-20 20:36:00

je suis devenu fou en essayant de trouver une solution élégante à ce problème car il semble que tout le monde a fait défaut à serializer de Newtonsoft pour contourner cette question. Bien que Newtonsoft fournit plus de fonctionnalités, il n'a pas certains inconvénients graves. Pour en énumérer quelques-uns: le besoin de constructeurs sans paramètres, comportement fou si vous souhaitez sérialiser les classes qui mettent en œuvre IEnumerable, et il fonctionne très mal lorsque les types abstraits sont utilisés (car il ne fait pas usage de l'attribut KnownTypes, et la solution de contournement génère une sortie verbeuse qui expose vos espaces de noms internes aux appelants).

d'autre part, il y a peu d'exemples sur la façon de personnaliser le DataContractJsonSerializer lors de son utilisation sur une solution WebApi MVC4.

il m'a fallu un certain temps pour trouver une solution qui représente enums comme chaînes de caractères et aborde les problèmes connus de formatage DateTime qui vient avec le DataContractJsonSerializer.

partie I-Put ces méthodes d'extension dans une classe extensions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#region JSon

    /// <summary>Serializes an object to JSon.</summary>
    /// <param name="obj">The object to serialize.</param>
    /// <returns>Returns a byte array with the serialized object.</returns>
    /// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
    public static byte[] SerializeJson(this object obj)
    {
        using (MemoryStream b = new MemoryStream())
        {
            SerializeJson(obj, b);
            return b.ToArray();
        }
    }

    /// <summary>Serializes an object to JSon.</summary>
    /// <param name="obj">The object to serialize.</param>
    /// <param name="stream">The stream to write to.</param>
    /// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
    public static void SerializeJson(this object obj, Stream stream)
    {
        var settings = new DataContractJsonSerializerSettings();
        settings.DateTimeFormat = new System.Runtime.Serialization.DateTimeFormat("yyyy-MM-dd'T'HH:mm:ssZ");
        settings.DataContractSurrogate = new EnumToStringDataContractSurrogate();

        var type = obj == null ? typeof(object) : obj.GetType();

        var enumerationValue = obj as System.Collections.IEnumerable;

        var fixedValue = enumerationValue != null
                         ? type.IsGenericType && !type.GetGenericArguments()[0].IsInterface
                            ? enumerationValue.ToArray(type.GetGenericArguments()[0])
                            : enumerationValue.OfType<object>().ToArray()
                         : obj;

        if (enumerationValue != null && (!type.IsGenericType || (type.IsGenericType || type.GetGenericArguments()[0].IsInterface)))
        {
            var firstMember = (fixedValue as System.Collections.IEnumerable).OfType<object>().FirstOrDefault();
            if (firstMember != null)
                fixedValue = enumerationValue.ToArray(firstMember.GetType());
        }

        var fixedType = obj == null
                        ? type
                        : fixedValue.GetType();

        var jsonSer = new DataContractJsonSerializer(fixedType, settings);
        jsonSer.WriteObject(stream, fixedValue);
    }

    /// <summary>
    /// Deserializes an object.
    /// </summary>
    /// <typeparam name="T">The output type of the object.</typeparam>
    /// <param name="data">The serialized contents.</param>
    /// <returns>Returns the typed deserialized object.</returns>
    /// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
    [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "JSon")]
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
    public static T DeserializeJSon<T>(this byte[] data)
    {
        using (MemoryStream b = new MemoryStream(data))
            return DeserializeJSon<T>(b);
    }

    /// <summary>Deserializes a JSon object.</summary>
    /// <typeparam name="T">The output type of the object.</typeparam>
    /// <param name="stream">The stream to read from.</param>
    /// <returns>Returns the typed object.</returns>
    /// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
    [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "JSon")]
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
    public static T DeserializeJSon<T>(this Stream stream)
    {
        var settings = new DataContractJsonSerializerSettings();
        settings.DateTimeFormat = new System.Runtime.Serialization.DateTimeFormat("yyyy-MM-dd'T'HH:mm:ssZ");
        settings.DataContractSurrogate = new EnumToStringDataContractSurrogate();

        var jsonSer = new DataContractJsonSerializer(typeof(T), settings);
        return (T)jsonSer.ReadObject(stream);
    }

    /// <summary>Deserializes a JSon object.</summary>
    /// <param name="data">The serialized contents.</param>
    /// <param name="targetType">The target type.</param>
    /// <returns>Returns the typed deserialized object.</returns>
    /// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
    [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "JSon")]
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
    public static object DeserializeJSon(this byte[] data, Type targetType)
    {
        using (MemoryStream b = new MemoryStream(data))
        {
            return DeserializeJSon(b, targetType);
        }
    }

    /// <summary>Deserializes a JSon object.</summary>
    /// <param name="data">The serialized contents.</param>
    /// <param name="targetType">The target type.</param>
    /// <returns>Returns the typed deserialized object.</returns>
    /// <remarks>This implementation outputs dates in the correct format and enums as string.</remarks>
    [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "JSon")]
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed.")]
    public static object DeserializeJSon(this Stream data, Type targetType)
    {
        var settings = new DataContractJsonSerializerSettings();
        settings.DateTimeFormat = new System.Runtime.Serialization.DateTimeFormat("yyyy-MM-dd'T'HH:mm:ssZ");
        settings.DataContractSurrogate = new EnumToStringDataContractSurrogate();

        var jsonSer = new DataContractJsonSerializer(targetType, settings);
        return jsonSer.ReadObject(data);            
    }

    /// <summary>Enumerator contract surrogate.</summary>
    internal class EnumToStringDataContractSurrogate : IDataContractSurrogate
    {
        Type IDataContractSurrogate.GetDataContractType(Type type)
        {
            return type == typeof(Enum) ? typeof(string) : type;
        }

        object IDataContractSurrogate.GetDeserializedObject(object obj, Type targetType)
        {
            if (targetType.IsEnum)
            {
                return obj == null
                       ? System.Enum.GetValues(targetType).OfType<int>().FirstOrDefault()
                       : System.Enum.Parse(targetType, obj.ToString());
            }
            return obj;
        }

        object IDataContractSurrogate.GetObjectToSerialize(object obj, Type targetType)
        {
            if (obj is Enum)
            {
                var pair = Enum.GetName(obj.GetType(), obj);
                return pair;
            }

            return obj;
        }

        object IDataContractSurrogate.GetCustomDataToExport(Type clrType, Type dataContractType)
        {
            throw new NotImplementedException();
        }

        object IDataContractSurrogate.GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
        {
            throw new NotImplementedException();
        }

        void IDataContractSurrogate.GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
        {
            throw new NotImplementedException();
        }

        Type IDataContractSurrogate.GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
        {
            throw new NotImplementedException();
        }

        System.CodeDom.CodeTypeDeclaration IDataContractSurrogate.ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
        {
            throw new NotImplementedException();
        }
    }

    #endregion


    /// <summary>Creates an array from a non generic source.</summary>
    /// <param name="source">The source.</param>
    /// <param name="type">The target type of the array.</param>
    /// <returns>Returns a typed array.</returns>
    public static Array ToArray(this IEnumerable source, Type type)
    {
        var param = Expression.Parameter(typeof(IEnumerable), "source");
        var cast = Expression.Call(typeof(Enumerable), "Cast", new[] { type }, param);
        var toArray = Expression.Call(typeof(Enumerable), "ToArray", new[] { type }, cast);
        var lambda = Expression.Lambda<Func<IEnumerable, Array>>(toArray, param).Compile();

        return lambda(source);
    }

partie II-créez votre propre formater en encapsulant le DataContractJsonSerializer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

/// <summary>Custom implementation of DataContract formatter.</summary>
public class DataContractJsonFormatter : MediaTypeFormatter
{

    /// <summary>Creates a new instance.</summary>
    public DataContractJsonFormatter()
    {
        SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
    }

    /// <summary>Gets if the formatter the write a given type.</summary>
    /// <param name="type">The type to handle.</param>
    /// <returns>Returns if the formatter the write a given type.</returns>
    public override bool CanWriteType(Type type)
    {
        return true;
    }

    /// <summary>Gets if the formatter the read a given type.</summary>
    /// <param name="type">The type to handle.</param>
    /// <returns>Returns if the formatter the read a given type.</returns>
    public override bool CanReadType(Type type)
    {
        return true;
    }

    /// <summary>Deserializes an object.</summary>
    /// <param name="type">The target type.</param>
    /// <param name="readStream">The stream to read from.</param>
    /// <param name="content">The http content.</param>
    /// <param name="formatterLogger">A logger.</param>
    /// <returns>Returns the deserialized object.</returns>
    public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, System.Net.Http.HttpContent content, IFormatterLogger formatterLogger)
    {
        var task = Task<object>.Factory.StartNew(() =>
        {
            return readStream.DeserializeJSon(type);
        });

        return task;
    }

    /// <summary>Serializes an object.</summary>
    /// <param name="type">The target type.</param>
    /// <param name="value">The object to serialize.</param>
    /// <param name="writeStream">The stream to write to.</param>
    /// <param name="content">The http content.</param>
    /// <param name="transportContext">The context.</param>
    /// <returns>Returns the deserialized object.</returns>
    public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
    {
        var task = Task.Factory.StartNew(() =>
        {
            value.SerializeJson(writeStream);
        });

        return task;
    }
}

PARTIE III - Modifier votre Global.asax fichier et consommer votre nouveau JSon formateur ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /// <summary>Event handlers of when the application starts.</summary>
    [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
    protected void Application_Start()
    {
       //Register my custom DataContract JSon serializer
        GlobalConfiguration.Configuration.Formatters.Insert(0, new DataContractJsonFormatter());

        //Register areas
        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
       // BundleConfig.RegisterBundles(BundleTable.Bundles);

        //JSON serialization config
        var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
        json.UseDataContractJsonSerializer = false;
    }
10
répondu Miguel 2016-07-07 15:04:08

edit: Désolé juste se leva pas le café :(

voici le code pour faire ce que vous voulez faire avec le Serializer JSON, pas le DataContractJsonSerializer.

Je n'ai pas encore fait de travail avec DataContractJsonSerializer, mais après avoir rapidement scanné les docs, je suis plutôt déçu dans MS.ils sont évidemment allés à des extrêmes pour rendre WCF très extensible, mais avec le DataContractJsonSerializer, il n'y a aucune extensibilité. Vous devez utiliser MS aromatisé JSON avec elle. SUPER boiteux de MS ... déjà en train de gâcher la WCF.

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

Certains Objets De Test & Enum:

    public enum SomeSillyEnum
    {
        Foo,Bar,Doo,Daa,Dee
    }

    public class UseSillyEnum
    {
        public SomeSillyEnum PublicEnum { get; set; }
        public string SomeOtherProperty { get; set; }
        public UseSillyEnum()
        {
            PublicEnum = SomeSillyEnum.Foo;
            SomeOtherProperty = "Testing";
        }
    }

JavaScriptConverters. Un pour tous les énums, et un pour un objet utilisant un enum.

public class EnumStringConverter : JavaScriptConverter
{
    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        foreach(string key in dictionary.Keys)
        {
            try { return Enum.Parse(type, dictionary[key].ToString(), false); }
            catch(Exception ex) { throw new SerializationException("Problem trying to deserialize enum from JSON.",ex); }
        }
        return Activator.CreateInstance(type);
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        Dictionary<string,object> objs = new Dictionary<string, object>();
        objs.Add(obj.ToString(), ((Enum)obj).ToString("D"));
        return objs;
    }

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

public class SillyConverter : JavaScriptConverter
{
    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        UseSillyEnum se = new UseSillyEnum();
        foreach (string key in dictionary.Keys)
        {
            switch(key)
            {
                case "PublicEnum":
                    se.PublicEnum = (SomeSillyEnum) Enum.Parse(typeof (SomeSillyEnum), dictionary[key].ToString(), false);
                    break;
                case "SomeOtherProperty":
                    se.SomeOtherProperty = dictionary[key].ToString();
                    break;
            }
        }
        return se;
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        UseSillyEnum se = (UseSillyEnum)obj;
        Dictionary<string, object> objs = new Dictionary<string, object>();
        objs.Add("PublicEnum", se.PublicEnum);
        objs.Add("SomeOtherProperty", se.SomeOtherProperty);
        return objs;
    }

    public override IEnumerable<Type> SupportedTypes { get { return new Type[] { typeof(UseSillyEnum) }; } }
}

et l'utiliser à l'intérieur d'une page:

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        /* Handles ALL Enums

        JavaScriptSerializer jsonSer = new JavaScriptSerializer(); 
        jsonSer.RegisterConverters( new JavaScriptConverter[] { new EnumStringConverter() } );

        string json = jsonSer.Serialize(new UseSillyEnum());
        Response.Write(json);

        UseSillyEnum obj = jsonSer.Deserialize<UseSillyEnum>(json);
        Response.Write(obj.PublicEnum);

        */

        /* Handles Object that uses an enum */
        JavaScriptSerializer jsonSer = new JavaScriptSerializer();
        jsonSer.RegisterConverters( new JavaScriptConverter[] { new SillyConverter() } );
        string json = jsonSer.Serialize(new UseSillyEnum());
        Response.Write(json);

        UseSillyEnum obj = jsonSer.Deserialize<UseSillyEnum>(json);
        Response.Write(obj.PublicEnum);
    }
}
3
répondu Chad Grant 2009-04-27 23:07:41

j'ai assemblé toutes les pièces de cette solution en utilisant la bibliothèque Newtonsoft.Json d'une manière qui fonctionne dans WCF. Il corrige le problème d'enum et améliore également la gestion des erreurs, et fonctionne dans les services hébergés par IIS. C'est beaucoup de code, donc vous pouvez le trouver sur GitHub ici: https://github.com/jongrant/wcfjsonserializer/blob/master/NewtonsoftJsonFormatter.cs

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

1
répondu Jon Grant 2016-07-12 13:31:17