ASP.NET MVC JsonResult 日期格式化

268

我有一个控制器操作,实际上只返回我的模型的JsonResult。 所以,在我的方法中,我有以下内容:

return new JsonResult(myModel);

这个方法很好用,但是存在一个问题。模型中有一个日期属性,在Json结果中会以如下方式返回:

"\/Date(1239018869048)\/"

我该如何处理日期,以便以所需的格式返回?或者说,在脚本中如何处理上述格式?


我已经发布了我的json net结果到同样的问题,它将日期转换为iso格式,使得处理更加容易。https://dev59.com/1HDYa4cB1Zd3GeqPAXJ0#15799992 - Kieran
请查看下面的链接。直截了当。https://dev59.com/UmQn5IYBdhLWcg3w36I1#60392503 - Mohamedasiq
25个回答

208

casperOne的回答上进行扩展。

JSON规范没有考虑到日期格式值。微软不得不做出决策,他们选择的方法是利用javascript字符串表示中的一个小技巧:字符串文字“ /”与“ \ /”相同,而且字符串文字永远不会被序列化为“ /”(甚至“ \/”也必须映射为“ \ / \”)。

请参见http://msdn.microsoft.com/en-us/library/bb299886.aspx#intro_to_json_topic2以了解更好的解释(向下滚动到“从JavaScript文字到JSON”)。

JSON中的一个痛点是缺乏日期/时间字面量。许多人在首次遇到JSON时对此感到惊讶和失望。对于缺少日期/时间字面量的简单解释(无论是否令人宽慰),是JavaScript本身也从未提供过这种值:JavaScript对于日期和时间值的支持完全通过Date对象实现。因此,大多数使用JSON作为数据格式的应用程序通常倾向于使用字符串或数字来表示日期和时间值。如果使用字符串,通常可以期望它以ISO 8601格式表示。相反,如果使用数字,则该值通常被认为是自1970年1月1日午夜(UTC)以来的通用协调时间(UTC)毫秒数, 其中始点被定义为UTC的。再次强调,这只是一个约定,而不是JSON标准的一部分。如果您正在与另一个应用程序交换数据,则需要查看其文档以了解它如何在JSON字面量中编码日期和时间值。例如,微软的ASP.NET AJAX不使用上述任何约定。相反,它将.NET DateTime值编码为JSON字符串,其中字符串的内容为“ /Date(ticks)/”,其中ticks表示自时代(UTC)以来的毫秒数。因此,1989年11月29日上午4:55:30 UTC会被编码为“\\/Date(628318530718)\\/”。

一个解决方案就是直接解析它:

value = new Date(parseInt(value.replace("/Date(", "").replace(")/",""), 10));

不过,我听说有一种设置可以让序列化程序以 new Date(xxx) 语法输出 DateTime 对象。我会试着找到它。


JSON.parse() 的第二个参数可以接受一个 reviver 函数,用于指定在返回值之前如何处理原始值。

以下是日期的示例:

var parsed = JSON.parse(data, function(key, value) {
  if (typeof value === 'string') {
    var d = /\/Date\((\d*)\)\//.exec(value);
    return (d) ? new Date(+d[1]) : value;
  }
  return value;
});

请查看JSON.parse()的文档


1
谢谢,但解析应该放在哪里呢? - Jon Archway
6
你可以将js代码简化为: new Date(parseInt(dateString.replace(//Date\((\d+)\)//gi, "$1"))) - kͩeͣmͮpͥ ͩ
6
实际上,正则表达式的正确性更高,应该写成replace(//Date\((-?\d+)\)//gi, "$1")因为日期可能以负数形式表示。 - Dokie
1
@HarshilShah 这是 parseInt() 的第二个参数。它告诉函数在十进制数系统中提取整数。这是一个基数。如果你在那里放入 8,它将提取一个八进制数。 - AnalogWeapon
正则表达式忽略1970年之前的日期。为了允许在ECMAScript日期范围内的所有日期,它应该包括一个可能的负号:/\/Date\((-?\d*)\)\// - RobG
显示剩余5条评论

108

以下是我的 Javascript 解决方案,非常像 JPot 的,但更简短(可能稍微快一点):

value = new Date(parseInt(value.substr(6)));

"value.substr(6)"将去掉"/Date("部分,然后parseInt函数会忽略出现在末尾的非数字字符。

编辑:我有意省略了解析器(parseInt函数)中的基数参数(第二个参数)。请参阅下面我的评论。另外,请注意,ISO-8601日期格式优于旧格式——因此,新开发通常不应使用这种格式。

对于ISO-8601格式的JSON日期,只需将字符串传递到Date构造函数中:

var date = new Date(jsonDate); //no ugly parsing needed; full timezone support

1
+1 我采用了你的简单解决方案,并将其放入递归函数中。请参见此处:http://danielsadventure.info/dotnetdatetime/ - Vivian River
7
在使用parseInt时,你应该始终指定一个基数。[来源]: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/parseInt - John Zabroski
7
每个规则都有例外。.NET日期序列化器从不返回带有前导零的整数,因此我们可以安全地省略基数。 - Roy Tinker
4
我们几乎做了同样的事情。我们使用value.substr(6, 13)来删除其他非数字字符。但是,如果您这样做,所有早于1938年4月26日的日期都将无效!我们不知道 parseInt会忽略非数字字符。谢谢! - LockTar
2
@JohnZabroski——根据ECMAScript ed 5(2011)的规定,parseInt应该忽略前导零。 - RobG
显示剩余3条评论

71

有很多答案可以在客户端处理它,但如果你愿意,也可以在服务器端更改输出。

有几种方法可以解决这个问题,我会从基础开始介绍。您将需要子类化JsonResult类并重写ExecuteResult方法。从那里,您可以采用一些不同的方法来更改序列化。

方法1: 默认实现使用JsonScriptSerializer。如果您查看文档,可以使用RegisterConverters方法添加自定义JavaScriptConverters。 但是有一些问题:JavaScriptConverter序列化为字典,即它将对象序列化为Json字典。为了使对象序列化为字符串,它需要一些技巧,请参见post。 这个特定的技巧还会转义字符串。

public class CustomJsonResult : JsonResult
{
    private const string _dateFormat = "yyyy-MM-dd HH:mm:ss";

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        HttpResponseBase response = context.HttpContext.Response;

        if (!String.IsNullOrEmpty(ContentType))
        {
            response.ContentType = ContentType;
        }
        else
        {
            response.ContentType = "application/json";
        }
        if (ContentEncoding != null)
        {
            response.ContentEncoding = ContentEncoding;
        }
        if (Data != null)
        {
            JavaScriptSerializer serializer = new JavaScriptSerializer();

            // Use your custom JavaScriptConverter subclass here.
            serializer.RegisterConverters(new JavascriptConverter[] { new CustomConverter });

            response.Write(serializer.Serialize(Data));
        }
    }
}

方法二(推荐): 第二种方法是从重写的JsonResult开始,选择另一个Json序列化器,例如我的选择是Json.NET。这不需要第一种方法中所需的技巧。这是我实现的JsonResult子类:

public class CustomJsonResult : JsonResult
{
    private const string _dateFormat = "yyyy-MM-dd HH:mm:ss";

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        HttpResponseBase response = context.HttpContext.Response;

        if (!String.IsNullOrEmpty(ContentType))
        {
            response.ContentType = ContentType;
        }
        else
        {
            response.ContentType = "application/json";
        }
        if (ContentEncoding != null)
        {
            response.ContentEncoding = ContentEncoding;
        }
        if (Data != null)
        {
            // Using Json.NET serializer
            var isoConvert = new IsoDateTimeConverter();
            isoConvert.DateTimeFormat = _dateFormat;
            response.Write(JsonConvert.SerializeObject(Data, isoConvert));
        }
    }
}

使用示例:

[HttpGet]
public ActionResult Index() {
    return new CustomJsonResult { Data = new { users=db.Users.ToList(); } };
}

额外鸣谢: James Newton-King


其他格式,如货币、身份证号码、电话等等呢?从ModelMetadata获取这些格式并将它们用于将Models序列化为Json不是更好的方法吗?怎么做? - Luciano
1
这是最佳解决方案(Perishable Dave的答案)。服务器负责提供正确的日期格式。同时,拥有自定义的JsonResult可以带来更多的好处和控制。我建议实现一个辅助方法“CustomJson(data)”,它将实例化CustomJsonResult,就像已经存在的“Json(data)”一样,它会实例化JsonResult并使用其数据。 - sports
2
如果您使用这两种方法中的任何一种,需要进行一次更正 - 第一行应该是: private const string _dateFormat = "yyyy-MM-ddTHH:mm:ss"; 我添加了“T”。 - Dominick
James Newton-King在这里提供了一个JsonNetResult类的示例,链接为http://james.newtonking.com/archive/2008/10/16/asp-net-mvc-and-json-net,该示例类似于第二种方法,并提供了完整的JsonSerializerSettings属性访问。 - undefined

32

首先下载moment.js文件。将其添加到您的项目中,然后使用moment("json_date_string_value").format('appropriate format');即可。 您可以在moment.js页面上看到不同的格式值。 - Harshil Shah

25

我发现创建新的JsonResult并返回它是不令人满意的 - 不得不用return new MyJsonResult { Data = obj }替换所有调用return Json(obj)的内容很麻烦。


于是我想,为什么不使用ActionFilter劫持JsonResult

public class JsonNetFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Result is JsonResult == false)
        {
            return;
        }

        filterContext.Result = new JsonNetResult(
            (JsonResult)filterContext.Result);
    }

    private class JsonNetResult : JsonResult
    {
        public JsonNetResult(JsonResult jsonResult)
        {
            this.ContentEncoding = jsonResult.ContentEncoding;
            this.ContentType = jsonResult.ContentType;
            this.Data = jsonResult.Data;
            this.JsonRequestBehavior = jsonResult.JsonRequestBehavior;
            this.MaxJsonLength = jsonResult.MaxJsonLength;
            this.RecursionLimit = jsonResult.RecursionLimit;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            var isMethodGet = string.Equals(
                context.HttpContext.Request.HttpMethod, 
                "GET", 
                StringComparison.OrdinalIgnoreCase);

            if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet
                && isMethodGet)
            {
                throw new InvalidOperationException(
                    "GET not allowed! Change JsonRequestBehavior to AllowGet.");
            }

            var response = context.HttpContext.Response;

            response.ContentType = string.IsNullOrEmpty(this.ContentType) 
                ? "application/json" 
                : this.ContentType;

            if (this.ContentEncoding != null)
            {
                response.ContentEncoding = this.ContentEncoding;
            }

            if (this.Data != null)
            {
                response.Write(JsonConvert.SerializeObject(this.Data));
            }
        }
    }
}
这适用于返回JsonResult的任何方法,可使用JSON.Net代替:
[JsonNetFilter]
public ActionResult GetJson()
{
    return Json(new { hello = new Date(2015, 03, 09) }, JsonRequestBehavior.AllowGet)
}

将会回应

{"hello":"2015-03-09T00:00:00+00:00"}

如您所愿!


如果您不介意在每个请求中调用is比较,可以将以下代码添加到您的FilterConfig中:

// ...
filters.Add(new JsonNetFilterAttribute());

现在您的所有JSON都将使用JSON.Net而不是内置的JavaScriptSerializer进行序列化。


1
这是唯一一个提供了可靠方法(可以设置为全局或粒度)而不需要奇怪的JavaScript内联代码的答案。我能投两次赞吗? - T-moty
这是问题的真正答案,以易于理解和简洁的方式为您提供满足需求的选项。谢谢。 - FF-

19

使用jQuery和$.parseJSON自动转换日期

注意:此答案提供了一个jQuery扩展,添加了自动支持ISO和.NET日期格式。

考虑到您正在使用Asp.net MVC,我猜您在客户端使用jQuery。建议您阅读这篇博客文章,其中有代码说明如何使用$.parseJSON来自动转换日期。

代码支持像您提到的Asp.net格式化日期以及ISO格式化日期。通过使用$.parseJSON(),所有日期都将自动为您格式化。


2
起初我认为这种方法非常有效。(有关如何在$.ajaxSetup()中注册转换器,请参见文章末尾的注释。)但是,这种解决方案的一个很大的缺点是它不支持纪元前(1970年)之前的日期......所以现在我已经决定放弃.asmx文件并切换到WebAPI,后者使用JSON.NET更好地格式化日期,并将规避所有这些麻烦。 - ClearCloud8

14

客户端与服务器之间的Ajax通信通常涉及JSON格式的数据。虽然JSON对于字符串、数字和布尔值有效,但由于ASP.NET序列化日期的方式,它可能会对日期造成一些困难。由于它没有任何特殊的表示形式,因此它们被序列化为普通字符串。

作为解决方案,ASP.NET Web Forms和MVC的默认序列化机制将日期序列化为特殊形式- /Date(ticks)/- 其中ticks是自1970年1月1日以来的毫秒数。

可以通过以下两种方式解决此问题:

客户端

将接收到的日期字符串转换为数字,并使用日期类的构造函数以ticks作为参数创建一个日期对象。

function ToJavaScriptDate(value) {
  var pattern = /Date\(([^)]+)\)/;
  var results = pattern.exec(value);
  var dt = new Date(parseFloat(results[1]));
  return (dt.getMonth() + 1) + "/" + dt.getDate() + "/" + dt.getFullYear();
}

服务器端

之前的解决方案使用客户端脚本将日期转换为JavaScript日期对象。您也可以使用服务器端代码以您选择的格式序列化.NET DateTime实例。 要完成此任务,您需要创建自己的ActionResult,然后按照所需的方式序列化数据。

参考资料: http://www.developer.com/net/dealing-with-json-dates-in-asp.net-mvc.html


9

我曾经遇到同样的问题,解决方法是在日期值上使用ToString("dd MMM yyyy"),然后在我的Javascript中使用new Date(datevalue),其中datevalue可以是"01 Jan 2009"。


1
这篇文章应该有更多的赞。它至少和那些最受欢迎的文章一样好。比起切割字符串,这种方法更加优雅。就我个人而言,我使用了这种方法,但是在前端没有重新创建日期对象,因为我只需要显示它,所以我只显示了(稍微不同格式的)字符串。感谢你的提示,@Joe! - vbullinger
1
它确实破坏了关注点分离,即将日期如何显示的关注点放在后端。但是算了,它仍然更优雅。 - A. Murray
1
为什么不使用一些更加稳健的东西,比如ToString("o") - binki
"dd MMM yyyy" 不受 ECMA-262 的支持,因此您不应该期望内置解析器对其进行解析。 - RobG

3

请看这个线程:

http://forums.asp.net/p/1038457/1441866.aspx#1441866

基本上,虽然Date()格式是有效的JavaScript,但它不是有效的JSON(有区别)。如果您想要旧格式,您可能需要创建一个门面并自己转换值,或找到一种方法来获取JsonResult中类型的序列化器,并使用自定义格式来处理日期。


你的意思是 "while the new Date() format is valid javascript" [注意 "new" 关键字] 吗? - JPot

2

虽然不是最优雅的方式,但这对我很有用:

var ms = date.substring(6, date.length - 2);
var newDate = formatDate(ms);


function formatDate(ms) {

    var date = new Date(parseInt(ms));
    var hour = date.getHours();
    var mins = date.getMinutes() + '';
    var time = "AM";

    // find time 
    if (hour >= 12) {
        time = "PM";
    }
    // fix hours format
    if (hour > 12) {
        hour -= 12;
    }
    else if (hour == 0) {
        hour = 12;
    }
    // fix minutes format
    if (mins.length == 1) {
        mins = "0" + mins;
    }
    // return formatted date time string
    return date.getMonth() + 1 + "/" + date.getDate() + "/" + date.getFullYear() + " " + hour + ":" + mins + " " + time;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接