27
votes

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?

10
almost 5yrs later... and I would never use a date format of "8/27/2009" in JSON... I always use ISO8601: 2009-08-27T00:00:00ZNick Franceschina

10 Answers

22
votes

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
12
votes

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.

10
votes

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: Custom DateTime JSON Format for .NET JavaScriptSerializer

4
votes

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);
3
votes

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

2
votes

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.

0
votes

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

0
votes

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
0
votes

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.

-1
votes

link text This example works

JavaScriptSerializer serializer = new JavaScriptSerializer();

DateTime dt = DateTime.Now;
DateTime dt1 = dt;

string jsonDateNow = serializer.Serialize(dt1);