JSON.net ContractResolver vs. JsonConverter

Great question. I haven’t seen a clear piece of documentation that says when you should prefer to write a custom ContractResolver or a custom JsonConverter to solve a particular type of problem. They really do different things, but there is some overlap between what kinds of problems can be solved by each. I’ve written a fair number of each while answering questions on StackOverflow, so the picture has become a little more clear to me over time. Below is my take on it.

ContractResolver

A contract resolver is always used by Json.Net, and governs serialization / deserialization behavior at a broad level. If there is not a custom resolver provided in the settings, then the DefaultContractResolver is used. The resolver is responsible for determining:

  • what contract each type has (i.e. is it a primitive, array/list, dictionary, dynamic, JObject, plain old object, etc.);
  • what properties are on the type (if any) and what are their names, types and accessibility;
  • what attributes have been applied (e.g. [JsonProperty], [JsonIgnore], [JsonConverter], etc.), and
  • how those attributes should affect the (de)serialization of each property (or class).

Generally speaking, if you want to customize some aspect of serialization or deserialization across a wide range of classes, you will probably need to use a ContractResolver to do it. Here are some examples of things you can customize using a ContractResolver:

  • Change the contract used for a type
    • Serialize all Dictionaries as an Array of Key/Value Pairs
    • Serialize ListItems as a regular object instead of string
  • Change the casing of property names when serializing
    • Use camel case for all property names
    • Camel case all property names except dictionaries
  • Programmatically apply attributes to properties without having to modify the classes (particularly useful if you don’t control the source of said classes)
    • Globally use a JsonConverter on a class without the attribute
    • Remap properties to different names defined at runtime
    • Allow deserializing to public properties with non-public setters
  • Programmatically unapply (ignore) attributes that are applied to certain classes
    • Optionally turn off the JsonIgnore attribute at runtime
    • Make properties which are marked as required (for SOAP) not required for JSON
  • Conditionally serialize properties
    • Ignore read-only properties across all classes
    • Skip serializing properties that throw exceptions
  • Introduce custom attributes and apply some custom behavior based on those attributes
    • Encrypt specially marked string properties in any class
    • Selectively escape HTML in strings during deserialization

JsonConverter

In contrast to a ContractResolver, the focus of a JsonConverter is more narrow: it is really intended to handle serialization or deserialization for a single type or a small subset of related types. Also, it works at a lower level than a resolver does. When a converter is given responsibility for a type, it has complete control over how the JSON is read or written for that type: it directly uses JsonReader and JsonWriter classes to do its job. In other words, it can change the shape of the JSON for that type. At the same time, a converter is decoupled from the “big picture” and does not have access to contextual information such as the parent of the object being (de)serialized or the property attributes that were used with it. Here are some examples of problems you can solve with a JsonConverter:

  • Handle object instantiation issues on deserialization
    • Deserialize to an interface, using information in the JSON to decide which concrete class to instantiate
    • Deserialize JSON that is sometimes a single object and sometimes an array of objects
    • Deserialize JSON that can either be an array or a nested array
    • Skip unwanted items when deserializing from an array of mixed types
    • Deserialize to an object that lacks a default constructor
  • Change how values are formatted or interpretted
    • Serialize decimal values as localized strings
    • Convert decimal.MinValue to an empty string and back (for use with a legacy system)
    • Serialize dates with multiple different formats
    • Ignore UTC offsets when deserializing dates
    • Make Json.Net call ToString() when serializing a type
  • Translate between differing JSON and object structures
    • Deserialize a nested array of mixed values into a list of items
    • Deserialize an array of objects with varying names
    • Serialize/deserialize a custom dictionary with complex keys
    • Serialize a custom IEnumerable collection as a dictionary
    • Flatten a nested JSON structure into a simpler object structure
    • Expand a simple object structure into a more complicated JSON structure
    • Serialize a list of objects as a list of IDs only
    • Deserialize a JSON list of objects containing GUIDs to a list of GUIDs
  • Work around issues (de)serializing specific .NET types
    • Serializing System.Net.IPAddress throws an exception
    • Problems deserializing Microsoft.Xna.Framework.Rectangle

Leave a Comment