from .net javascript ajax converter

.net - from - Personalizado JavaScriptConverter para DateTime?



short date javascript (10)

Tengo un objeto, tiene una propiedad DateTime ... Quiero pasar ese objeto de un controlador .ashx a una página web a través de AJAX / JSON ... No quiero usar controles de terceros ...

cuando hago esto

new JavaScriptSerializer().Serialize(DateTime.Now);

Entiendo esto:

"//Date(1251385232334)//"

pero quiero "26/08/2009" (no importa la localización ... mi aplicación está muy localizada, por lo que mis supuestos de formato de fecha no están sujetos a debate en esta pregunta). Si hago / registro un convertidor personalizado

public class DateTimeConverter : JavaScriptConverter { public override IEnumerable<Type> SupportedTypes { get { return new List<Type>() { typeof(DateTime), typeof(DateTime?) }; } } public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) { Dictionary<string, object> result = new Dictionary<string, object>(); if (obj == null) return result; result["DateTime"] = ((DateTime)obj).ToShortDateString(); return result; } public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) { if (dictionary.ContainsKey("DateTime")) return new DateTime(long.Parse(dictionary["DateTime"].ToString()), DateTimeKind.Unspecified); return null; } }

luego obtengo este resultado (ya que el valor de retorno del método de serialización personalizado es un diccionario):

{"DateTime":"8/27/2009"}

Así que ahora en mi Javascript, en lugar de hacerlo.

somePerson.Birthday

tengo que hacer

somePerson.Birthday.DateTime or somePerson.Birthday["DateTime"]

¿Cómo puedo hacer que el convertidor personalizado devuelva una cadena directa para que pueda tener Javascript limpio?


Aquí hay una mejora para la respuesta aceptada.

Usando genéricos, pasando un tipo y usando reflexión para determinar las propiedades de fecha y hora.

public class ExtendedJavaScriptConverter<T> : JavaScriptConverter where T : new() { private const string _dateFormat = "dd/MM/yyyy"; public override IEnumerable<Type> SupportedTypes { get { return new[] { typeof(T) }; } } public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) { T p = new T(); var props = typeof(T).GetProperties(); foreach (string key in dictionary.Keys) { var prop = props.Where(t => t.Name == key).FirstOrDefault(); if (prop != null) { if (prop.PropertyType == typeof(DateTime)) { prop.SetValue(p, DateTime.ParseExact(dictionary[key] as string, _dateFormat, DateTimeFormatInfo.InvariantInfo), null); } else { prop.SetValue(p, dictionary[key], null); } } } return p; } public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) { T p = (T)obj; IDictionary<string, object> serialized = new Dictionary<string, object>(); foreach (PropertyInfo pi in typeof(T).GetProperties()) { if (pi.PropertyType == typeof(DateTime)) { serialized[pi.Name] = ((DateTime)pi.GetValue(p, null)).ToString(_dateFormat); } else { serialized[pi.Name] = pi.GetValue(p, null); } } return serialized; } public static JavaScriptSerializer GetSerializer() { JavaScriptSerializer serializer = new JavaScriptSerializer(); serializer.RegisterConverters(new[] { new ExtendedJavaScriptConverter<T>() }); return serializer; } }

El uso es simple:

JavaScriptSerializer serialiser = ExtendedJavaScriptConverter<Task>.GetSerializer();

Espero que ayude a alguien.


En realidad, hay una buena forma de hacerlo sin saber el tipo de envoltura o incluso sin necesitar un objeto de envoltura.

Utiliza JavaScriptConverter para convertir su objeto a un Uri que también implementa IDictionary. JavaScriptSerializer serializará esto como una cadena.

Este truco se describe aquí:

http://blog.calyptus.eu/seb/2011/12/custom-datetime-json-serialization/


En realidad, hay una manera fea, crear un JavaScriptConverter para la clase de contenedor (Persona / Artículo / Lo que sea)

Envase:

public class Article { public int Id { get; set; } public string Title { get; set; } public DateTime Date { get; set; } }

Convertidor:

public class ArticleJavaScriptConverter : JavaScriptConverter { public override IEnumerable<Type> SupportedTypes { get { return new Type[] { typeof(Article) }; } } public override object Deserialize( IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) { DateTime date = DateTime.ParseExact(dictionary["date"] as string, "s", null); return new Article() { Id = (int)dictionary["id"], Title = dictionary["title"] as string, Date = date }; } public override IDictionary<string, object> Serialize( object obj, JavaScriptSerializer serializer) { var article = obj as Article; var result = new Dictionary<string,object>(); if (article != null) { this.SerializeInternal(article, result); } return result; } private void SerializeInternal( Article article, IDictionary<string, object> result) { result.Add("id", article.Id); result.Add("title", article.Title); result.Add("date", article.Date.ToString("s")); } }

Feliz para siempre...

var serializer = new JavaScriptSerializer(); serializer.RegisterConverters( new JavaScriptConverter[] { new ArticleJavaScriptConverter() }); var expected = new Article() { Id = 3, Title = "test", Date = DateTime.Now }; // {"id":3,"title":"test","date":"2009-12-02T05:12:00"} var json = serializer.Serialize(article); var actual = serializer.Deserialize<Article>(json); Assert.AreEqual(expected, actual);


JavaScriptSerializer definitivamente puede hacer lo que desees.

Es posible personalizar la serialización realizada por JavaScriptSerializer para cualquier tipo creando un convertidor personalizado y registrándolo con el serializador. Si tiene una clase llamada Persona, podríamos crear un convertidor así:

public class Person { public string Name { get; set; } public DateTime Birthday { get; set; } } public class PersonConverter : JavaScriptConverter { private const string _dateFormat = "MM/dd/yyyy"; public override IEnumerable<Type> SupportedTypes { get { return new[] { typeof(Person) }; } } public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) { Person p = new Person(); foreach (string key in dictionary.Keys) { switch (key) { case "Name": p.Name = (string)dictionary[key]; break; case "Birthday": p.Birthday = DateTime.ParseExact(dictionary[key] as string, _dateFormat, DateTimeFormatInfo.InvariantInfo); break; } } return p; } public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) { Person p = (Person)obj; IDictionary<string, object> serialized = new Dictionary<string, object>(); serialized["Name"] = p.Name; serialized["Birthday"] = p.Birthday.ToString(_dateFormat); return serialized; } }

Y úsalo así:

JavaScriptSerializer serializer = new JavaScriptSerializer(); serializer.RegisterConverters(new[] { new PersonConverter() }); Person p = new Person { Name = "User Name", Birthday = DateTime.Now }; string json = serializer.Serialize(p); Console.WriteLine(json); // {"Name":"User Name","Birthday":"12/20/2010"} Person fromJson = serializer.Deserialize<Person>(json); Console.WriteLine(String.Format("{0}, {1}", fromJson.Name, fromJson.Birthday)); // User Name, 12/20/2010 12:00:00 AM



Sé que esto se ve muy tonto, pero hasta ahora no he encontrado nada mejor ... Todavía estoy mirando, así que los comentarios son bienvenidos.

new JavaScriptSerializer().Serialize(DateTime.Now).Replace("/"///", "").Replace("////"", "");

Esto solo elimina las comillas y las barras, por lo que la salida es solo la Date(123456789) que, aunque técnicamente no es un literal, es entendida por el navegador como un valor de fecha real y no como una cadena.

En JSON, se vería así

{"myDate":Date(123456789)}

Un truco, supongo. Si esto se implementa realmente en el código de producción, yo personalmente lo ajustaría, ya sea en un método de extensión como FormatForDates () o en el propio serializador como en un patrón de decorador ... o en este caso, un "undecorator". Realmente debo estar perdiendo el bote en cuanto a por qué esto parece tan difícil. Solo quiero hacer una cita, gente! :-pag


Tuve un problema similar en el que quería que la clase SensorReading que tenía las propiedades Enum ''tipo'' y ''unidad'' se serializara con el nombre de los valores Enum. (El resultado predeterminado es 0 si el Enum no tiene un valor numérico explícito)

Antes de que el resultado serializado se viera así:

[{"id":"0","type":0,"value":"44.00","unit":0}]

Lo que quería era esto:

[{"id":"0","type":"temperature","value":"44.00","unit":"C"}]

En la siguiente función de ejemplo, registro un serializador personalizado ''EnumConverter < SensorReading >'' antes de serializar un objeto.

public static string ToSJSon(object obj) { var jss = new JavaScriptSerializer(); jss.RegisterConverters(new[] { new EnumConverter<SensorReading>() }); return jss.Serialize(obj); }

Aquí está el genérico EnumConverter

public class EnumConverter<T> : JavaScriptConverter { public override IEnumerable<Type> SupportedTypes { get { return new[] { typeof(T) }; } } public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) { throw new NotImplementedException(String.Format("''{0}'' does not yet implement ''Deserialize", this.GetType().Name)); } public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) { IDictionary<string, object> serialized = new Dictionary<string, object>(); if (obj.GetType() == typeof(T)) { if (obj.GetType().IsEnum) { serialized[obj.GetType().Name] = Enum.GetName(obj.GetType(), obj); ; } else { var sourceType = obj.GetType(); var properties = sourceType.GetProperties(); foreach (PropertyInfo property in properties) { if (property.CanRead) { if (property.PropertyType.IsEnum) { var str = Enum.GetName(property.PropertyType, property.GetValue(obj, null)); serialized[property.Name] = str; } else { serialized[property.Name] = property.GetValue(obj, null); } } } } } return serialized; } }

El serializador personalizado anuncia que serializa objetos de tipo T y, en Serializar, reproduce todas las propiedades legibles. Si la propiedad es un Enum, devuelve el nombre en lugar del valor al diccionario.

Esto podría extenderse a otros tipos de propiedades que no estén serializando como queremos.

Agregué una prueba por separado si los serializadores personalizados T resultan ser Enum. Luego, en su lugar, escriba el nombre de la clase Enum y su valor. El resultado será el siguiente:

[{"id":"0","type":{"ReadingType":"temperature"},"value":"44.00","unit":{"ReadingUnit":"C"}}]

Eso puede ser una mejor salida si planea deserializar y quiere saber a qué tipo de Enum pertenece el valor.


Una conversión vb.net de la respuesta por @sambomartin. Todo el crédito de esto va a él. Acabo de pegar esto aquí en caso de que alguien lo necesite para vb.net.

También lo hice recursivo y agregué la capacidad de anular los nombres de propiedad predeterminados con las anotaciones de datos XmlElement. ( XmlElementAttribute )

Imports System.Web.Script.Serialization Imports System.Linq Imports System.Globalization Imports System.Xml.Serialization Public Class ExtendedJavaScriptSerializer(Of T As New) Inherits JavaScriptConverter Private Const _dateFormat As String = "dd/MM/yyyy" Public Overrides Function Deserialize(dictionary As IDictionary(Of String, Object), type As Type, serializer As JavaScriptSerializer) As Object Dim p As New T() Dim props = GetType(T).GetProperties() For Each key As String In dictionary.Keys Dim prop = props.Where(Function(x) x.Name = key).FirstOrDefault() If prop IsNot Nothing Then If prop.PropertyType = GetType(DateTime) Then prop.SetValue(p, DateTime.ParseExact(CStr(dictionary(key)), _dateFormat, DateTimeFormatInfo.InvariantInfo), Nothing) Else prop.SetValue(p, dictionary(key), Nothing) End If End If Next Return p End Function Public Overrides Function Serialize(obj As Object, serializer As JavaScriptSerializer) As IDictionary(Of String, Object) Dim serialized As IDictionary(Of String, Object) = New Dictionary(Of String, Object) CheckProperties(obj, serialized) Return serialized End Function Public Overrides ReadOnly Property SupportedTypes As IEnumerable(Of Type) Get Return {GetType(T)} End Get End Property Private Sub CheckProperties(obj As Object, ByRef serialized As IDictionary(Of String, Object)) If obj Is Nothing Then Return Dim objType As Type = obj.GetType() For Each pi In objType.GetProperties() '' define serialization attribute name from '' '' xmlelement dataannotation'' Dim displayname As String = pi.Name Dim attrs() As Object = pi.GetCustomAttributes(True) For Each attr In attrs If GetType(XmlElementAttribute) = attr.GetType() Then displayname = CType(attr, XmlElementAttribute).ElementName End If Next '' fix date format'' If pi.PropertyType = GetType(DateTime) Then serialized(displayname) = CType(pi.GetValue(obj, Nothing), DateTime).ToString(_dateFormat) Else '' recursive'' If pi.PropertyType.Assembly = objType.Assembly Then CheckProperties(pi.GetValue(obj, Nothing), serialized) Else If pi.GetValue(obj, Nothing) IsNot Nothing Then serialized(displayname) = pi.GetValue(obj, Nothing) End If End If End If Next End Sub Public Shared Function GetSerializer() As JavaScriptSerializer Dim serializer As New JavaScriptSerializer serializer.RegisterConverters({New ExtendedJavaScriptSerializer(Of T)}) Return serializer End Function End Class


texto de enlace Este ejemplo funciona

JavaScriptSerializer serializer = new JavaScriptSerializer(); DateTime dt = DateTime.Now; DateTime dt1 = dt; string jsonDateNow = serializer.Serialize(dt1);


la respuesta es: no se puede usar JavaScriptConverter de esta manera ... no tiene las capacidades.

pero para referencia:

¿Cómo formateo una fecha Microsoft JSON? http://blog.stevenlevithan.com/archives/date-time-format

Si te importa, lo que terminé haciendo fue agregar un método al prototipo de cadena de javascript para que esto sea más fácil para mí en el código:

String.prototype.dateFromJSON = function () { return eval(this.replace(///Date/((/d+)/)///gi, "new Date($1)")); };

Esto aún es doloroso de usar en la carne del código porque tienes que llamar constantemente a dateFromJSON () en todo el lugar ... lo cual es tonto.