I have an object, it has a DateTime property... I want to pass that object from an .ashx handler back to a webpage via AJAX/JSON... I don't want to use 3rd party controls...
when I do this:
new JavaScriptSerializer().Serialize(DateTime.Now);
I get this:
"\/Date(1251385232334)\/"
but I want "8/26/2009" (nevermind localization... my app is very localized, so my date formatting assumptions are not up for debate in this question). If I make/register a custom converter
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;
}
}
then I get this result (since the return value of the custom serialize method is a dictionary):
{"DateTime":"8/27/2009"}
so now in my Javascript, instead of doing
somePerson.Birthday
I have to do
somePerson.Birthday.DateTime
or
somePerson.Birthday["DateTime"]
how can I make the custom converter return a direct string so that I can have clean Javascript?
JavaScriptSerializer can definitely do what you desire.
It's possible to customize the serialization performed by JavaScriptSerializer for any type by creating a custom converter and registering it with the serializer. If you have a class called Person, we could create a converter like so:
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;
}
}
And use it like this:
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
Here's an enhancement for the accepted answer.
Using generics, passing a type and using reflection to determine the datetime properties.
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;
}
}
Usage is simple:
JavaScriptSerializer serialiser = ExtendedJavaScriptConverter<Task>.GetSerializer();
Hope that helps someone.
There is actually a nice clean way to do this without knowing the wrapper type or even needing a wrapper object.
You use JavaScriptConverter to convert your object to a Uri that also implements IDictionary. JavaScriptSerializer will serialize this as a string.
This hack is described here:
http://blog.calyptus.eu/seb/2011/12/custom-datetime-json-serialization/
Actually there is an ugly way, create a JavaScriptConverter for the container (Person/Article/Whatever) class
Container:
public class Article
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime Date { get; set; }
}
Converter:
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"));
}
}
Happily ever after...
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);
I realise this is a bit late for an answer but I recently came upon a really nice solution to this problem. It's documented in this blog post just in case anyone else finds it useful: http://icanmakethiswork.blogspot.co.uk/2012/04/beg-steal-or-borrow-decent-javascript.html
the answer is: you can't use JavaScriptConverter this way... it doesn't have the capabilities.
but for reference:
How do I format a Microsoft JSON date? http://blog.stevenlevithan.com/archives/date-time-format
If you care, what I ended up doing was adding a method to the javascript string prototype to make this easier for me in code:
String.prototype.dateFromJSON = function () {
return eval(this.replace(/\/Date\((\d+)\)\//gi, "new Date($1)"));
};
this is still painful to use in the meat of the code because you have to constantly call dateFromJSON() all over the place... which is dumb.
I know this looks really dumb, but so far I haven't found anything better...I'm still looking though, so comments are welcome.
new JavaScriptSerializer().Serialize(DateTime.Now).Replace("\"\\/", "").Replace("\\/\"", "");
This just removes the quotes and slashes, so the output is just Date(123456789)
which, though technically not a literal, is understood by the browser as an actual date value and not a string.
In JSON, it would look like this
{"myDate":Date(123456789)}
A hack, I suppose. If this is actually implemented in production code, I'd personally wrap it up, either in an extension method like FormatForDates() or wrap the serializer itself as in a decorator pattern...or in this case, an "undecorator." I must really be missing the boat as to why this seems so hard. I just want to render a date, people! :-p
A vb.net conversion of the answer by @sambomartin. All credit of this goes to him. I just pasted this here in case someone needs this for vb.net.
I also made it recursive and added the ability to override the default property names with XmlElement data annotations. (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
I had a similar problem where I wanted class SensorReading having Enum properties 'type' and 'unit' to serialize with the name of Enum values. (Default result is 0 if the Enum does not have explicit numeric value)
Before the serialized result looked like this:
[{"id":"0","type":0,"value":"44.00","unit":0}]
What I wanted was this:
[{"id":"0","type":"temperature","value":"44.00","unit":"C"}]
In the example function below I register a custom serializer 'EnumConverter<SensorReading>' before serializing an object.
public static string ToSJSon(object obj)
{
var jss = new JavaScriptSerializer();
jss.RegisterConverters(new[] { new EnumConverter<SensorReading>() });
return jss.Serialize(obj);
}
Here is the generic 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;
}
}
The custom serializer announces it serializes objects of type T and in Serialize it loops all readable properties. If property is an Enum it returns the name instead of the value to the dictionary.
This could be extended to other property types not serializing the way we want.
I added a separate test if the custom serializer(s) T happens to be Enum. Then instead output the name of the Enum class and it;s value. The result will the look like this:
[{"id":"0","type":{"ReadingType":"temperature"},"value":"44.00","unit":{"ReadingUnit":"C"}}]
That may be a better output if you plan to deserialize and want to know what Enum type the value belongs to.
link text This example works
JavaScriptSerializer serializer = new JavaScriptSerializer();
DateTime dt = DateTime.Now;
DateTime dt1 = dt;
string jsonDateNow = serializer.Serialize(dt1);
来源:https://stackoverflow.com/questions/1341719/custom-javascriptconverter-for-datetime