Date and Time Values and Java Interop: a concrete example using XStream
Last month I posted on Date and Time values and interop. One of the key points I made is that you can lose information (specifically timezone information) when you transition from .NET to something else. If you stay within .NET, then you don't lose that information, even if you serialize to XML and then de-serialize. If you transition outside of .NET, then you may lose information.
What do I mean by "serialize"?
It is possible to produce an XML instance document that contains the state stored within a .NET type. Imagine a Person object that contains a name, a birthdate, and a coolness rating. These three data items might appear as three elements within the XML instance document that is produced via "serialization". This is exactly what happens within a web services call.
Digression: Within a traditional request/response communication transaction, there are four transition points, points at which data enters or leaves an application. Outbound transitions occur when data is emitted from the app. We call this serialization. Inbound transitions occur when data enters the app from outside; this happens via de-serialization.
The first outbound transition occurs when transmitting the request on the caller. The request is converted from an object graph stored in the memory of the .NET application, into an XML instance document, via serialization. The serialized request is then transmitted via some mechanism to the server. On the receiving (server) side, the request is then converted into an in-memory object graph; this is an inbound transition, which occurs via de-serialization. The server then performs some action and sends the response, which is a mirror image of the request, from the point of view of serialization and de-serialization. Both outbound and inbound transitions are of the kind I described in that prior post, a transition you need to be careful about.
With each transition, there are two types of pitfalls, In my prior post I spoke of the loss of information. There is also the converse pitfall - one side of the transmission could provide more information than can be handled by the other. Let's look at this case now.
An Example
Here's a concrete example. I'm going to take some data and serialize it to XML using .NET, and then de-serialize it from XML into an instance of a Java object. For now I am not going to concern myself with the transport - the details of how the XML instance document gets from the .NET app to the Java application. It could be any one of a number of ways: REST-style invocation, shared filesystem, shared database, message queue, smtp, ftp, tcp transfer, and so on. It could be wrapped in syntactic sugar so you can call it a "web services call". It doesn't really matter. I'm concerned here only with the serialization and de-serialization.
For the Java side, I selected the XStream Java XML Serialization package (version 1.2.2). The thing about Java is, there are about a thousand and one XML Serialization libraries out there, and you know, every one of them is a little different. Apparently many people each thought they could build a better mousetrap, and so there are now a zillion choices for Java-based XML serialization libraries. As to which is "best" ? I will leave that to someone with more time than I have. In any case, XStream is pretty easy to use, and seems to be reasonably common, so I chose that. (I have previously used JAXB as well as the Apache XMLBeans library. This time, I chose a different library.)
Ok, now in this scenario, the classes on the .NET side and on the Java side are pretty simple. The .NET class looks like this:
1 [XmlRoot("person")]
2 public class Person
3 {
4 public Person() {}
5 public Person(string name, DateTime birthdate, int rating)
6 {
7 _birthdate= birthdate;
8 _rating= rating;
9 _name= name;
10 }
11
12 [XmlElement("name")]
13 public string _name;
14
15 [XmlElement("rating")]
16 public int _rating;
17
18 [XmlElement("birthdate", DataType="dateTime")]
19 public System.DateTime _birthdate;
20 }
You can see that one of the data members in the .NET type is a DateTime value. There are a number of options for serializing a DateTime value into XML. One option is to serialize it as a number, for example a time_t quantity which from my Un*x days is a 32-bit quantity representing the number of seconds that have elapsed since January 1st, 1970. This is an interesting option but not one I explored for this example. Another set of options is to serialize it as one of the xsd built-in types, like xsd:dateTime, xsd:date, or xsd:time, as defined in the XML Schema standard. I chose the xsd dateTime type, as you can see from the attribute on line 18 in the code above.
There is a corresponding Java class; It looks like this:
1 public class MyData
2 {
3 // fields to be serialized
4 public String name;
5 public int count;
6 public java.util.Date date;
7 }
The Java class lacks annotations because the XStream library I chose allows me to specify the Java-to-XML binding in code, programmatically.
Serializing from .NET
I use this kind of code to serialize from an instance of the object in .NET to an XML document:
1 XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
2 ns.Add( "", "" );
3
4 System.Console.WriteLine("\n\nThe serialized Person:\n");
5 s1.Serialize(new XTWFND(System.Console.Out), d1, ns);
6 System.Console.WriteLine("\n");
The XmlSerializerNamespaces thing just tells the serializer to omit the default namespaces. I do this only to tidy up the resulting XML. Also, that XTWFND is defined like so:
1 /// XmlTextWriterFormattedNoDeclaration
2 /// helper class : eliminates the XML Documentation at the
3 /// start of a XML doc.
4 /// XTWFND = XmlTextWriterFormattedNoDeclaration
5 public class XTWFND : System.Xml.XmlTextWriter {
6 public XTWFND (System.IO.TextWriter w) : base(w) { Formatting= System.Xml.Formatting.Indented;}
7 public override void WriteStartDocument () { }
8 }
It is a helper class, an XmlTextWriter that indents and does not emit an XML Declaration line; again, this is for cleaner XML but is not strictly necessary for interop.
The XML resulting from this code looks like this:
<person>
<name>Yiifusxxwic</name>
<rating>859</rating>
<birthdate>2007-10-08T22:56:45.8697911-07:00</birthdate>
</person>
Serializing from Java
In Java with XStream, we can do something similar. The code to serialize looks like this:
Person p1= getInstance();
XStream xstream= new XStream();
xstream.alias("person", Person.class);
System.out.println(xstream.toXML(p1));
And then XStream infers the element names from the names of the members in the class. The resulting XML document looks like this:
<person>
<name>Mvsnyzilgen</name>
<rating>426</rating>
<birthdate>2007-10-08 23:17:11.679 PDT</birthdate>
</person>
Looks pretty similar to the XML generated from .NET, right? Yes, it is Similar, but not the same. And because the XML is not the same format on either end of the wire, interop isn't happening.
When .NET serializes the DateTime value, it uses the xsd:dateTime format, which includes a timezone specified in "offset from UTC". The default Date serialization in XStream does not. But, there is a nice ISO8601DateConverter in XStream. This gives you application code like so:
1 Person p1= getInstance();
2 XStream xstream= new XStream();
3 xstream.alias("person", Person.class);
4 xstream.registerConverter(new ISO8601DateConverter());
5
6 System.out.println(xstream.toXML(p1));
This modified usage gives you XML out of the Java application like this:
<person>
<name>Kgdtqgcdyxg</name>
<rating>211</rating>
<birthdate>2007-10-08T23:21:44.931-07:00</birthdate>
</person>
...which is much closer to what we want.
Deserializing
De-serializing from within .NET is pretty simple. We can do it with code like this:
string xmlFromXstream =
"<person>\n" +
" <name>Jjqbrkqcnuk</name>\n" +
" <rating>934</rating>\n" +
" <birthdate>2007-10-08T23:21:44.931-07:00</birthdate>\n" +
"</person>\n";
Person d2 = (Person)s1.Deserialize(new System.Xml.XmlTextReader(new System.IO.StringReader(xmlFromXstream)));
And slurping in the XML as generated from XStream, just works.
De-serializing from within Java with XStream is similar. The code looks like this:
String xmlFromDotNet = "<person>" +
" <name>Sxgiyg</name>" +
" <rating>859</rating>" +
" <birthdate>2007-10-08T22:06:28.8700931-07:00</birthdate>" +
"</person>";
Person p2 = (Person)xstream.fromXML(xmlFromDotNet);
But this does not work. This is because .NET serializes time values with a higher precision than Java can deal with. Look closely at the birthdate values generated from .NET and compare them to the birthdate values generated from Java. If you look carefully you will see that the dates generated from .NET have 7 decimal digits of precision, while Java has just 3. And Java is not able to de-serialize from the more precise representation generated by .NET! Ack!
What to do? The easiest thing to do, if you don't mind the loss of precision, is to modify the .NET class to serialize the dateTime value into something XStream can handle. We can do this by modifying the type to look like this:
1 [XmlRoot("person")]
2 public class Person
3 {
4 private static string formatString= "yyyy-MM-ddTHH:mm:ss.fffzzz";
5
6 public Person() { }
7 public Person(string name, DateTime birthdate, int rating)
8 {
9 _birthdate = birthdate;
10 _rating = rating;
11 _name = name;
12 }
13
14 [XmlElement("name")]
15 public string _name;
16
17 [XmlElement("rating")]
18 public int _rating;
19
20 [XmlElement("birthdate")]
21 public string _bdayString
22 {
23 get { return _birthdate.ToString(formatString); }
24 set
25 {
26 try {
27 _birthdate =
28 System.DateTime.ParseExact(value,
29 new string[] {formatString},
30 new System.Globalization.CultureInfo("en-US", true),
31 System.Globalization. DateTimeStyles.AllowWhiteSpaces);
32 }
33 catch {}
34 }
35 }
36
37 [XmlIgnore]
38 public System.DateTime _birthdate;
39 }
This "hides" the actual DateTime value from the XML serializer with the XmlIgnore attribute, but then serializes in its place, a string, formatted just the way Java wants it. The getter and setter on the string property allows the serialization and de-serialization to work reflexively, using the same format. The XML generated from that Person type works nicely with the Java code I showed previously.
Where are We?
This example shows that .NET and Java can use XML to interop, but it shows some of the pitfalls associated with DateTime values and loss of information on serialization. In particular it shows that you need to serialize an common dateTime format (ISO8601 mostly works) and you need to be careful of precision on either side. We did not cover Nillable Date values - that's another pitfall I will leave for another day.
Check the attachments for the full source code.
Comments
Anonymous
October 10, 2007
PingBack from http://www.artofbam.com/wordpress/?p=6841Anonymous
September 01, 2008
A couple years ago, I had problems with the DateTime serializer as well, but not for interop but within .Net - essentially it came down to the serializer always putting in the offset of the current timezone, no matter whether it was a local date or a UTC date, which made it impossible to correctly deserialize. I think the solution was to not use UTC dates because of that. In the end the implementation of date serialization drove part of the design here, which was less than ideal and required some other workarounds. Not sure if this is still the case or if we missed sth completely back then Good postAnonymous
October 23, 2008
I'm looking to put together more Java and .NET interop samples. If any of you have any particular requests,Anonymous
July 15, 2010
Excellent post! Did you ever get around to writing about approaches to handling Nullable date values using XStream?Anonymous
September 27, 2010
These issue are because of xstream acting funky! xsd has xsd datetime type which webservices use all the time both java and .net understand it jaxb / xmlbeans will be your better bet