Update: this is only an issue with a concrete type in a custom class. I implemented the same pattern with the Contact
sObject containing a DateTime field, no exception thrown. Code below and the question title updated to demonstrate this difference in behavior.
I am using @RemoteAction
methods to expose a custom Apex Type to the page. When performing the retrieve operation, the DateTime
members on the class are serialized into the unix epoch format. Like so:
{
name: "Item Name",
theDate: 1440797362525
}
However, when performing a commit back to the controller with this exact same data, the unix epoch format is not able to be deserialized back into the concrete type and it throws an exception.
Visualforce Remoting Exception: Unable to convert date '1440797362525' to Apex type Datetime.
This is "JS Remoting: 101" and it frustrates me that it does not work properly.
Questions:
- What is the correct way to implement this very simple pattern?
- Why does this work properly with an sObject but not with an apex type?
Controller
public with sharing class DateTimeSerializationController {
public class MyItem {
public String name { get; set; }
public DateTime theDate { get; set; }
}
@RemoteAction
public static MyItem fetchTheItem() {
MyItem item = new MyItem();
item.name = 'Item Name';
item.theDate = DateTime.now();
return item;
}
@RemoteAction
public static MyItem storeTheItem(MyItem item) {
// DML or something similar could happen here
system.debug(item);
return item;
}
@RemoteAction
public static Contact fetchTheContact() {
Contact item = new Contact();
item.FirstName = 'Joe';
item.LastName = 'Bob';
item.My_Date__c = DateTime.now();
return item;
}
@RemoteAction
public static Contact storeTheContact(Contact item) {
// DML or something similar could happen here
system.debug(item);
return item;
}
}
VF Page
<apex:page controller="DateTimeSerializationController">
<script>
window.myApp = window.myApp || {};
window.myApp.fetchItem = function() {
Visualforce.remoting.Manager.invokeAction(
'DateTimeSerializationController.fetchTheItem',
function(result, event){
if (event.status) {
console.log(result);
myApp.myItem = result;
} else {
console.log(event.message);
}
},
{escape: true}
);
}
window.myApp.storeItem = function() {
Visualforce.remoting.Manager.invokeAction(
'DateTimeSerializationController.storeTheItem',
myApp.myItem,
function(result, event){
if (event.status) {
console.log(result);
} else {
console.log(event.message);
}
},
{escape: true}
);
}
window.myApp.fetchContact = function() {
Visualforce.remoting.Manager.invokeAction(
'DateTimeSerializationController.fetchTheContact',
function(result, event){
if (event.status) {
console.log(result);
myApp.myContact = result;
} else {
console.log(event.message);
}
},
{escape: true}
);
}
window.myApp.storeContact = function() {
Visualforce.remoting.Manager.invokeAction(
'DateTimeSerializationController.storeTheContact',
myApp.myContact,
function(result, event){
if (event.status) {
console.log(result);
} else {
console.log(event.message);
}
},
{escape: true}
);
}
</script>
<p>Open the browser console and then click the buttons.</p>
<button onclick="myApp.fetchItem();">Fetch Item</button>
<button onclick="myApp.storeItem();">Store Item</button>
<button onclick="myApp.fetchContact();">Fetch Contact</button>
<button onclick="myApp.storeContact();">Store Contact</button>
</apex:page>
toUTCString
on dates going from JavaScript to Apex Date fields and explicitly use newDate()
on the number coming from Apex back into JavaScript. Looks like that may still be the case.