Bug Tracker

Ticket #8671 (closed bug: wontfix)

Opened 4 years ago

Last modified 16 months ago

.parseJSON can't decode a new Date

Reported by: mithon81@… Owned by:
Priority: undecided Milestone: 1.next
Component: data Version: 1.5.1
Keywords: Cc:
Blocking: Blocked by:

Description

When I do a

jQuery.parseJSON("{\"BirthDate\":new Date(1301180400000)}");

i get: Invalid JSON: {"BirthDate":new Date(1301180400000)}

But if I use the ExtJS equivalent:

Ext.decode("{\"BirthDate\":new Date(1301180400000)}");

i get the desired object containing a javascript Date instance.

Right now this means I must return MIME text/plain from my service and convert the return value with Ext.decode instead of simply supplying application/json in the service and getting the desired json back immediately.

I realize there is a discussion about the validity of sending constructor calls inside a json, but I think there was a rather well made point in that discussion; Since JSON is the javascript object notation wouldn't that imply that javascript builtin objects should deserialize properly?

Change History

comment:1 Changed 4 years ago by ajpiano

  • Status changed from new to closed
  • Resolution set to wontfix

jQuery.parseJSON emulates the behaviour of the native browser JSON object where it does not exist, and delegates to it where it does, which is most modern browsers. Deviating from the native behaviour and supporting constructor functions, which are NOT valid JSON, would not be a wise move from my perspective. It's all well and good that Ext chooses to support this, but I can't see us following in that direction - primarily because of compatibility with native JSON.

comment:2 Changed 4 years ago by dmethvin

Go to jsonlint.com and enter this:

{"BirthDate": new Date(1301180400000)}

It's not valid JSON. It's valid JavaScript, but not JSON.

comment:3 Changed 4 years ago by Gaute Løken <mithon81@…>

Sorry about my late reply. Your responses were marked as spam unfortunately.

I would much appreciate a pointer or two on the following:

1) How (in what format) should I return dates from my web services and how should I consume them on the client? Is there a standard way? Will I have to make a pass over the response json replacing strings with dates according to certain rules etc, or is there a more slick way of doing this?

2) The stated reason is "primarily because of compatibility with native JSON". Is there an introductory resource available to inform me of these compatibility issues?

I'll just have to take your word for supporting constructor functions not being a wise move. From a cursory search this seems to be due to parsing performance. Good enough reason for me.

Would the problem then be that Date is not a strong type in native JSON? To me it sounds like this is a standards problem that should be looked into. One has to deal with formating amounts and dates for presentation, but there should be a standard for the model datatypes. Like there are floats usable for amounts, there should also be a native date datatype.

If native JSON could be expanded to accept something like the following it would be awesome:

{"BirthDate": D1301180400000}

As an aside it should probably also accept a format for a regex as a native type as well, but I guess that's more of a long shot.

I realize I'm probably ranting to the wrong people, but maybe you have some influence with these things.

comment:4 Changed 4 years ago by anonymous

What about the valid JSON format that ASP.NET uses.

{"recdate":"\/Date(1302776454430)\/","count":1}

comment:5 Changed 4 years ago by rwaldron

  • Component changed from unfiled to data

comment:6 Changed 4 years ago by anonymous

As per http://bugs.jquery.com/ticket/8320 here is a converter for the date.

$.ajaxSetup({

use custom converter to handle invalid json data returned by WCF Data Service ignores invalid escaped single quotes (\') in json data e.g. { "foo": "bar\'tender" } --> { "foo": "bar'tender" } also converts Microsoft Dates into actual Date objects e.g. { "\/Date(###)\/" } --> { new Date(###) } converters: {

"text json": function (textValue) {

return (new Function("return " + textValue

.replace(/"
\/Date\((-?\d*)\)
\/"/g, "new Date($1)")

))();

}

}

});

comment:7 Changed 4 years ago by Gaute Løken <mithon81@…>

Firstly, thank you very much for the tip on the converters which gives me a hook for working around the problem, and for the tip on $.ajaxSetup which allows me to use that hook in a sentralized place instead of specifying it to $.ajax() all the time.

Unfortunately it's not the perfect hook since $.parseJSON doesn't have an overload that allows me to deal with how each value is decoded, but only has a global point for the complete raw json string. Actually I might dig into that a bit and see if I can suggest something (in a different post).

Without that however, the suggested replace doesn't work. That sort of regex would only work if I had such a hook. As it is the global replace works only to fix faulty json from the service. But it must still return valid JSON to be parsed by $.parseJSON. That is, this only works for non-nested data, which isn't good enough for my needs.

I am left with 3 options:

1) Return the non-native JSON from my service and set up jQuery to use the Ext decoder which is able to handle this:

$.ajaxSetup({
    converters: { "text json": Ext.decode }
});

Now that I know this is non-valid JSON I hate giving up and using something non standard. In practice it seems to solve everything though.

2) Return valid JSON and use a replace scheme to convert it to non-valid JSON with Date constructors, and then use the Ext decoder. This would allow me to insert JSON validation before I make it invalid, and thus giving me the security I loose from option 1.

$.ajaxSetup({
    converters: {
        "text json": function(json) {
            //TODO: Test json is valid
            var procJson = json.replace(/"\\\/Date\((-?\d*\+\d{4})\)\\\/"/g, "new Date($1)");
            return Ext.decode(procJson);
        }
    }
});

3) Return valid JSON, but make a more complex JSON converter which first parses the input using $.parseJSON, and then recursively traverses the resulting object graph looking for strings containing Dates, and converting them. This is a little more involved, so I'm going to need to define some helper methods and regexes. I will assume ISO 8601 format for my dates, and I might not have the perfect regex for it, since I didn't look up the iso, and merely cooked up a regex from an example string; "1993-04-16T18:36:40.8983282+02:00". Here's the code:

var reDate = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{7}[\+-]\d{2}:\d{2}$/;
var reArray = /^function Array\(\)/;
var reObject = /^function Object\(\)/;

function isArray(val) { return reArray.test(val.constructor.toString()); }
function isObject(val) { return reObject.test(val.constructor.toString()); }

function convertJsonDates(value, index, parent) {
    if (typeof(value)=='string' && reDate.test(value)) {
        if (parent)
            parent[index] = new Date(value);
        else
            return new Date(value);
    } else if (isArray(value)) {
        for (var i = 0; i < value.length; i++)
            convertJsonDates(value[i], i, value);
    } else if (isObject(value)) {
        for (var p in value)
            convertJsonDates(value[p], p, value);
    }
    return parent || value;
}

$.ajaxSetup({
    converters: {
        "text json": function(json) {
            return convertJsonDates($.parseJSON(json));
        }
    }
});

And the sanity-check for the conversion function:

console.log(convertJsonDates({
    "a": "foo",
    "b": "goo",
    "c": [
    1024,
    "1993-04-16T18:36:40.8983282+02:00"
    ],
    "d": {
        "a": 3.1415,
        "b": 1.2345,
        "c": "1993-04-16T18:36:40.8983282+02:00"
    },
    "e": "1993-04-16T18:36:40.8983282+02:00"
}));

So, that was quite fun tbh. :) But I'm still undecided on what's the best approach. At this point I guess it's a matter of performance. I'm guessing that Ext.decode uses eval at some point internally. So the question really is wether or not $.parseJSON + a call to convertJsonDates is faster than eval. Any educated guesses? If I want to validate the JSON (option 2), then I have to add that validation to the equation. Finally it's a question of maintainability and cleanliness. Is it better to do these things in custom code (option 3), or cut my losses and let eval handle it (through Ext decoder)?

Sorry about the length of these posts. I hope some of you find them helpfull however. I appreciate the input so far. Thanks!

comment:8 follow-up: ↓ 9 Changed 16 months ago by jeanph01@…

I would be nice if jQuery provided an option to take care of dates during JSON parsing (ex:a callback) so that we can get native JavaScript date objects.

comment:9 in reply to: ↑ 8 Changed 16 months ago by rwaldron

Replying to jeanph01@…:

I would be nice if jQuery provided an option to take care of dates during JSON parsing (ex:a callback) so that we can get native JavaScript date objects.

You can easily duck-punch jQuery.parseJSON to include this functionality

Note: See TracTickets for help on using tickets.