Does Json.NET cache types' serialization information?

c# expression-trees json json.net serialization

Question

When it comes to object serialization in the.NET environment, it often involves checking the object's fields and attributes in real time. Reflection is often sluggish for this task, which is undesirable when working with huge groups of objects. The alternative is to use IL emit or create expression trees, both of which provide a large performance advantage over reflection. And when it comes to serialization, most contemporary libraries opt for the latter. The effort spent creating and generating IL during runtime, however, is only repaid if this data is cached and reused for objects of the same type.

Which of the methods mentioned above is utilized when using Json.NET, and if the latter is used, if caching is used, is unclear to me.

For instance, when I:

JsonConvert.SerializeObject(new Foo { value = 1 });

Does Json.NET create the member access information and cache for Foo to subsequently reuse it?

1
26
11/6/2015 1:02:38 AM

Accepted Answer

Inside of its IContractResolver, DefaultContractResolver, and CamelCasePropertyNamesContractResolver classes, Json.NET stores type serialization information. Without a specific contract resolver specified, this data is cached and reused.

For DefaultContractResolver When the application doesn't provide its own contract resolver, Json.NET utilizes a global static instance that is kept up internally.CamelCasePropertyNamesContractResolver , on the other hand, keeps common static tables for all instances. (I think the discrepancy is caused by legacy problems; see here for more information.)

Sharing across threads should not be an issue since both of these kinds are intended to be completely thread-safe.

Type information will only be cached and reused if you cache and reuse the contract resolver instance itself if you decide to create your own contract resolver. Newtonsoft recommends therefore:

For performance you should create a contract resolver once and reuse instances when possible. Resolving contracts is slow and implementations of IContractResolver typically cache contracts.

An approach to ensure caching in a subclass ofDefaultContractResolver is to give it a global static instance and make its constructor protected or private. (Obviously, this is only suitable if the resolver is "stateless" and consistently returns the same results.) Inspire by this inquiry, for instance, here is a Pascal scenario to illustrate the contract resolver:

public class PascalCaseToUnderscoreContractResolver : DefaultContractResolver
{
    protected PascalCaseToUnderscoreContractResolver() : base() { }

    // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
    // http://www.newtonsoft.com/json/help/html/ContractResolver.htm
    // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
    // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
    static PascalCaseToUnderscoreContractResolver instance;

    // Using an explicit static constructor enables lazy initialization.
    static PascalCaseToUnderscoreContractResolver() { instance = new PascalCaseToUnderscoreContractResolver(); }

    public static PascalCaseToUnderscoreContractResolver Instance { get { return instance; } }

    static string PascalCaseToUnderscore(string name)
    {
        if (name == null || name.Length < 1)
            return name;
        var sb = new StringBuilder(name);
        for (int i = 0; i < sb.Length; i++)
        {
            var ch = char.ToLowerInvariant(sb[i]);
            if (ch != sb[i])
            {
                if (i > 0) // Handle flag delimiters
                {
                    sb.Insert(i, '_');
                    i++;
                }
                sb[i] = ch;
            }
        }
        return sb.ToString();
    }

    protected override string ResolvePropertyName(string propertyName)
    {
        return PascalCaseToUnderscore(propertyName);
    }
}

Using which you would do:

var json = JsonConvert.SerializeObject(someObject, new JsonSerializerSettings { ContractResolver = PascalCaseToUnderscoreContractResolver.Instance });

(N.B. : With the advent of SnakeCaseNamingStrategy, the value of this specific resolver has been diminished; it has been left here merely as an example.)

For whatever reason you need to reduce the amount of memory that cached contracts permanently use, you may create your own local instance ofDefaultContractResolver (or a custom subclass), serialize using it, then immediately erase any references to it, for example:

public class JsonExtensions
{
    public static string SerializeObjectNoCache<T>(T obj, JsonSerializerSettings settings = null)
    {
        settings = settings ?? new JsonSerializerSettings();
        if (settings.ContractResolver == null)
            // To reduce memory footprint, do not cache contract information in the global contract resolver.
            settings.ContractResolver = new DefaultContractResolver();
        return JsonConvert.SerializeObject(obj, settings);
    }
}

Most cached contract memory will ultimately be cleaned out by trash collection. Naturally, by doing so, Serialization performance can be severely impacted..

See Newtonsoft's Reuse Contract Resolver performance recommendations for further details.

28
3/13/2019 7:47:40 PM


Related Questions





Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow