serialize newtonsoft newsoft net deserialize and wcf json serialization

wcf - newtonsoft - serialize class c# json



DataContractJsonSerializer y Enums (5)

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

ES DECIR:

enum foo { bar, baz }

La serialización de un valor de foo.bar devuelve "0", no "bar".

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

Editar:

Como no quería cambiar el serializador, usé un truco de solución simple.

Expongo una propiedad en la clase para serializar las llamadas a 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 {} }


He reunido todas las piezas de esta solución usando la biblioteca Newtonsoft.Json de una manera que 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 una gran cantidad de código, por lo que puede encontrarlo en GitHub aquí: https://github.com/jongrant/wcfjsonserializer/blob/master/NewtonsoftJsonFormatter.cs

Debe agregar algunas entradas a su Web.config para que funcione, puede ver un archivo de ejemplo aquí: https://github.com/jongrant/wcfjsonserializer/blob/master/Web.config


Me volví loco al tratar de encontrar una solución elegante a este problema, ya que parece que todo el mundo incumplió el serializador de Newtonsoft para solucionar este problema. Aunque Newtonsoft proporciona más funciones, 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 genera un salida detallada 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 usa 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: cree su propio formateador encapsulando 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 - Edite su archivo Global.asax y consuma 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; }


Para obtener la serialización / deserilización bidireccional para wcf json, puede agregar una segunda propiedad get set de tipo string, cuando construya su objeto json en javascript utilice la cadena llamada property, en el lado del servidor utilice la versión enum fuertemente tipada: por 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 }


editar: Lo siento, no tengo café :(

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

Todavía no he trabajado con DataContractJsonSerializer, pero después de escanear rápidamente los documentos, estoy algo 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 objetos de prueba y 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 usándolo 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); } }


Parece que esto es por diseño y este comportamiento no se puede cambiar:

Los valores de miembro de enumeración se tratan 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 que usa un serializador alternativo (y mejor y más extensible) que logra lo que está buscando:

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