Пользовательский JavaScriptConverter для DateTime?
У меня есть объект, у него есть свойство DateTime... Я хочу передать этот объект из обработчика .ashx обратно на веб-страницу через AJAX/JSON... Я не хочу использовать сторонние элементы управления..
когда я это делаю:
new JavaScriptSerializer().Serialize(DateTime.Now);
Я получаю это:
"\/Date(1251385232334)\/"
но я хочу "8/26/2009" (некорректная локализация... мое приложение очень локализовано, поэтому мои предположения о форматировании даты не обсуждаются в этом вопросе). Если я создаю/зарегистрирую пользовательский конвертер
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;
}
}
то я получаю этот результат (поскольку возвращаемое значение пользовательского метода serialize является словарем):
{"DateTime":"8/27/2009"}
поэтому теперь в моем Javascript вместо того, чтобы делать
somePerson.Birthday
Мне нужно сделать
somePerson.Birthday.DateTime
or
somePerson.Birthday["DateTime"]
как я могу заставить пользовательский конвертер возвращать прямую строку, чтобы я мог иметь чистый Javascript?
Ответы
Ответ 1
JavaScriptSerializer может определенно делать то, что вы хотите.
Можно настроить сериализацию, выполняемую JavaScriptSerializer для любого типа, создав собственный конвертер и зарегистрировав его с помощью сериализатора. Если у вас есть класс под названием Person, мы могли бы создать такой конвертер:
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;
}
}
И используйте его следующим образом:
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
Ответ 2
Здесь улучшается принятый ответ.
Использование generics, передача типа и использование отражения для определения свойств datetime.
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;
}
}
Использование прост:
JavaScriptSerializer serialiser = ExtendedJavaScriptConverter<Task>.GetSerializer();
Надеюсь, что это поможет кому-то.
Ответ 3
На самом деле есть хороший чистый способ сделать это, не зная типа оболочки или даже не нуждаясь в объекте оболочки.
Вы используете JavaScriptConverter для преобразования вашего объекта в Uri, который также реализует IDictionary. JavaScriptSerializer будет сериализовать это как строку.
Этот хак описан здесь: Пользовательский формат DateTime JSON для .NET JavaScriptSerializer
Ответ 4
На самом деле есть уродливый способ, создайте JavaScriptConverter для класса (Person/Article/Whatever)
Контейнер:
public class Article
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime Date { get; set; }
}
Преобразователь
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"));
}
}
С радостью после...
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);
Ответ 5
Я понимаю, что это немного поздно для ответа, но я недавно пришел к действительно хорошему решению этой проблемы. Он задокументирован в этом блоге на всякий случай, если кто-либо найдет его полезным: http://icanmakethiswork.blogspot.co.uk/2012/04/beg-steal-or-borrow-decent-javascript.html
Ответ 6
ответ: вы не можете использовать JavaScriptConverter таким образом... у него нет возможностей.
но для справки:
Как мне настроить дату Microsoft JSON?
http://blog.stevenlevithan.com/archives/date-time-format
Если вам все равно, то, что я закончил, это добавить метод к прототипу строки javascript, чтобы сделать это проще для меня в коде:
String.prototype.dateFromJSON = function () {
return eval(this.replace(/\/Date\((\d+)\)\//gi, "new Date($1)"));
};
Это по-прежнему болезненно использовать в мясе кода, потому что вам нужно постоянно называть dateFromJSON() повсеместно..., который немой.
Ответ 7
Я знаю, что это выглядит действительно немым, но пока я не нашел ничего лучшего... Я все еще смотрю, поэтому комментарии приветствуются.
new JavaScriptSerializer().Serialize(DateTime.Now).Replace("\"\\/", "").Replace("\\/\"", "");
Это просто удаляет кавычки и косые черты, поэтому вывод - это просто Date(123456789)
, который, хотя и технически не является буквальным, понимается браузером как фактическое значение даты, а не строка.
В JSON это будет выглядеть как
{"myDate":Date(123456789)}
Взлом, я полагаю. Если это действительно реализовано в производственном коде, я бы лично его завернул, либо в методе расширения, например FormatForDates(), либо обернул сам сериализатор, как в шаблоне декоратора... или в этом случае "undecorator". Я действительно должен скучать по лодке, почему это так сложно. Я просто хочу показать дату, люди!:-p
Ответ 8
Преобразование vb.net ответа @sambomartin. Все это относится к нему. Я просто вставлял это здесь, если кому-то это нужно для vb.net.
Я также сделал его рекурсивным и добавил возможность переопределять имена свойств по умолчанию с аннотациями данных 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
Ответ 9
У меня была аналогичная проблема, когда я хотел, чтобы класс SensorReading имел свойства "Свойства" Enum и "unit" для сериализации с именем значений Enum. (По умолчанию результат равен 0, если Enum не имеет явного числового значения)
До того, как сериализованный результат будет выглядеть следующим образом:
[{"id":"0","type":0,"value":"44.00","unit":0}]
Что я хотел, так это:
[{"id":"0","type":"temperature","value":"44.00","unit":"C"}]
В приведенной ниже функции примера я регистрирую пользовательский сериализатор EnumConverter <SensorReading> перед сериализацией объекта.
public static string ToSJSon(object obj)
{
var jss = new JavaScriptSerializer();
jss.RegisterConverters(new[] { new EnumConverter<SensorReading>() });
return jss.Serialize(obj);
}
Вот общий 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;
}
}
Пользовательский сериализатор объявляет, что он сериализует объекты типа T, а в сериализованном цикле - все доступные для чтения свойства. Если свойство является Enum, оно возвращает имя вместо значения в словаре.
Это может быть распространено на другие типы свойств, которые не сериализуются так, как мы хотим.
Я добавил отдельный тест, если пользовательский сериализатор T оказался Enum. Затем вместо этого выведите имя класса Enum и его значение s. Результат будет выглядеть следующим образом:
[{"id":"0","type":{"ReadingType":"temperature"},"value":"44.00","unit":{"ReadingUnit":"C"}}]
Это может быть лучшим выходом, если вы планируете десериализовать и хотите знать, к какому типу Enum относится значение.
Ответ 10
текст ссылки
Этот пример работает
JavaScriptSerializer serializer = new JavaScriptSerializer();
DateTime dt = DateTime.Now;
DateTime dt1 = dt;
string jsonDateNow = serializer.Serialize(dt1);