2009-04-27 16 views
37

Cuando serializo un valor enum usando DataContractJsonSerializer, serializa el valor numérico de la enumeración, no el nombre de la cadena.DataContractJsonSerializer y Enums

IE:

enum foo 
{ 
    bar, 
    baz 
} 

números de serie a un valor de rendimientos foo.bar "0", no "bar".

Preferiría que sea al revés, ¿hay alguna manera de anular esto?

Editar:

Porque no quiero cambiar el serializador, he usado un simple truco solución.

expuse una propiedad en la clase para serializar que llama ToString en el valor, es decir:

// 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 {} 
} 
+0

Este no es una gran sorpresa, ya que las enum son por defecto de tipo int. – Powerlord

+0

En la serialización de XML, también se recomienda evitar las enumeraciones en los contratos de datos wcf porque crean sutiles problemas compatibles con versiones anteriores. Ver http://stackoverflow.com/questions/326339/do-you-use-enum-types-in-your-wcf-web-services –

Respuesta

25

It looks like this is by design y este comportamiento no se puede cambiar: valores de los miembros

de enumeración son tratados como números en JSON, que es diferente de cómo se tratan en los contratos de datos , donde se incluyen como nombres de miembro .

Aquí hay un ejemplo usando an alternative (y la OMI mejor y más extensible) serializador que logra lo que busca:

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()); 
    } 
} 
+7

Creo que la nueva forma de hacerlo es 'jsonSerializer.Converters.Add (new StringEnumConverter()); '- a partir de 4.5 – shanabus

+0

@shanabus - ¿Dónde agregas esa línea mágica en un servicio wcf? (la pregunta fue etiquetada como wcf) – BornToCode

+0

@BornToCode No estoy muy familiarizado con 'wcf' y no es así como encontré esta pregunta. ¿Responde esto a su pregunta - http://stackoverflow.com/questions/6642961/can-i-use-json-serialization-in-wcf-service? – shanabus

3

Edit: Lo siento acabo de levantar nada de café :(

Aquí es el código para hacer lo que quiere hacer con el Serializador Json, no DataContractJsonSerializer.

No he trabajado con DataContractJsonSerializer aún pero después de quic kly escaneando los documentos, estoy bastante decepcionado con MS. Obviamente llegaron a extremos para hacer que WCF sea muy extensible, pero con el DataContractJsonSerializer no hay extensibilidad. Tienes que usar MS con sabor a JSON con él. SUPER cojo de MS ... ya arruinando WCF.

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

Algunos Prueba Objetos & 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. Uno para todas las enumeraciones, y otro para un objeto que usa una enumeración.

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) }; } } 
} 

Y su uso dentro de una página:

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); 
    } 
} 
10

Para obtener 2 vías serialización/deserilization para JSON WCF se puede añadir una segunda propiedad consiga el sistema de tipo cadena, cuando está la construcción de su JSON objeto en javascript usa la propiedad string nombrada, en el lado del servidor usa la versión enum fuertemente tipada: ej.

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 
} 
+0

¡Esto funcionó muy bien! – kpasgma

+0

Aunque es feo como el infierno. Qué vergüenza de Microsoft para forzar este tipo de implementación. – crush

9

que se volvieron locos tratando de encontrar una solución elegante a este problema, ya que parece que todo el mundo por defecto en serializador de Newtonsoft a solucionar este problema. Aunque Newtonsoft proporciona más características, tiene algunos inconvenientes graves. Para enumerar algunos: la necesidad de constructores sin parámetros, comportamiento alocado si desea serializar clases que implementan IEnumerable, y funciona muy mal cuando se usan tipos abstractos (ya que no utiliza el atributo KnownTypes, y la solución alternativa genera un resultado detallado que expone sus espacios de nombres internos a las personas que llaman).

Por otro lado, hay pocos ejemplos sobre cómo personalizar DataContractJsonSerializer cuando se utiliza en una solución MVC4 WebApi.

Me tomó un tiempo encontrar una solución que represente las enumeraciones como cadenas y aborde los problemas conocidos de formato DateTime que viene con el DataContractJsonSerializer.

PARTE I - Ponga estos métodos de extensión en una clase de extensiones ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~

#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); 
    } 

PARTE II - Crear su propio formateador encapsulando el 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; 
    } 
} 

PARTE III - Modificar el archivo Global.asax y consumir su nuevo formateador JSON ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~

/// <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; 
    } 
1

he reunido todas las piezas de esta solución utilizando la biblioteca de una manera Newtonsoft.Json eso funciona dentro de WCF. Soluciona el problema de la enumeración y también hace que el manejo de errores sea mucho mejor, y funciona en los servicios alojados de IIS. Es toda una gran cantidad de código, por lo que se puede encontrar en GitHub aquí: https://github.com/jongrant/wcfjsonserializer/blob/master/NewtonsoftJsonFormatter.cs

Hay que añadir algunas entradas a su Web.config a conseguir que funcione, se puede ver un ejemplo de archivo aquí: https://github.com/jongrant/wcfjsonserializer/blob/master/Web.config