Joey Robichaud

code and thoughts

From JavaScriptSerializer to JSON.Net

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.

1
2
3
4
5
6
7
8
public interface IJsonSerializer
{
    void RegisterConverters(IEnumerable<IJsonConverter> converters);
    T ConvertToType<T>(IDictionary<string, object> dictionary);
    T Deserialize<T>(string input);
    string Serialize(object obj);
    string DefaultSerialize(object obj);
}
1
2
3
4
5
6
public interface IJsonConverter
{
    object Deserialize(IDictionary<string, object> dictionary, Type type, IJsonSerializer serializer);
    IDictionary<string, object> Serialize(object obj, IJsonSerializer serializer);
    IEnumerable<Type> SupportedTypes { get; }
}

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
public class NewtonsoftJsonConverter : JsonConverter
{
    private IJsonConverter _converter = null;
    private IJsonSerializer _serializer = null;

    public NewtonsoftJsonConverter(IJsonSerializer serializer, IJsonConverter converter)
    {
        _serializer = serializer;
        _converter = converter;
    }

    public override bool CanConvert(Type objectType)
    {
        foreach (Type type in _converter.SupportedTypes)
            if (type.IsAssignableFrom(objectType))
                return true;

        return false;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        object value = ReadValue(reader);

        if (value == null)
            return value;

        if (!(value is IDictionary<string, object>))
            throw new Exception("Expected dictionary but found a list");

        value = _converter.Deserialize((IDictionary<string, object>)value, objectType, _serializer);

        return value;
    }

    private object ReadValue(JsonReader reader)
    {
        while (reader.TokenType == JsonToken.Comment)
        {
            if (!reader.Read())
                throw new Exception("Unexpected end.");
        }

        switch (reader.TokenType)
        {
            case JsonToken.StartObject:
                return ReadObject(reader);
            case JsonToken.StartArray:
                return ReadList(reader);
            default:
                if (IsPrimitiveToken(reader.TokenType))
                    return reader.Value;

                throw new Exception(string.Format("Unexpected token when converting to Dictionary: {0}", reader.TokenType));
        }
    }

    private bool IsPrimitiveToken(JsonToken token)
    {
        switch (token)
        {
            case JsonToken.Integer:
            case JsonToken.Float:
            case JsonToken.String:
            case JsonToken.Boolean:
            case JsonToken.Undefined:
            case JsonToken.Null:
            case JsonToken.Date:
            case JsonToken.Bytes:
                return true;
            default:
                return false;
        }
    }

    private object ReadList(JsonReader reader)
    {
        IList<object> list = new List<object>();

        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.Comment:
                    break;
                default:
                    object v = ReadValue(reader);

                    list.Add(v);
                    break;
                case JsonToken.EndArray:
                    return list;
            }
        }

        throw new Exception("Unexpected end.");
    }

    private object ReadObject(JsonReader reader)
    {
        IDictionary<string, object> dictionary = new Dictionary<string, object>();

        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.PropertyName:
                    string propertyName = reader.Value.ToString();

                    if (!reader.Read())
                        throw new Exception("Unexpected end.");

                    object v = ReadValue(reader);

                    dictionary[propertyName] = v;
                    break;
                case JsonToken.Comment:
                    break;
                case JsonToken.EndObject:
                    return dictionary;
            }
        }

        throw new Exception("Unexpected end.");
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class NewtonsoftJsonSerializer : IJsonSerializer
{
    private JsonSerializerSettings _settings = new JsonSerializerSettings();

    public void RegisterConverters(IEnumerable<IJsonConverter> converters)
    {
        foreach (IJsonConverter converter in converters)
            _settings.Converters.Add(new NewtonsoftJsonConverter(this, converter));
    }

    public T ConvertToType<T>(IDictionary<string, object> dictionary)
    {
        string intermediate = JsonConvert.SerializeObject(dictionary);
        T value = JsonConvert.DeserializeObject<T>(intermediate);
        return value;
    }

    public T Deserialize<T>(string input)
    {
        return JsonConvert.DeserializeObject<T>(input, _settings);
    }

    public string Serialize(object obj)
    {
        return JsonConvert.SerializeObject(obj, Formatting.None, _settings);
    }

    public string DefaultSerialize(object obj)
    {
        return JsonConvert.SerializeObject(obj);
    }
}

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.

This post originally appeared on The DevStop.