Friday, October 17, 2014

Benchmarking .NET JSON Serializers

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace PlayJsonSerializers
{
    class Program
    {
        static void Main(string[] args)
        {
            Func<object,string> netJson = o => NetJSON.NetJSON.Serialize(o);
                
            Func<object,string> jil = o => Jil.JSON.Serialize(o);
            
            Func<object, string> ssText = o => ServiceStack.Text.JsonSerializer.SerializeToString(o);

            // ASP.NET Web API default JSON serializer
            Func<object, string> jsonNet = o => Newtonsoft.Json.JsonConvert.SerializeObject(o);

            var aspNetWebMvcDefaultJsonSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
            Func<object, string> javaScriptSerializer = o => aspNetWebMvcDefaultJsonSerializer.Serialize(o);
            // To replace ASP.NET MVC JSON serializer: http://www.ienablemuch.com/2014/10/aspnet-mvc-return-json-without-using-jsonresult.html


            var jsSerializers = new Dictionary<string, Func<object, string>>()
            {
                { "NetJSON", netJson },                
                { "Jil", jil },
                { "ServiceStack.Text", jil },
                { "Json.NET", jsonNet },
                { "ASP.NET MVC Default", javaScriptSerializer}
            };


            Action<string,object> benchmark = (description,o) =>
            {
                const string netJsonReplacedWithJsonNet = "NetJSON Replaced with Json.NET";

                var fallbackJsonSerializer = jsonNet;

                bool isAnonymousType = o.GetType().IsAnonymousType();

                Console.WriteLine(description);
               
                Console.WriteLine("Serialization");
                foreach (var item in jsSerializers)
                {
                    if (isAnonymousType && item.Key == "NetJSON")
                        fallbackJsonSerializer(o).Show(netJsonReplacedWithJsonNet);
                    else
                        item.Value(o).Show(item.Key);
                    
                }

                Console.WriteLine("Speed");
                foreach (var item in jsSerializers)
                {
                    if (isAnonymousType && item.Key == "NetJSON")
                        ShowTime(netJsonReplacedWithJsonNet, delegate { for (int i = 0; i < 20000; i++) fallbackJsonSerializer(o); });
                    else
                        ShowTime(item.Key, delegate { for (int i = 0; i < 20000; i++) item.Value(o); });
                }

                Console.WriteLine(new string('-',80));
            };


            benchmark("Object with complete properties", 
                new Person { PersonId = 123456789, FirstName = "Linus", MiddleName = "Great", LastName = "Snoopy" });

            benchmark("Object with null properties", 
                new Person { PersonId = 123456789, FirstName = "Linus", LastName = "Snoopy" });


            benchmark("Object with inheritance",
                new Employee { PersonId = 123456789, FirstName = "Linus", MiddleName = "Great", LastName = "Snoopy", IsActive = true });

                        
            benchmark("Anonymous object with complete properties", 
                new { PersonId = 123456789, FirstName = "Linus", MiddleName = "Great", LastName = "Snoopy", IsActive = true });


            benchmark("Anonymous object. Property with inheritance",
                new { Greet = "Hello", Employee = new Employee { PersonId = 123456789, FirstName = "Linus", MiddleName = "Great", LastName = "Snoopy", IsActive = true } });
                            
        }


        static void ShowTime(string jsSerializer, Action task)
        {
            var sw = Stopwatch.StartNew();

            task();

            sw.Stop();
            var milliseconds = (long)sw.ElapsedMilliseconds;
            Console.WriteLine("{0, 30}: {1}", jsSerializer, milliseconds);
        }
    }

    public class Person
    {
        public int      PersonId    { get; set; }
        public string   FirstName   { get; set; }
        public string   MiddleName  { get; set; }
        public string   LastName    { get; set; }
    }

    public class Employee : Person
    {
        public bool IsActive { get; set; }
    }

    static class StringExtension
    {
        internal static void Show(this string s, string description = "")
        {
            Console.WriteLine("{0, 30}: {1}", description, s);
        }
    }

    
    static class TypeExtension
    {
        // http://www.jefclaes.be/2011/05/checking-for-anonymous-types.html
        static internal bool IsAnonymousType(this Type type)
        {
            Debug.Assert(type != null, "Type should not be null");

            // HACK: The only way to detect anonymous types right now.
            return Attribute.IsDefined(type, typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false)
                       && type.IsGenericType && type.Name.Contains("AnonymousType")
                       && (type.Name.StartsWith("<>", StringComparison.OrdinalIgnoreCase) ||
                           type.Name.StartsWith("VB$", StringComparison.OrdinalIgnoreCase))
                       && (type.Attributes & System.Reflection.TypeAttributes.NotPublic) == System.Reflection.TypeAttributes.NotPublic;
        }
    }
}


Results:
Object with complete properties
Serialization
                       NetJSON: {"PersonId":123456789,"FirstName":"Linus","MiddleName":"Great","LastName":"Snoopy"}
                           Jil: {"PersonId":123456789,"LastName":"Snoopy","MiddleName":"Great","FirstName":"Linus"}
             ServiceStack.Text: {"PersonId":123456789,"LastName":"Snoopy","MiddleName":"Great","FirstName":"Linus"}
                      Json.NET: {"PersonId":123456789,"FirstName":"Linus","MiddleName":"Great","LastName":"Snoopy"}
           ASP.NET MVC Default: {"PersonId":123456789,"FirstName":"Linus","MiddleName":"Great","LastName":"Snoopy"}
Speed
                       NetJSON: 38
                           Jil: 77
             ServiceStack.Text: 89
                      Json.NET: 119
           ASP.NET MVC Default: 1022
--------------------------------------------------------------------------------
Object with null properties
Serialization
                       NetJSON: {"PersonId":123456789,"FirstName":"Linus","LastName":"Snoopy"}
                           Jil: {"PersonId":123456789,"LastName":"Snoopy","MiddleName":null,"FirstName":"Linus"}
             ServiceStack.Text: {"PersonId":123456789,"LastName":"Snoopy","MiddleName":null,"FirstName":"Linus"}
                      Json.NET: {"PersonId":123456789,"FirstName":"Linus","MiddleName":null,"LastName":"Snoopy"}
           ASP.NET MVC Default: {"PersonId":123456789,"FirstName":"Linus","MiddleName":null,"LastName":"Snoopy"}
Speed
                       NetJSON: 28
                           Jil: 84
             ServiceStack.Text: 75
                      Json.NET: 131
           ASP.NET MVC Default: 1020
--------------------------------------------------------------------------------
Object with inheritance
Serialization
                       NetJSON: {"IsActive":true,"PersonId":123456789,"FirstName":"Linus","MiddleName":"Great","LastName":"Snoopy"}
                           Jil: {"IsActive":true}
             ServiceStack.Text: {"IsActive":true}
                      Json.NET: {"IsActive":true,"PersonId":123456789,"FirstName":"Linus","MiddleName":"Great","LastName":"Snoopy"}
           ASP.NET MVC Default: {"IsActive":true,"PersonId":123456789,"FirstName":"Linus","MiddleName":"Great","LastName":"Snoopy"}
Speed
                       NetJSON: 37
                           Jil: 53
             ServiceStack.Text: 48
                      Json.NET: 148
           ASP.NET MVC Default: 1307
--------------------------------------------------------------------------------
Anonymous object with complete properties
Serialization
NetJSON Replaced with Json.NET: {"PersonId":123456789,"FirstName":"Linus","MiddleName":"Great","LastName":"Snoopy","IsActive":true}
                           Jil: {"IsActive":true,"PersonId":123456789,"LastName":"Snoopy","MiddleName":"Great","FirstName":"Linus"}
             ServiceStack.Text: {"IsActive":true,"PersonId":123456789,"LastName":"Snoopy","MiddleName":"Great","FirstName":"Linus"}
                      Json.NET: {"PersonId":123456789,"FirstName":"Linus","MiddleName":"Great","LastName":"Snoopy","IsActive":true}
           ASP.NET MVC Default: {"PersonId":123456789,"FirstName":"Linus","MiddleName":"Great","LastName":"Snoopy","IsActive":true}
Speed
NetJSON Replaced with Json.NET: 156
                           Jil: 94
             ServiceStack.Text: 111
                      Json.NET: 171
           ASP.NET MVC Default: 1702
--------------------------------------------------------------------------------
Anonymous object. Property with inheritance
Serialization
NetJSON Replaced with Json.NET: {"Greet":"Hello","Employee":{"IsActive":true,"PersonId":123456789,"FirstName":"Linus","MiddleName":"Great","LastName":"Snoopy"}}
                           Jil: {"Employee":{"IsActive":true},"Greet":"Hello"}
             ServiceStack.Text: {"Employee":{"IsActive":true},"Greet":"Hello"}
                      Json.NET: {"Greet":"Hello","Employee":{"IsActive":true,"PersonId":123456789,"FirstName":"Linus","MiddleName":"Great","LastName":"Snoopy"}}
           ASP.NET MVC Default: {"Greet":"Hello","Employee":{"IsActive":true,"PersonId":123456789,"FirstName":"Linus","MiddleName":"Great","LastName":"Snoopy"}}
Speed
NetJSON Replaced with Json.NET: 222
                           Jil: 95
             ServiceStack.Text: 103
                      Json.NET: 211
           ASP.NET MVC Default: 2200
--------------------------------------------------------------------------------


Summary:
* NetJSON is the fastest
* Jil and ServiceStack can't serialize inherited classes
* NetJSON doesn't serialize null propeties, good optimization

As for replacing NetJSON with Json.NET, NetJSON can't serialize anonymous objects yet



Happy Coding!

No comments:

Post a Comment