20

I am trying to deserialize a json object that has a javascript date in it. When JSON.stringify is called on the object, dates are serialized to strings that are not properly deserialized back to dates. I have attempted to deserialize the object using both the native browser implementation with chrome, IE, and FF and using jquery. Both give the some results. Here is the snippet:

var obj = {Date: new Date()};
var objSer = JSON.stringify(obj);
var objDeser = JSON.parse(objSer);
var objJqDeser = $.parseJSON(objSer);

function getYear(value){
  try{
     return value.getYear();
  }
  catch(err){
    return err;
  }
}

$("#orig").text("Orig Year: " + getYear(obj.Date));
$("#deser").text("Deser Year: " + getYear(objDeser.Date));
$("#jqDeser").text("JqDeser Year: " + getYear(objJqDeser.Date));

I want objDeser.Date to be a js date not a string. You can see this problem in action here: http://jsbin.com/unijud/24/edit. Is there any js libraries that can properly deserialize the dates when building the javascript object?

adamdehaven
  • 5,890
  • 10
  • 61
  • 84
mdeangelo272
  • 674
  • 1
  • 5
  • 14

6 Answers6

14

JSON.parse has a little-known second parameter: the 'reviver' function. This is used for precisely this purpose: to revive a date string into a Date object (or, hypothetically, any other kind of object you wanted to convert from string) during the initial parse.

There's an SO post about this, and here's a blog post that includes an implementation example and a function that will do property checking for a couple common date encodings (ISO & that weird .NET AJAX format), before parsing to a Date.

Here's the key function from that blog post, fwiw:

// JSON date deserializer
// use as the second, 'reviver' argument to JSON.parse();

if (window.JSON && !window.JSON.dateParser) {
    var reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/;
    var reMsAjax = /^\/Date\((d|-|.*)\)[\/|\\]$/;

    JSON.dateParser = function (key, value) {
        // first, just make sure the property is a string:
        if (typeof value === 'string') {
            // then, use regex to see if it's an ISO-formatted string
            var a = reISO.exec(value);
            if (a) {
                // if so, Date() can parse it:
                return new Date(value);
            }
            // otherwise, see if it's a wacky Microsoft-format string:
            a = reMsAjax.exec(value);
            if (a) {
                // and perform some jujitsu to make use of it:
                var b = a[1].split(/[-+,.]/);
                return new Date(b[0] ? +b[0] : 0 - +b[1]);
            }
            // here, you could insert any additional tests and parse instructions you like, for other date syntaxes...
        }
        // important: you need to return any values you're not parsing, or they die...
        return value;
    };
}

// use: JSON.parse(json,JSON.dateParser); 

(There are lots of opinions about proper regexes for ISO 8601 dates. YMMV. Also, there's no particular reason to punch the function onto the global JSON object. You could store/reference it anywhere you like. )

Community
  • 1
  • 1
XML
  • 19,206
  • 9
  • 64
  • 65
  • 3
    Better ISO date regexp: `/^\d{4}-(0[1-9]|1[0-2])-([12]\d|0[1-9]|3[01])([T\s](([01]\d|2[0-3])\:[0-5]\d|24\:00)(\:[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3])\:?([0-5]\d)?)?)?$/` – catamphetamine Dec 13 '16 at 23:32
  • My hero! The anwer's solution couldn't handle a date like this `2018-04-03T22:00:00+00:00`, but @asdfasdfads's solution worked – Dylan Meador Apr 03 '18 at 02:38
  • @asdfasdfads Why is it better? – xr280xr Oct 22 '19 at 23:21
5

I took @LastCoder advice and wrote a simple implementation. It seems to be doing what I wanted it to.

var jsonDates = {
  dtrx2: /\d{4}-\d{2}-\d{2}/,
  parse: function(obj){
      var parsedObj = JSON.parse(obj);
      return this.parseDates(parsedObj);
  },
  parseDates: function(obj){
    // iterate properties
    for(pName in obj){

      // make sure the property is 'truthy'
      if (obj[pName]){
        var value = obj[pName];
        // determine if the property is an array
        if (Array.isArray(value)){
          for(var ii = 0; ii < value.length; ii++){
            this.parseDates(value[ii]);
          }
        }
        // determine if the property is an object
        else if (typeof(value) == "object"){
          this.parseDates(value);
        }
        // determine if the property is a string containing a date
        else if (typeof(value) == "string" && this.dtrx2.test(value)){
          // parse and replace
          obj[pName] = new Date(obj[pName]);
        }
      }
    }

    return obj;
  }
};

A live example is available on jsbin. A reference is available on gist.

mdeangelo272
  • 674
  • 1
  • 5
  • 14
5

In order to represent dates using JavaScript, I found that JSON uses ISO 8601, a specific string format to encode dates as string. When I last checked though, there is not an official standard for what the date format should look like. The major browsers use ISO 8601 as the JSON Date encoding format.

So, dates are encoded as ISO 8601 strings and then used just like a regular strings when the JSON is serialized and deserialized.

That being said, ISO dates can be converted into JavaScript dates by use of the JavaScript Date constructor, which accepts a wide variety of inputs to construct a date, ISO 8601 being one of them.

Get todays date:

 var curDate = new Date();
document.write(curDate); //Mon Feb 01 2016 12:57:12 GMT-0600 (Central Standard Time)

Parse it into a string:

var dateStr = JSON.parse(JSON.stringify(curDate));
document.write(dateStr);//2016-02-01T18:59:35.375Z

Then convert it back to a javascript date, using the constructor:

var date = new Date(dateStr);
document.write(date); //Mon Feb 01 2016 12:59:35 GMT-0600 (Central Standard Time)
Community
  • 1
  • 1
James Drinkard
  • 15,342
  • 16
  • 114
  • 137
3

The JSON spec does not include special formatting for dates. As such they are often serialized as a string, sometimes with special markings to indicate it should be treated as a Date object if the language supports them. As such, most (all?) browser-native JSON parsers can not round-trip a Date object properly.

There are several good libraries that help with this - I very much like MomentJS though I have used datejs in the past as well. You would just need to iterate over your objects and convert the proper fields to Date objects after they have been parsed.

I find it helpful to remember that the JSON format is much more restrictive than JavaScript object literal notation.

Goyuix
  • 23,614
  • 14
  • 84
  • 128
  • Thank you. These libraries seem to add functionality to dates but do not seem to enhance/implement a json parser. – mdeangelo272 Jan 23 '13 at 22:16
  • The `JSON.parse()` function accepts a reviver function that will allow it to iterate over the properties for you, applying the reviver function to each one. – xr280xr Oct 22 '19 at 23:23
1

The JavaScript es5 is able to parse the date like 2018-04-03T22:00:00... in its default Date constructor. (e.g. new Date("2018-04-03T22:00:00...");.

For someone like me, who is searching for automatic date deserialization from JSON web responses, this could be handy.

/**
 * Iterates over all entries of the input object and replace the string dates with the objects of {@link Date}.
*/
function fixDateObjects(responseBody) {
  if (responseBody) {
    const regex = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}/;
    for (const [key, value] of Object.entries(responseBody)) {
      const val = String(value);
      if (val.startsWith('[object Object]')) {
        fixDateObjects(value);
      }
      if (val.match(regex)) {
        responseBody[key] = new Date(val);
      }
    }
  }
}

Explanation: It iterates over all object entries of the JSON(responseBody) and replaces the string dates (matched by the given regex) with the new Date(str) objects.

Result: This brings you freedom of further processing. Now you have all date strings fully deserialized.

//usage
function parseBody(response) {
   fixDateObjects(response);
   
   console.log(response.someDate); // Mon Aug 30 2021 22:45:59 GMT+0200 (...)
   // further processing
}
Ben
  • 196
  • 15
0

You could manually add all of the Date functions you require to the String.prototype.

String.prototype.getYear = function() {
    return Date.parse(this).getYear();
};
var obj = {date: new Date()};
var dtObj = JSON.parse(JSON.stringify(obj));
console.log(dtObj.date.getYear());

Or you could override JSON.parse and have it loop through the result object looking for strings that match the time stamp regex and then convert them to Date objects.

var JSON_parse = JSON.parse;
JSON.parse = function(str) {
    var res = JSON_parse(str);
    findAndConvertStringsToDates(res);
    return res;
} 

EDIT Here's what I'd throw together for an implementation

(function() {
    var jsonParse = JSON.parse;
    var reDate = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/i;
    function jsonDate(obj) {
        var type = typeof(obj);
        if(type == 'object') {
            for(var p in obj)
                if(obj.hasOwnProperty(p))
                    obj[p] = jsonDate(obj[p]);
            return obj;
        } else if(type == 'string' && reDate.test(obj)) {
            return new Date(obj);
        } 
        return obj;
    }
    JSON.parse = function(str) { return jsonDate(jsonParse(str)); }
})();
/*
 * Tests
 */
var dt = JSON.parse(JSON.stringify({date: new Date()}));
console.log(typeof(dt.date));
console.log(JSON.parse(JSON.stringify(null)));
console.log(JSON.parse(JSON.stringify(123)));
console.log(JSON.parse(JSON.stringify("test")));
console.log(JSON.parse(JSON.stringify(new Date())));
console.log(JSON.parse(JSON.stringify([1,new Date(),2])));
console.log(JSON.parse(JSON.stringify({d: new Date(), d2: {d3: new Date(), d4: [0,new Date(),4]}})));
Louis Ricci
  • 20,804
  • 5
  • 48
  • 62
  • Option 2 is essentially what I am after. I was hoping to find a library that implemented the findAndConvertStringsToDates functionality. I am not quite a js guru and was hoping to avoid having to implement this myself. – mdeangelo272 Jan 23 '13 at 22:18
  • @mdeangelo272 - I edited with a workable solution, there may be issues with very old browsers, but less than 20 lines of code isn't too much to work through. – Louis Ricci Jan 24 '13 at 13:14
  • 1
    For anyone that finds this answer in the future, this is a very bad idea. This modifies the `JSON` global, so any libraries on the page that use the JSON global will get this monkey-patched version that behaves in a non standard way. If you want this behavior, do not add it to the global JSON object, but use a separate function for it. – bcherny Jan 26 '15 at 04:33