Recently I was in charge of replacing the JSON serializers being used in an existing project to squeeze out a little better performance. I looked at a couple libraries for .Net and settled on the almost ubiquitous JSON.Net. This decision felt even more right after it was announced that JSON.Net will become the default serializer for ASP.Net.
A few thoughts were guiding me in this process:
– I am not intimately familiar with JSON.Net
– I didn’t have a lot of time to become familiar with it
– I had lots of code in the project that used the serializer directly
– I need to port several JavaScriptConverters over to equivalent JSON.Net JsonConverters
With this in mind, I choose to abstract out the differences instead of attempt a wholesale rewrite. The first task was to create a set of interfaces that roughly resembled the JavaScriptSerializer and JavaScriptConverter classes.
Next, I made a JSON.Net JsonConverter that will wrap my existing converter classes that now implement IJsonConverter. It handles serializing and deserializing into a Dictionary before passing off to the legacy JavaScriptSerializer code.
Since I needed to get up and running fast, I borrowed the ExpandoObjectConverter class from JSON.Net and tweaked it to build out a Dictionary instead. I could then invoke the IJsonConverter class to take the serialization the rest of the way.
publicclassNewtonsoftJsonConverter:JsonConverter{privateIJsonConverter_converter=null;privateIJsonSerializer_serializer=null;publicNewtonsoftJsonConverter(IJsonSerializerserializer,IJsonConverterconverter){_serializer=serializer;_converter=converter;}publicoverrideboolCanConvert(TypeobjectType){foreach(Typetypein_converter.SupportedTypes)if(type.IsAssignableFrom(objectType))returntrue;returnfalse;}publicoverrideobjectReadJson(JsonReaderreader,TypeobjectType,objectexistingValue,JsonSerializerserializer){objectvalue=ReadValue(reader);if(value==null)returnvalue;if(!(valueisIDictionary<string,object>))thrownewException("Expected dictionary but found a list");value=_converter.Deserialize((IDictionary<string,object>)value,objectType,_serializer);returnvalue;}privateobjectReadValue(JsonReaderreader){while(reader.TokenType==JsonToken.Comment){if(!reader.Read())thrownewException("Unexpected end.");}switch(reader.TokenType){caseJsonToken.StartObject:returnReadObject(reader);caseJsonToken.StartArray:returnReadList(reader);default:if(IsPrimitiveToken(reader.TokenType))returnreader.Value;thrownewException(string.Format("Unexpected token when converting to Dictionary: {0}",reader.TokenType));}}privateboolIsPrimitiveToken(JsonTokentoken){switch(token){caseJsonToken.Integer:caseJsonToken.Float:caseJsonToken.String:caseJsonToken.Boolean:caseJsonToken.Undefined:caseJsonToken.Null:caseJsonToken.Date:caseJsonToken.Bytes:returntrue;default:returnfalse;}}privateobjectReadList(JsonReaderreader){IList<object>list=newList<object>();while(reader.Read()){switch(reader.TokenType){caseJsonToken.Comment:break;default:objectv=ReadValue(reader);list.Add(v);break;caseJsonToken.EndArray:returnlist;}}thrownewException("Unexpected end.");}privateobjectReadObject(JsonReaderreader){IDictionary<string,object>dictionary=newDictionary<string,object>();while(reader.Read()){switch(reader.TokenType){caseJsonToken.PropertyName:stringpropertyName=reader.Value.ToString();if(!reader.Read())thrownewException("Unexpected end.");objectv=ReadValue(reader);dictionary[propertyName]=v;break;caseJsonToken.Comment:break;caseJsonToken.EndObject:returndictionary;}}thrownewException("Unexpected end.");}publicoverridevoidWriteJson(JsonWriterwriter,objectvalue,JsonSerializerserializer){IDictionary<string,object>dictionary=_converter.Serialize(value,_serializer);serializer.Serialize(writer,dictionary);}}
Lastly, I needed to wrap the JSON.Net serializer within my IJsonSerializer interface so that my existing code can get all the benefit of its performance with little changes in my code.
After changing a few import statements I was ready to go. Testing showed a particular server query that had been taking close to 2 minutes to return would now return in under 30 seconds consistently. This was certainly a huge win considering it required a very small set of changes to the project’s code.
Overall the process went smoothly and I am very happy with the result. If you would like to use this code yourself, it is available as a Gist on GitHub.