OData.request with dates

Feb 25, 2012 at 11:17 AM

Hi,

I'm sending an OData.request like this:

OData.request({ requestUri : uri, method : "POST", data : data }, function(insertedItem) { } });

Everything is ok unless "data" contains a javascript date object. The object is not converted to OData format and the service replies with a "500 (invalid date format)".

Can you explain me how am I supposed to make datajs automatically translate the javascript date object?

I'm using datajs-1.0.2.min.js.

Thanks!

Feb 27, 2012 at 11:31 PM

Hi Miguelans,

Can you share a network trace of the request?  I assume you are sending your request using JSON as the payload format so the library should convert the date object to the ATOM wire format.. (please see Marcelo's Blog for an explanation behind this decision)

Regards,

Alex Trigo.

Feb 28, 2012 at 9:03 AM

Hi Alex,

This is the request that is sent:

Headers:

POST /datastore.svc/Appointment(151) HTTP/1.1
Host: 127.0.0.1:8888
Connection: keep-alive
Content-Length: 106
Origin: http://127.0.0.1:8888
X-HTTP-Method: MERGE
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.22 (KHTML, like Gecko) Chrome/19.0.1049.3 Safari/535.22
Content-Type: application/json
Accept: application/atomsvc+xml;q=0.8, application/json;q=0.5, */*;q=0.1
DataServiceVersion: 1.0
Referer: http://127.0.0.1:8888/home
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,pt;q=0.6
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: JSESSIONID=bf2cao6yt5n1


Payload:

{"startDate":"2012-01-23T19:00:49.878","endDate":"2012-01-23T20:00:00","description":"Desc","name":"Test"}

 

JS object sent:

  1. dataObject
    1. description"Desc"
    2. endDateMon Jan 23 2012 20:00:00 GMT+0000 (GMT Standard Time)
    3. name"Test"
    4. startDateMon Jan 23 2012 19:00:49 GMT+0000 (GMT Standard Time)
    5. __proto__Object

 

If you need any other info, please just ask!

Thanks!

Feb 28, 2012 at 7:08 PM

Hi Miguelans, 

   Thanks for the info!  Looking at the trace above the library did convert the date objects into a string using the ISO date format.  That is intended because, of how JSON works, we cannot serialize the date into the OData format you mention above (the link to the blog I posted explains the reasons behind this).  Do you know if the odata endpoint is implemented using Microsoft's WCF Data Services library or another implementation?  If it is WCF based, which verison? I ask you this because we tested this scenario using WCF Data Services based OData V1 and OData V2 endpoints and they both support ISO date strings in a JSON request. 

   Have you tried using ATOM instead of json?  You can do that in two different ways, one is to add the Content-Type header to the request object header collections and set it to atom:   

OData.request({
  requestUri: "http://odata.netflix.com/v1/Catalog/Genres",
  headers: { "Content-Type": "application/atom+xml" } },
  function (data, response) {
    alert("Operation succeeded.");
  }, function (err) {
    alert("Error occurred " + err.message);
  });

Or by explicitly using the library's atom handler:  

OData.request(requestObject, 
    function(data, response) { 
        // success function 
    }, 
    function(err) { 
        //error function 
    }, 
    OData.atomHandler);

Thanks,

Alex Trigo.

Feb 28, 2012 at 10:19 PM
Edited Feb 29, 2012 at 9:50 AM

Hi Alex,

I've just tried your suggestion to use Atom and it works.
I'm using odata4j 0.5 so that is the problem...
As it is still in an early stage of development, maybe some day it'll support the other JSON format.

Thanks.

Best regards,

Miguel

Feb 29, 2012 at 10:12 AM
Edited Feb 29, 2012 at 10:14 AM

Hi again,

Unfortunately, odata4j has another problem, now regarding dates and the Atom protocol.
While they don't fix it, I'd like to know if there is any kind of flag or something that may enable the date formatting in standard OData JSON format (that is, the "\/Date(millis)\/"), or some method I may override to force this formatting.
I found a method you apparently had in a previous version of datajs to format dates in standard OData JSON format (in here http://datajs.codeplex.com/SourceControl/changeset/view/2286#16634 - formatJsonDateString). Can I reuse it safely in 1.0.2?

JSON format is also much clearer for debugging purposes. Are you considering supporting the standard OData JSON date format by default in a future version of datajs?

Thanks.

Mar 1, 2012 at 11:35 AM

May I also suggest the replacing of the Atom format in JSON requests by the format "/Date(millis)/" which is supported by odata4j (don't know if this is the case for WCF Data Services). If you need a beta tester I'm available.

Thanks.

Mar 1, 2012 at 7:11 PM

Hi Miguel, 

    So, odata4j doesn't process properly dates in Atom?  The date format in JSON is precisely the problem.  OData, as per the protocol (and the WCF Data Services implementation is strict on this one) is that dates in json follow the following format on the wire:

Edm.DateTime

"\/Date(<ticks>["+" | "-" <offset>)\/"

<ticks> = number of milliseconds since midnight Jan 1, 1970

<offset> = number of minutes to add or subtract

   The problem specifically are the "\/" combinations. These cannot be reliably generated by the JSON serializers currently available in the browsers... And based on what you mention that odata4j works with /Date(nnn)/ rathern than \/Date(nnn)\/  makes me think that the JSON implementation they use has the same behavior.  

   This issue made us use the ISO date format for JSON requests and comment out the function you mention above.  As a workaround you can make your own build of datajs replacing the current date formatting function with the one above.  Thanks for the test offer :), once we sorted this out in a later release (no plans yet) I hope you try it out.

Thanks!

Alex Trigo.    

Mar 2, 2012 at 8:44 AM

Hi Alex,

I ended up replacing the date formatting function with the commented function in the file I referred to in a previous post.
Dates are now sent in the format I told you ("/Date(<ticks>)/") and odata4j works correctly with it.
For anyone experiencing the same problem, I pasted the patched datajs.js file here: http://pastebin.com/rY0qayEx
Thanks for you fast and kind support, Alex.
I'll occasionally check if a new version is available with this problem solved :)

Best regards,
Miguel

Mar 2, 2012 at 11:16 AM

By the way,

Receiving a date in JSON format is not being interpreted correctly by datajs (as you probably already know, and most likely due to the same parser problem you were talking about). Here's an example:

The following response:

{
"d" : {
"results" : [
{
"__metadata" : {
"uri" : "http://127.0.0.1:8888/datastore.svc/Appointment(150L)", "type" : "Datastore.Appointment"
}, "id" : "150", "startDate" : "\/Date(1327536000000)\/", "endDate" : "\/Date(1327622400000)\/", "description" : "Desc1", "name" : "Evento1"
}, ...

Will result in the following object, where the dates are simple strings with the back slash missing:

  1. description"Desc1"
  2. endDate"/Date(1327622400000)/"
  3. id"150"
  4. name"Evento1"
  5. startDate"/Date(1327536000000)/"

 

Regards,
Miguel 

Mar 3, 2012 at 12:11 AM

Hi Miguel, 

   You have to instruct the library that you want it to intrepret  those string values as dates. This can be done in several ways:

   Globally:   The conversion will happen on every single request you make.  This is enabled by setting the flag

   odata.jsonHandler.recognizeDates = true;

   Per request:  The conversion will happen on a request only. This is enabled by adding the approrpiate flag to the rquest object:          

OData.request({
  requestUri: "http://odata.netflix.com/v1/Catalog/Genres",
  headers: { },
  recognizeDates: true },
  function (data, response) {
    alert("Operation succeeded.");
  }, function (err) {
    alert("Error occurred " + err.message);
  });

   Regards,

Alex Trigo.

 

Mar 5, 2012 at 3:42 PM

Hi Alex,

All working now with JSON.

Thanks a lot :)
Regards,
Miguel 

Aug 29, 2012 at 8:22 PM

Hi,

I'm using datajs 1.0.3, my oData endpoint is implemented using WCF Data Services library, also I configured the request to instruct string values as date (recognizeDates: true), but I get the error "Cannot convert a primitive value to the expected type 'Edm.DateTime'" from my endpoint.

I'm using a single Html page to send requests (post, put, get, merge, and delete) to a OData endpoint. That page allows the user to type the request data (in Json format) and selects the Http method to send the request. I have to accept Json format only (user's requirement). If the request does not include dates, the request works fine.

I use fiddler to see the real request and the request does not include the escape character "\"; so dates come like: "/Date(...)/" and not like "\/Date(...)\/". The OData endpoint answer with a deserialization error, because it can not interpret correctly that string like a date.

 

<script src="datajs-1.0.3.min.js" type="text/javascript"></script>
<script src="json2.js" type="text/javascript"></script>

<script language="javascript" type="text/javascript">

function btnTest_onclick() {

OData.defaultHttpClient.enableJsonpCallback = true;
OData.jsonHandler.recognizeDates = true;
...
var data = $("#txtData").val();
var jsonData = JSON.parse(data);
var request = { requestUri: url, method: httpMethod, user: user, password: password, data: jsonData, jsonpCallback: 'myCallback', dataType: 'jsonp', recognizeDates: true };


OData.request(
	request,
	function (data, result) {
						var jsonResult = "";
					jsonResult = "Status code:" + result.statusCode + "\n" + "Status text:" + result.statusText;
					txtResult.value = jsonResult;
							},
					handleException);
}
</script>

Any help is appreciated!
Regards,

Christian Sandoval

Aug 31, 2012 at 10:59 AM

Hi,

 

I also have the same error. I think it is a bug in datajs.

Aug 31, 2012 at 7:27 PM
Edited Aug 31, 2012 at 7:35 PM

Hi Christian, Joe

I assume that the user of your web page would enter a text in JSON format, so if she wants to include a date it would do something like this right?

{ dateValue: "\/Date(12345678)\/" }

The problem is the format of the datetime literal itself. It is unfortunate, but this pattern doens't roundtrip when using standards compliant JSON parsers and serializers (like the one in the browser). So in the code above, when you do  var jsonData = JSON.parse(data),  the value that you get in the variable is:

"{ dateValue: /Date(12345678)/ }" 

 You get it back as string, but notice that the backslashes are gone. This is because of the escaping (and WCF Data Services doesn't like this).  When you pass this value to DataJS, it is already a string in the wrong format.  DataJS recongnizeDates flag only works when reading responses, not when sending them. This because DataJS expect that dates are javascript date objects.

Also, going back a litle bit to the roundtripping, you can force the JSON.parse function to give you back a string in the format that you want... but you will need to doulbe-escape the backslashes: 

var x = '"\\\\/Date(...)\\\\/"';
JSON.parse(x);

"\/Date(...)\/"

 But that's half of the stroy, now you have to send this string as is to the endpoint. But if you stringify it again, there is no way you can keep it as it is (no matter how much escaping you do), the JSON stringify function will mess it up do to the escaping of the backslashes. 

 I see three options for you. 

1. When writing your payload in your webpage, use the ISO datetime format for date time literals instead.  This works well with WCF data services and that is what DataJS does when dealing with dates due to the problems inherent to the OData JSON datetime literal format.

2. use DataJS to parse your date by injecting a response using a mock httpclient object (if you want to still parse and stringify the data): 

OData.read("", 
    function success(data) {
        jsonData = data;    
    },     
    function error(err) {
        // do something with the error 
    },
    OData.jsonHandler,
    {
       request: function(request, success) {
            var response = { 
                  body: $("#txtData").val(), 
                  statusCode: 200, 
                  headers: { 
                       "Content-Type": "application/json"
                  } 
            };
            success(data);
       }
   } 
);

3. use a custom handler object that will not modify the data (it is already in JSON format, so why you would want to parse and stringify it again? )

var request = { requestUri: url, ..., data:$("#txtData").val()};
OData.request(request, 
    function success(data) {
       // do something with data  
    },     
    function error(err) {
        // do something with the error 
    },
    {
       accept: OData.defaultHandler.accept,
       maxDataServiceVersion: OData.defaultHandler.maxDataServiceVersion,
       read: OData.defaultHandler.read,
       write: function(request){
           // set the appropriate headers to the request.
           request.headers = {
               "Content-Type" : "application/json",
               "DataServiceVersion" : "2.0"
           };
   
           // data is assumed to be a JSON string, so just pass it through
           request.body = request.data;
       }
    }
);

Regards,

Alex Trigo.

Aug 31, 2012 at 11:05 PM

Excellent! It works!

Option A, always was my plan "B".

I choose the third option. I have only a doubt: my endpoint creates the entity correctly, with the correct date; but I get the response in the error function (not in the success function).
The message property of the error object says: "ERROR: Invalid date/time value", but the endpoint does not report any error. The error object has in the response property the data of the created register (in Atom format, not in Json).

Thank you so much, I appreciate your valuable time and answer!