.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
Me doy cuenta de que es un poco tarde para una respuesta, pero recientemente encontré una solución muy buena para este problema. Está documentado en esta publicación de blog por si alguien más lo encuentra útil: http://icanmakethiswork.blogspot.co.uk/2012/04/beg-steal-or-borrow-decent-javascript.html
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.