Custom JavaScriptConverter for DateTime?

前端 未结 10 638
走了就别回头了
走了就别回头了 2020-12-02 19:18

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

相关标签:
10条回答
  • 2020-12-02 19:23

    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.

    0 讨论(0)
  • 2020-12-02 19:27

    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

    0 讨论(0)
  • 2020-12-02 19:27

    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 讨论(0)
  • 2020-12-02 19:29

    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 讨论(0)
  • 2020-12-02 19:34

    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 讨论(0)
  • 2020-12-02 19:36

    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
    
    0 讨论(0)
提交回复
热议问题