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..
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.
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
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 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
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.
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