In the previous installment in this series I explained how to implement annotations in JavaScript. In this, the final article in the series, I will explain how to implement a JavaScript equivalent of the .NET System.Xml.Serialization.XmlSerializer class.
A challenge I’ve been faced with in numerous projects is to implement a rich client like “chubby client” UI in plain ASP.NET applications. Most controls found in rich clients are fairly trivial to replicate in modern browsers with extensive DHTML support. In addition, the requirements for this type of user interface usually arise in intranet projects where the target platform is standardized. Most of the clients I’ve worked with use Microsoft Internet Explorer 5.0 or later.
The main challenge is how to handle client to server communication. When users are faced with the familiar functionality like drag and drop and similar, they also expect applications to behave the same way as old school Windows applications. For example, users might expect long running database queries to run “in the background” while they continue to fill in other data in forms or simply information in a page being changed without updating the entire page. In scenarios like these the postback architecture of ASP.NET does not provide the required functionality.
Out-of-band calls
At the end of the 90ies Microsoft introduced a technology called remote scripting. This was a combination of JavaScript helper methods, a proxy implemented as a Java applet and some ASP code which enabled you to do both synchronous and asynchronous calls from client side script to functions in server side ASP pages.
Following the introduction of SOAP, Microsoft released the WebService behavior, a DHTML behavior with the ability to call web services from client side script.
This is a sophisticated component which makes use of XmlHttp to invoke web service both synchronously and asynchronously.
In ASP.NET 2.0 it is possible to invoke server events from client side script without posting back the page. This is supported through a new mechanism called script callbacks. For more information on this technology, read Dino Espositio’s excellent “Cutting Edge” article on the subject.
Mostly out-of-band calls have been used with fairly simple return values such as strings and numerals. The “advanced” uses have typically been passing arrays as comma separated lists. This has greatly limited the applicability of the technology and created a wide functionality gap between the object oriented programming environment of the server world and the more primitive environment in the browser. As a result DHTML user applications tend to be buggy and difficult to maintain. This, in conjunction with the functional requirements getting steeper and steeper, led me to come up with a more seamless way of passing data between the client and the server.
A JavaScript XmlSerializer
One of the most celebrated classes in the .NET framework is the XmlSerializer class. This class enables you to serialize objects into XML documents and deserialize XML documents into objects. As we all know, XML documents are represented as strings, so it is simple to pass an XML document as either a parameter or a return value on an out-of-band call.
By implementing a client side XML based serialization and deserialization it would be possible to pass an object from a client script to a server method and vice versa. There are of course huge differences between the powerful .NET platform and the simple JavaScript language, but these have little impact on a client to server communications channel as it would only make sense to pass data transfer objects.
The solution
The first step was to create a serialize method able of traversing an object graph to build a XML representation of the object. JavaScript has very few built in types. These are string, boolean, number and object. In addition the object type has various well known constructor signatures such as date and array.
Building the XML document
When serializing an object to XML the member names should map directly to the XML element names. Consider the following object instance.
|
Person |
|
FirstName (string) |
“Anders” |
|
LastName (string) |
“Norås” |
|
Age (number) |
28 |
When serialized, the Person instance should result in the following XML document.
To identify class, object and member names the techniques described in the previous article are used.
The JavaScript XML serializer traverses the object graph to create an XML node for every public member of an instance. If the member is a simple type, such as a string or number, the member value returned. If the member is a complex type the child object is serializer recursively calls it self to serialize this object as well.
Mapping types
Simple types like string and boolean are straight forward to represent in XML. Numerals are more of a challenge since whole numbers, such as int and long, have a different XML representation than decimal numbers.
All JavaScript arrays are weakly types whilst XML serialized arrays are either strongly or weakly typed. Object members who themselves are objects make up an additional complex types in the objects XML representation.
As mentioned, the JavaScript type system is much more lightweight than the .NET Common Type System. This gave some challenges, but also limited the problem scope since the types that needed to be supported where few.
To be able to determine whether a number is a whole or decimal, create a rounded copy of the value and compare this to the original value. If the values are equal it’s a whole number otherwise it’s a decimal.
|
if (Math.round(obj)==obj) return "int"; else return "double"; |
In XML schema there are defined many different whole and decimal types, such as negativeInteger, unsignedByte and double. In JavaScript all of these map to the number type. When converting from a JavaScript number it is safe to use the largest XML schema type to contain the value to avoid overflow.
To determine whether an array is strong or weak simply count the number of different types in an array. If the count is greater than one the array is weak, otherwise it is strong.
XML dates follow a specific pattern which makes it simple to create a date instance in JavaScript by parsing the date string. It is just as simple to get an XML representation of a date object by formatting the date according to the pattern.
Serializing arrays
When storing objects in a JavaScript array the instance name of each element is “object”. In .NET each element gets the name of the class as the element name. If you wish to use a different element name you can do this in .NET by using an XmlArrayItem attribute to specify the element name to be used for the individual array elements.
public class Group { [XmlArrayItemName(“Member”)] public Customer[] Customers; } |
To support serialization of arrays in JavaScript it is necessary to have a similar functionality since there is no way of retrieving the class name of an array element.
To support this I replicated the XmlArrayItemNameAttribute as a JavaScript annotation. The array serialization methods of the JavaScript XML serializer try to retrieve this annotation from every object element in arrays. If the object is annotated with an XmlArrayItemName this name is used for XML elements. Otherwise the element is “object”.
Deserialization
All information about how objects should be serialized to XML is contained within the objects themselves. This is not the case when deserializing XML documents into objects.
Consider the following example:
|
<Customer> <ID>15530</ID> <Name>Monkey’s Business</Name> <AccountType>KeyAccount</AccountType> </Customer> |
The “AccountType” is an enumerator value, but when reading the XML document the element appears to be a string. To deserialize the document correctly you will need to have this additional knowledge about the document. The JavaScript deserializer uses an XML schema fragment as a blueprint for building an object. The object member values are retrieved from the XML document. The following schema fragment would define the above XML code.
|
<s:complexType name="Customer"> <s:sequence> <s:element minOccurs="1" maxOccurs="1" name="ID" type="s:int" /> <s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string" /> <s:element minOccurs="1" maxOccurs="1" name="AccountType" type="s0:Account" /> </s:sequence> </s:complexType> |
If you use web services with your out-of-band calls you can use the schema in the WSDL file to support deserialization.
If the member is a complex type the deserializer looks for an object constructor matching the type’s name. If no such constructor is found an object is created. This enables you to create client side equivalents of server side classes with custom client side methods to manipulate the data. The ability to fallback to creating plain objects ensures that any object can be deserialized.
Enumerators
If the member is an enumeration value the deserializer requires a matching JavaScript object to be defined. Consider the following example:
|
public enum Account { KeyAccount=1, Other=42 } |
The above C# code will have the following XML schema fragment generated to define it.
|
<s:simpleType name=” Account”> <s:restriction base=”s:string”> <s:enumeration value=”KeyAccount”/> <s:enumeration value=”Other”/> </s:restriction> </s:simpleType> |
To ensure that the same values are used on the client the server side enumerator has to be mirrored.
|
Account={ KeyAccount:1, Other:42 } |
To retrieve the appropriate value the deserializer first locates the Account object. If no object is defined an exception is thrown. Then the derserializer iterates thru every member of the object comparing the member name to the value in the XML document. When a match is fount the value from the Account enumerator is set on the object.
Using the serializer
To serialize an object to XML simply create a new instance of the XmlSerializer class and invoke it’s serialize method.
function CustomerInfo(id,name,isActive,lastUpdate) { this.ID=id; this.Name=name; this.IsActive=isActive; this.LastUpdate=lastUpdate; this.Manager=new CustomerEmployee(); this.Manager.Role=Role.Manager; this.Employees=[new CustomerEmployee(),new CustomerEmployee()]; } CustomerEmployee=function() { /// @XmlArrayItem(itemName="CustomerEmployee") this.ID= CustomerEmployee._InstanceNumber++; this.Name="I.P. Freely"; this.EmployeeNumber=42; this.TelephoneNumber="555 12345678"; this.Role=Role.Clerk; } CustomerEmployee._InstanceNumber=0; onload=function() { var myCustomer=new CustomerInfo(1234,“Monkey’s Business”,true,new Date()); var mySerializer=new XmlSerializer(); var myDocument=mySerializer.serialize(myCustomer); alert(myDocument.xml); } |
The code snippet above shows how to serialize an instance of the CustomerInfo class.
To deserialize an XML document into a class invoke the deserialize method of the XmlSerializer instance.
|
var myCustomerInfo=mySerializer.deserialize(myDocument,mySchema); alert(myCustomerInfo.Name); |
The complete source code for the XmlSerializer can be found here. In addition you will need the Annotation and Reflection classes.