The best option to avoid constructor calls on deserialization is to create special contract resolver that overrides creator function for all classes without constructor marked with JsonConstructor attribute. This way you still can force JSON.NET to call constructor if you really need it, but all other classes will be created much like in standard DataContract serializers in .NET. Here is the code:
/// <summary>
/// Special contract resolver to create objects bypassing constructor call.
/// </summary>
public class NoConstructorCreationContractResolver : DefaultContractResolver
{
/// <summary>
/// Creates a <see cref="T:Newtonsoft.Json.Serialization.JsonObjectContract"/> for the given type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// A <see cref="T:Newtonsoft.Json.Serialization.JsonObjectContract"/> for the given type.
/// </returns>
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
// prepare contract using default resolver
var objectContract = base.CreateObjectContract(objectType);
// if type has constructor marked with JsonConstructor attribute or can't be instantiated, return default contract
if (objectContract.OverrideCreator != null || objectContract.CreatedType.IsInterface || objectContract.CreatedType.IsAbstract)
return objectContract;
// prepare function to check that specified constructor parameter corresponds to non writable property on a type
Func<JsonProperty, bool> isParameterForNonWritableProperty =
parameter =>
{
var propertyForParameter =
objectContract.Properties.FirstOrDefault(property => property.PropertyName == parameter.PropertyName);
if (propertyForParameter == null)
return false;
return !propertyForParameter.Writable;
};
// if type has parameterized constructor and any of constructor parameters corresponds to non writable property, return default contract
// this is needed to handle special cases for types that can be initialized only via constructor, i.e. Tuple<>
if (objectContract.CreatorParameters.Any(parameter => isParameterForNonWritableProperty(parameter)))
return objectContract;
// override default creation method to create object without constructor call
objectContract.DefaultCreatorNonPublic = false;
objectContract.DefaultCreator = () => FormatterServices.GetSafeUninitializedObject(objectContract.CreatedType);
return objectContract;
}
}
All you need is simply set this contract resolver in serializer settings before deserialization.
Update on readonly fields and get-only properties
A readonly field can be deserialized if it has a JsonProperty attribute applied.
A get-only property can also be deserialized if it's backing field has a JsonProperty attribute applied.
Since C# 7.3 it's possible to apply attributes to compiler-generated backing fields of auto-properties.
Leveraging this we could do something like this:
public class SampleClass
{
[JsonProperty("Field1")]
private readonly string _field1 = "Field1Value";
[JsonProperty("Property1")]
private string _property1BackingField = "Property1Value";
[JsonIgnore]
public string Property1 => _property1BackingField;
[field: JsonProperty("Property2")]
[JsonIgnore]
public string Property2 { get; } = "Property2Value";
}
const string json = "{\"Field1\":\"NEW-Field1Value\",\"Property1\":\"NEW-Property1Value\",\"Property2\":\"NEW-Property2Value\"}";
var serializerSettings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
// this settings is needed to work with compiler-generated backing fields
SerializeCompilerGeneratedMembers = true
}
};
var deserializedSample = JsonConvert.DeserializeObject<SampleClass>(json, serializerSettings);
If you need to do this in older C# version or have no control over attributes, you could override CreateProperty() method of DefaultContractResolver with something like this:
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var jsonProperty = base.CreateProperty(member, memberSerialization);
if (jsonProperty.Writable == false)
{
// this uses compiler implementation details and may not work for all cases
// better to use smarter approach like in BackingFieldResolver from Mono.Reflection library
var fieldInfo = jsonProperty.DeclaringType.GetField($"<{jsonProperty.PropertyName}>k__BackingField",
BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
jsonProperty.ValueProvider = new ReflectionValueProvider(fieldInfo);
jsonProperty.Writable = true;
}
}
return jsonProperty;
}