Introduction
In this article, we’ll examine the ASP.NET AJAX serialization process. We will examine the server-side methods and client-side methods which serialize and deserialize objects.
What is JavaScript Object Notation (JSON)?
JavaScript Object Notation or JSON provides a more efficient means of transferring data than previously found with XML and SOAP. JSON is a means of serializing an object into a lightweight string that can be sent across the wire. For example, consider the following C# class:
public class User
{
string FirstName;
string LastName;
}
Now, consider an instance of the object as follows:
User usr = new User();
usr.FirstName = "John"
usr.LastName = "Smith";
When serialized into JSON formatted text, we’ll be left with the following:
{ FirstName : "John"
LastName : "Smith" }
From within JavaScript, we can deserialize the JSON text string into an object using the JavaScript supplied method eval()
or the class Sys.Serialization.JavaScriptSerializer
. Using the AJAX class, we can also serialize a JavaScript object to be sent to the server. We can then use the server-side object JavaScriptSerializer
to deserialize the JSON text string object, as we will discover in this article.
Getting Dirty
The object responsible for serialization/deserialization is JavaScriptSerializer
. The relevant members of this object can be seen below.
public class JavaScriptSerializer
{
internal const int DefaultMaxJsonLength = 0x200000;
internal const int DefaultRecursionLimit = 100;
internal const string ServerTypeFieldName = "__type";
static JavaScriptSerializer();
public JavaScriptSerializer();
public JavaScriptSerializer(JavaScriptTypeResolver resolver);
public T ConvertToType<t />(object obj);
public T Deserialize<t />(string input);
public object DeserializeObject(string input);
public void RegisterConverters(IEnumerable<javascriptconverter /> converters);
public string Serialize(object obj);
public void Serialize(object obj, StringBuilder output);
public int MaxJsonLength { get; set; }
public int RecursionLimit { get; set; }
internal JavaScriptTypeResolver TypeResolver { get; }
}
First off, the serialization process will fail if the number of nested objects is greater than that defined within the RecursionLimit
property. That said; note the default recursion limit of 100. The serialization process will also fail if the length of the serialized text is greater than that of the MaxJsonLength
property; again, note the default value of 2,097,152 base ten.
The object is serialized into a StringBuilder
object; after the serialization completes, the string representation will be returned. The majority of the action happens within the private SerializeValue()
method. Before we examine this method, it’s worth noting that the JavaScriptSerializer
object may make use of JavaScriptTypeResolver
, which is used to resolve a type from a string and vice versa, which is important when custom objects are serialized. The __type
attribute will be included within the JSON serialized text, hence indicating the object type. The client will then deserialize the JSON text into an object and populate the indicated properties with the values found within the JSON serialized text.
The JavaScriptTypeResolver
object includes two public methods, one to resolve the type to string, and the other from string to type. The class prototype is as follows:
public abstract class JavaScriptTypeResolver
{
protected JavaScriptTypeResolver();
public abstract Type ResolveType(string id);
public abstract string ResolveTypeId(Type type);
}
The JavaScriptTypeResolver
class is a base abstract class, and should be implemented by another object which can be used to resolve the type to string and vice versa. The object which we may use is the SimpleTypeResolver
, and implements the above methods exactly as expected – making use of the System.Type
object to resolve either a string representation into a valid Type
object or a valid Type
object into a string representation. See below.
public override Type ResolveType(string id)
{
return Type.GetType(id);
}
public override string ResolveTypeId(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
return type.AssemblyQualifiedName;
}
Finally, the JavaScriptSerializer
object may also make use of a JavaScriptConverter
object as the internal serialization process is not capable of serializing all available data types, in which case, an object can inherit from the base abstract JavaScriptConverter
object and implement the serialization/deseralization process for specific data types. A converter object may be registered with the JavaScriptSerializer
using the RegisterConverters()
method which stores all converter objects in a Dictionary
object, as multiple converters may be registered for different data types. The Dictionary
object is defined as follows:
this._converters = new Dictionary<type, >();
That is, the Type
of the object is the key, while the value is an instance of a JavaScriptConverter
object to use for the associated data type.
The SerializeValue()
method will check if a custom converter object exists for the data type which should be serialized; if one exists, it will make use of the custom converter object to serialize/deserialize the object; otherwise, an internal serialization method will be used, which is also determined based on the object's data type. The method SerializeValueInternal()
will be invoked and will serialize the data types as described in the table below:
Table 1 – Serialization of data types
Data type | Serialized as |
null or DBNull | "null " |
string | Quoted string |
char | If ‘\0’, "null". Else serialized as a quoted string. |
bool | "true " or "false " |
DateTime | "\/Date (ticks since 12:00AM 1970/01/01 UTC)\/" |
Guid | “string representation”: sb.Append("\"").Append(guid.ToString()).Append("\""); |
Uri | sb.Append("\"").Append(uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped)).Append("\""); |
double | sb.Append(((double) o).ToString("r", CultureInfo.InvariantCulture)); |
float | sb.Append(((float) o).ToString("r", CultureInfo.InvariantCulture)); |
primitive or decimal | IConvertible convertible = o as IConvertible;
sb.Append(convertible.ToString(CultureInfo.InvariantCulture));
|
Enum | sb.Append((int) o); |
IDictionary | JSON text, for example.
{"Key1":Value1,"Key2":Value2 ... }
|
IEnumerable | JSON text, for example.
{"Key1":Value1,"Key2":Value2 ... }
|
A custom object is serialized similar to an IDictionary
, with a few differences. If a JavaScriptTypeResolver
object has been defined, the object type will be converted to a string, and the object definition will include the string literal __type
followed by the string representation of the object data type. The remaining object properties and fields are obtained by looping the fields and properties obtained from the Type
object. All fields and properties which have been defined as public
and do not include the metadata ScriptIgnoreAttribute
property will be included within the JSON object representation of the object.
Let us now examine the serialization process by way of an example object. Considering the objects defined below.
public class Customer
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
private string _email;
public string EmailAddress
{
get { return _email; }
set { _email = value; }
}
private Phone _phoneNumber;
public Phone PhoneNumbers
{
get { return _phoneNumber; }
set { _phoneNumber = value; }
}
}
public class Phone
{
private string _homePhone;
public string HomePhone
{
get { return _homePhone; }
set { _homePhone = value; }
}
private string _workPhone;
public string WorkPhone
{
get { return _workPhone; }
set { _workPhone = value; }
}
}
If this object were returned from a Web Service method, the object would be serialized automatically from within the InvokeMethod()
of the RestHandler
. However, in the sample case above, we’re making use of these objects from within our Page.Page_Load()
method, hence we should create the objects and serialize them automatically using the JavaScriptSerializer
object previously examined. Consider the following code to serialize an object:
JavaScriptSerializer jsSerializer =
new JavaScriptSerializer(new SimpleTypeResolver());
Customer cust = new Customer();
cust.FirstName = "Joe";
cust.EmailAddress = "jknown@domain.com";
cust.PhoneNumbers = new Phone();
cust.PhoneNumbers.HomePhone = "888-888-8888";
string serializedText = jsSerializer.Serialize(cust);
Notice that the JavaScriptSerializer
object has been initialized with the SimpleTypeResolver
which, as you know, will be invoked to evaluate the type of the serialized object to a string. The serialized JSON text is shown below:
{"__type":"Customer, App_Web_plrzlwbj,
Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null","FirstName":"Joe","LastName":null,
"EmailAddress":jknown@domain.com,
"PhoneNumbers":{"__type":"Phone, App_Web_plrzlwbj, Version=0.0.0.0,
Culture=neutral, PublicKeyToken=null",
"HomePhone":"888-888-8888","WorkPhone":null}}
Notice that the PhoneNumbers
property is of the custom type Phone
, hence when serialized, the value of the PhoneNumbers
property will be a JSON object, which is the serialized version of the Phone
object. Again, notice the type is identified, which is used when deserializing so that the correct object may be created and populated. That said, let us now examine the deserialization process.
The deserialization process is performed using the JavaScriptObjectDeserializer
object, which when an instance is created, the JSON text string should be supplied as a parameter to the constructor. After an instance has been created, we may invoke the DeserializeInternal()
method. Here, the JSON serialized string will be parsed, and the appropriate objects matching the serialized version will be created and populated.
To deserialize a JSON string, we may invoke the Deserialize()
method of the JavaScriptSerializer
object. The return value being an instance of the initial object with the properties defined to the previously specified values. Consider the code below:
Customer cust = jsSerializer.Deserialize<customer>(serializedText);
Conclusion
That’s all there really is to it. We should now have a basic understanding of how the AJAX JSON serialization/deserialization process works, and how we can leverage JSON serialization for use within our AJAX code. The process is really quite simple.