Welcome to Part 4 of a multi-part series on JavaScript Object Notation (JSON) support in the Microsoft .NET Framework. In this article, we'll focus on theJavaScriptConverter class from the System.Web.Script.Serialization namespace. You can find the other parts of this series at these locations:

  • Part 1 - An exploration of the DataContractJsonSerializer class
  • Part 2 - An exploration of the JavaScriptSerializer class
  • Part 3 - JSON serialization from WCF (including a REST primer)
  • Part 4 - Using the JavaScriptConverter class to customize JSON serialization

Welcome back. We've been exploring the differences in .NET's two competing JSON serialization classes for a while now. This time, let's dig into ways that we can customize the JavaScriptSerializer to solve some difficult problems. In some circumstances, the services provided by the JavaScriptSerializer for dehydrating and rehydrating of POCOs just isn't sufficient. As discussed in Part 2 of this series, the JavaScriptSerializer has the interesting capability of handling plain .NET classes that aren't marked up with serialization-related metadata in any way. Most of the competing serialization and deserialization classes in the .NET Framework Class Library require some kind of meadata, typically in the form of attributes, to assist the serializer in doing it's job. And there's good reason for this. Sometimes, simply iterating over the properties and fields of an object doesn't provide enough information to properly put it into a format that's suitable for storage or for transmission. For example, look at the following class definition which uses two properties to expose a single DateTime object in different formats:

public class Redundant
{
 private DateTime _ts = DateTime.Now;
 
 public Redundant()
 {
 _ts = DateTime.Now;
 }
 
 public Redundant( DateTime ts )
 {
 _ts = ts;
 }
 
 public string AsRFC1123
 {
 // return the date in RFC 1123 format
 get { return _ts.ToString( "R" ); }
 }
 
 public string AsSortable
 {
 // return the date in a sortable format
 get { return _ts.ToString( "s" ); }
 }
}

When an instance of this class is serialized to JSON using the JavaScriptSerializer, it may look something like this:

{"AsRFC1123":"Sun, 17 May 2009 01:00:00 GMT","AsSortable":"2009-05-17T01:00:00"}

As you can see, the serializer decided to include both string properties, AsRFC1123 and AsSortable which is redundant. There's no need to include the DateTime information twice. However, the JavaScriptSerializer can't know what should and should not be serialized because the definition of the Redundant class has no markup to give it clues about what should be included. The JavaScriptSerializer doesn't depend on attributes as some other .NET serializers do. Instead, if you want to customize the serialization process when using the JavaScriptSerializer, you must provide an instance of a class derived from JavaScriptConverter. For the Redundant class, the following converter can be used to fix the problem:

private class RedundantConverter : JavaScriptConverter
{
 public override IEnumerableType> SupportedTypes
 {
 // register the Redundant type
 get { return new[] { typeof( Redundant ) }; }
 }
 
 public override IDictionarystring, object> Serialize( object obj, JavaScriptSerializer serializer )
 {
 // convert the object obj into a dictionary
 // of name value pairs
 var r = obj as Redundant;
 if (r == null) return null;
 var map = new Dictionarystring, object>();
 map["ts"] = r.AsSortable;
 return map;
 }
 
 public override object Deserialize( IDictionarystring, object> map, Type type, JavaScriptSerializer serializer )
 {
 // read the name value pairs in the
 // dictionary and rehydrate an instance
 DateTime ts;
 if (DateTime.TryParse( map["ts"].ToString(), out ts )) return new Redundant( ts );
 return null;
 }
}

First of all, notice that there are 3 members from the abstract base class that need to be overridden. These are:

  • IEnumerable SupportedTypes { get; }
  • IDictionary Serialize( object obj, JavaScriptSerializer serializer );,>
  • object Deserialize( IDictionary map, Type type, JavaScriptSerializer serializer );,>

The SupportedTypes property tell the serializer which types the converter supports. In this case, only the Redundant class is handled. The Serialize method accepts an instance of the invoking serializer and the object to be converted into JSON. However, the converter doesn't actually produce JSON output. Instead, the Serialize method on the converter must return a dictionary that maps strings, the names of the members, to objects, the actual objects that will be included. In the RedundantConverter class, you can see that were only including one property to be serialized named "ts". Remember, the whole point of attaching custom serialization is because the AsRFC1123 and AsSortable are redundant. We don't need both of them. And since, we'll only be including one of them (AsSortable), we took the opportunity to shorten the name to "ts". This will make the resulting JSON more compact which is almost always a good idea. The Deserialize method works in reverse, accepting a dictionary of name value pairs and constructing a new Redundant object from it. In this case, the Type parameter of the Deserialize method isn't used because this converter only assists with the Redundant type. However, if multiple types were converted by this class, you could include a switch statement to instantiate the appropriate type based on the Type parameter. Here's a bit of code that registers a RedundantConverter and tests the serialization and deserialization processes:

private static void JavaScriptConverterTests()
{
 var r = new Redundant();
 var jser = new JavaScriptSerializer();
 var rs = jser.Serialize( r );
 Console.WriteLine( "Redundant serialized with" + "out conversion support: '{0}'", rs );
 jser.RegisterConverters(
 new JavaScriptConverter[]
 {
 new RedundantConverter()
 }
 );
 rs = jser.Serialize( r );
 Console.WriteLine( "Redundant serialized with " + "conversion support: '{0}'", rs );
 r = jser.DeserializeRedundant>( rs );
 Console.WriteLine("Redundant deserialized with " + "conversion support: AsRFC1123 = '{0}', " + "AsSortable = '{1}'", r.AsRFC1123, r.AsSortable );
}

The output of this code will look something like this:

// Redundant serialized without conversion support:
"{"AsRFC1123":"Sun, 17 May 2009 01:00:00 GMT", "AsSortable":"2009-05-17T01:06:21"}"
 
// Redundant serialized with conversion support:
"{"ts":"2009-05-17T01:00:00"}"
 
// Redundant deserialized with conversion support:
AsRFC1123 = "Sun, 17 May 2009 01:00:00 GMT", AsSortable = "2009-05-17T01:00:00"

In closing, let me make one more key observation. Serialization requires that you can get the property values from an object. Deserialization requires that you can set the property values of an object. But notice that the Redundant class has no mutators (set handlers) on its properties. However, since we're doing custom conversion using the RedundantConverter class, that doesn't matter. The converter's Deserialize method simply uses a custom constructor to inject the DateTime value rather than using properties to do it. Yet another really cool thing that the JavaScriptSerializer can do that baffles the average competing serializer class.

That's all for now. Join me next time as we look at how to consume JSON-encoded content in a Silverlight control. Enjoy!