Having rummaged in Newtonsoft.Json sources, I can tell you the algorithm for creating an instance of the object that is used there. And yes, the constructor is almost always called (*). The only question is, "which?" Here is a colorful version of the answer:

TL DR First of all, Newtonsoft.Json creates a JsonContract
the type you are about to deserialize. This is an abstract class. And it has different implementations for dictionaries, arrays, objects, etc. In your case, a JsonObjectContract
will be created. The contract contains various metadata about the deserialized type. The most interesting for us are:
IsInstantiable
- determines if the deserialized type is available (see below)Properties
is a set of properties of an object.DefaultCreator
- The DefaultCreator
creation method used to create the Func<object>
DefaultCreatorNonPublic
- Determines whether the default constructor is open.OverrideCreator
- non-default creator used if <<28> is applied to the constructor of the objectParametrizedCreator
- the creator that calls the parameterized constructor is used if we have no default or overriding creatorsCreatorParameters
- a collection of properties that are used to override the creator or creator of the parameterizationMemberSerialization
- This value determines how properties and fields are serialized. The default value is OptOut
- that is, all public members are serialized. If you want to exclude some, you should use the JsonIgnore
attribute. But there is also the Fields
option, which says that all public and private fields should be serialized. There are several options for enabling this option. But by default it was disabled.
Some of this metadata can be obtained by reflecting type metadata. For instance. IsInstantiable
computed by checking if the deserialized type is not abstract, not an interface. Some metadata is added by DefaultContractResolver
. In particular, it determines the method of constructing an object. In pseudo code:
if (contract.IsInstantiable) { if (type has default constructor or its a value type) { contract.DefaultCreator = get default (parameterless) constructor; contract.DefaultCreatorNonPublic = check if default constructor public } if (we have constructor marked with JsonConstructorAttribute) { contract.OverrideCreator = constructor marked with attribute contract.CreatorParameters = get properties which match constructor parameters } else if (contract.MemberSerialization == MemberSerialization.Fields) { // only if the upplication if fully trusted contract.DefaultCreator = FormatterServices.GetUninitializedObject } else if (contract.DefaultCreator == null || contract.DefaultCreatorNonPublic) { if (we have one public constructor with parameters) { contract.ParametrizedCreator = constructor with parameters; contract.CreatorParameters = get properties which match ctor parameters } } }
So, as you can see, prioirty is sent to the constructor marked with the JsonConstructorAttribute
attribute. You will also get an error if there is more than one such constructor.
(*) The following is the only case where an object can be created without calling the constructor. For instance. if you mark a class with the [JsonObject(MemberSerialization = MemberSerialization.Fields)]
attribute for serializing private fields.
Then we check if we have a standard constructor without parameters, which is not private. If so, then we go for another constructor that has parameters and should be publicly available. If there is more than one such constructor, you will also get an error.
And the last thing to note is CraeatorParameters
. Newtonsoft.Json uses reflection to get the constructor parameters, and then tries to find the closest match by the name of these constructor parameters for the properties of the object. It also checks the type of property and parameters for compliance. If no match is found, the default value will be passed to this parameterized constructor.