DateTime 的自定义 JavaScriptConverter?

Posted

技术标签:

【中文标题】DateTime 的自定义 JavaScriptConverter?【英文标题】:Custom JavaScriptConverter for DateTime? 【发布时间】:2010-11-23 10:12:55 【问题描述】:

我有一个对象,它有一个 DateTime 属性...我想通过 AJAX/JSON 将该对象从 .ashx 处理程序传递回网页...我不想使用第 3 方控件... .

当我这样做时:

  new javascriptSerializer().Serialize(DateTime.Now);

我明白了:

  "\/Date(1251385232334)\/"

但我想要“2009 年 8 月 26 日”(没关系本地化......我的应用程序非常本地化,所以我的日期格式假设在这个问题中不存在争议)。如果我制作/注册自定义转换器

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;
    

然后我得到这个结果(因为自定义序列化方法的返回值是一个字典):

"DateTime":"8/27/2009"

所以现在在我的 Javascript 中,而不是这样做

somePerson.Birthday

我必须这样做

somePerson.Birthday.DateTime 

  or

somePerson.Birthday["DateTime"]

我怎样才能让自定义转换器返回一个直接字符串,以便我可以拥有干净的 Javascript?

【问题讨论】:

差不多 5 年后......我永远不会在 JSON 中使用“2009 年 8 月 27 日”的日期格式......我总是使用 ISO8601:2009-08-27T00:00:00Z 【参考方案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】:

这是对已接受答案的增强。

使用泛型、传递类型并使用反射来确定日期时间属性。

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();

希望对某人有所帮助。

【讨论】:

不错。谢谢你。 这在我的类型有一个列表的情况下不起作用,即匿名表:List> 我一直收到错误:在“调用 PropertyInfo.GetValue 时出现参数计数不匹配异常” prop.SetValue(p, 字典[key], null);"【参考方案3】:

实际上有一个很好的干净方法可以做到这一点,而无需知道包装器类型,甚至不需要包装器对象。

您使用 JavaScriptConverter 将您的对象转换为同样实现 IDictionary 的 Uri。 JavaScriptSerializer 会将其序列化为字符串。

这里描述了这个 hack:Custom DateTime JSON Format for .NET JavaScriptSerializer

【讨论】:

这就是我需要将 DateTime 对象转换为匿名对象的属性,而无需将 DateTime 对象转换为具有日期时间的对象。 TL;博士;做它应该做的。【参考方案4】:

其实有一个丑陋的方法,为容器(Person/Article/Whatever)类创建一个JavaScriptConverter

容器:

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...它没有这些功能。

但仅供参考:

How do I format a Microsoft JSON date? 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】:

我知道这看起来很愚蠢,但到目前为止我还没有找到更好的东西......不过我还在寻找,所以欢迎 cmets。

new JavaScriptSerializer().Serialize(DateTime.Now).Replace("\"\\/", "").Replace("\\/\"", "");

这只是删除了引号和斜杠,因此输出只是Date(123456789),虽然技术上不是文字,但浏览器将其理解为实际的日期值而不是字符串。

在 JSON 中,它看起来像这样

"myDate":Date(123456789)

我想是个黑客。如果这实际上是在生产代码中实现的,我会亲自将其包装起来,或者使用 FormatForDates() 之类的扩展方法,或者将序列化程序本身包装为装饰器模式……或者在这种情况下,使用“取消装饰器”。我一定真的想念这艘船,为什么这看起来这么难。我只想渲染一个日期,人! :-p

【讨论】:

我使用正则表达式得到了类似的结果:new Regex(@"""DateTime"":(""[0-9/-]+"")").Replace(s, "$1")。结果看起来像"myDate":"5/3/2011"【参考方案8】:

@sambomartin 对答案的 vb.net 转换。这一切都归功于他。我只是把它贴在这里,以防有人需要这个用于 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 没有明确的数值,则默认结果为 0)

之前的序列化结果是这样的:

["id":"0","type":0,"value":"44.00","unit":0]

我想要的是这样的:

["id":"0","type":"temperature","value":"44.00","unit":"C"]

在下面的示例函数中,我在序列化对象之前注册了一个自定义序列化程序“EnumConverterSensorReading>”。

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 类型的对象,并在 Serialize 中循环所有可读属性。如果属性是一个枚举,它会返回名称而不是值给字典。

这可以扩展到其他不按我们想要的方式序列化的属性类型。

如果自定义序列化程序 T 恰好是 Enum,我添加了一个单独的测试。然后改为输出 Enum 类的名称及其值。结果将如下所示:

["id":"0","type":"ReadingType":"temperature","value":"44.00","unit":"ReadingUnit":"C"]

如果您打算反序列化并想知道值属于哪种 Enum 类型,这可能是一个更好的输出。

【讨论】:

【参考方案10】:

link text 这个例子有效

JavaScriptSerializer serializer = new JavaScriptSerializer();

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

string jsonDateNow = serializer.Serialize(dt1);

【讨论】:

该示例绝对行不通...它与我在帖子顶部列出的完全相同...所以它只输出“\/Date(1251467694063)\/”不是我想要的。但感谢 4 到另一个类似线程的链接 这个例子非常适合我,奇怪的是如果我没有创建变量“dt1”序列化错误。

以上是关于DateTime 的自定义 JavaScriptConverter?的主要内容,如果未能解决你的问题,请参考以下文章

js时间类型的自定义转换库 datetime-fmt

SQLite SELECT查询中的自定义字段填充后不会成为DataTable中的DateTime列?

Python json.dumps 特殊数据类型的自定义序列化操作

使用 Play Framework JSON 库的自定义 Joda 时间序列化器?

九种主流编程语言:PythonJavaCC++JavaScriptC#RubyPHP以及Objective-C

如何为DateTime创建和使用自定义IFormatProvider?