JSON.net: how to deserialize without using the default constructor?

C#Jsonjson.net

C# Problem Overview


I have a class that has a default constructor and also an overloaded constructor that takes in a set of parameters. These parameters match to fields on the object and are assigned on construction. At this point i need the default constructor for other purposes so i would like to keep it if i can.

My Problem: If I remove the default constructor and pass in the JSON string, the object deserializes correctly and passes in the constructor parameters without any issues. I end up getting back the object populated the way I would expect. However, as soon as I add the default constructor into the object, when i call JsonConvert.DeserializeObject<Result>(jsontext) the properties are no longer populated.

At this point I have tried adding new JsonSerializerSettings(){CheckAdditionalContent = true} to the deserialization call. That did not do anything.

Another note: the constructor parameters do match the names of the fields exactly except that the parameters are start with a lowercase letter. I wouldn't think this would matter since, like i mentioned, the deserialization works fine with no default constructor.

Here is a sample of my constructors:

public Result() { }

public Result(int? code, string format, Dictionary<string, string> details = null)
{
    Code = code ?? ERROR_CODE;
    Format = format;

    if (details == null)
        Details = new Dictionary<string, string>();
    else
        Details = details;
}

C# Solutions


Solution 1 - C#

Json.Net prefers to use the default (parameterless) constructor on an object if there is one. If there are multiple constructors and you want Json.Net to use a non-default one, then you can add the [JsonConstructor] attribute to the constructor that you want Json.Net to call.

[JsonConstructor]
public Result(int? code, string format, Dictionary<string, string> details = null)
{
    ...
}

It is important that the constructor parameter names match the corresponding property names of the JSON object (ignoring case) for this to work correctly. You do not necessarily have to have a constructor parameter for every property of the object, however. For those JSON object properties that are not covered by the constructor parameters, Json.Net will try to use the public property accessors (or properties/fields marked with [JsonProperty]) to populate the object after constructing it.

If you do not want to add attributes to your class or don't otherwise control the source code for the class you are trying to deserialize, then another alternative is to create a custom JsonConverter to instantiate and populate your object. For example:

class ResultConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Result));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Load the JSON for the Result into a JObject
        JObject jo = JObject.Load(reader);
        
        // Read the properties which will be used as constructor parameters
        int? code = (int?)jo["Code"];
        string format = (string)jo["Format"];
        
        // Construct the Result object using the non-default constructor
        Result result = new Result(code, format);
        
        // (If anything else needs to be populated on the result object, do that here)
        
        // Return the result
        return result;
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Then, add the converter to your serializer settings, and use the settings when you deserialize:

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new ResultConverter());
Result result = JsonConvert.DeserializeObject<Result>(jsontext, settings);

Solution 2 - C#

A bit late and not exactly suited here, but I'm gonna add my solution here, because my question had been closed as a duplicate of this one, and because this solution is completely different.

I needed a general way to instruct Json.NET to prefer the most specific constructor for a user defined struct type, so I can omit the JsonConstructor attributes which would add a dependency to the project where each such struct is defined.

I've reverse engineered a bit and implemented a custom contract resolver where I've overridden the CreateObjectContract method to add my custom creation logic.

public class CustomContractResolver : DefaultContractResolver {
    
    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var c = base.CreateObjectContract(objectType);
        if (!IsCustomStruct(objectType)) return c;
    
        IList<ConstructorInfo> list = objectType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).OrderBy(e => e.GetParameters().Length).ToList();
        var mostSpecific = list.LastOrDefault();
        if (mostSpecific != null)
        {
            c.OverrideCreator = CreateParameterizedConstructor(mostSpecific);
            c.CreatorParameters.AddRange(CreateConstructorParameters(mostSpecific, c.Properties));
        }
    
        return c;
    }
    
    protected virtual bool IsCustomStruct(Type objectType)
    {
        return objectType.IsValueType && !objectType.IsPrimitive && !objectType.IsEnum && !objectType.Namespace.IsNullOrEmpty() && !objectType.Namespace.StartsWith("System.");
    }
    
    private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
    {
        method.ThrowIfNull("method");
        var c = method as ConstructorInfo;
        if (c != null)
            return a => c.Invoke(a);
        return a => method.Invoke(null, a);
    }
}

I'm using it like this.

public struct Test {
  public readonly int A;
  public readonly string B;

  public Test(int a, string b) {
    A = a;
    B = b;
  }
}

var json = JsonConvert.SerializeObject(new Test(1, "Test"), new JsonSerializerSettings {
  ContractResolver = new CustomContractResolver()
});
var t = JsonConvert.DeserializeObject<Test>(json);
t.A.ShouldEqual(1);
t.B.ShouldEqual("Test");

Solution 3 - C#

Based on some of the answers here, I have written a CustomConstructorResolver for use in a current project, and I thought it might help somebody else.

It supports the following resolution mechanisms, all configurable:

  • Select a single private constructor so you can define one private constructor without having to mark it with an attribute.
  • Select the most specific private constructor so you can have multiple overloads, still without having to use attributes.
  • Select the constructor marked with an attribute of a specific name - like the default resolver, but without a dependency on the Json.Net package because you need to reference Newtonsoft.Json.JsonConstructorAttribute.
public class CustomConstructorResolver : DefaultContractResolver
{
    public string ConstructorAttributeName { get; set; } = "JsonConstructorAttribute";
    public bool IgnoreAttributeConstructor { get; set; } = false;
    public bool IgnoreSinglePrivateConstructor { get; set; } = false;
    public bool IgnoreMostSpecificConstructor { get; set; } = false;

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var contract = base.CreateObjectContract(objectType);

        // Use default contract for non-object types.
        if (objectType.IsPrimitive || objectType.IsEnum) return contract;

        // Look for constructor with attribute first, then single private, then most specific.
        var overrideConstructor = 
               (this.IgnoreAttributeConstructor ? null : GetAttributeConstructor(objectType)) 
            ?? (this.IgnoreSinglePrivateConstructor ? null : GetSinglePrivateConstructor(objectType)) 
            ?? (this.IgnoreMostSpecificConstructor ? null : GetMostSpecificConstructor(objectType));

        // Set override constructor if found, otherwise use default contract.
        if (overrideConstructor != null)
        {
            SetOverrideCreator(contract, overrideConstructor);
        }

        return contract;
    }

    private void SetOverrideCreator(JsonObjectContract contract, ConstructorInfo attributeConstructor)
    {
        contract.OverrideCreator = CreateParameterizedConstructor(attributeConstructor);
        contract.CreatorParameters.Clear();
        foreach (var constructorParameter in base.CreateConstructorParameters(attributeConstructor, contract.Properties))
        {
            contract.CreatorParameters.Add(constructorParameter);
        }
    }

    private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
    {
        var c = method as ConstructorInfo;
        if (c != null)
            return a => c.Invoke(a);
        return a => method.Invoke(null, a);
    }

    protected virtual ConstructorInfo GetAttributeConstructor(Type objectType)
    {
        var constructors = objectType
            .GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
            .Where(c => c.GetCustomAttributes().Any(a => a.GetType().Name == this.ConstructorAttributeName)).ToList();

        if (constructors.Count == 1) return constructors[0];
        if (constructors.Count > 1)
            throw new JsonException($"Multiple constructors with a {this.ConstructorAttributeName}.");

        return null;
    }

    protected virtual ConstructorInfo GetSinglePrivateConstructor(Type objectType)
    {
        var constructors = objectType
            .GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);

        return constructors.Length == 1 ? constructors[0] : null;
    }

    protected virtual ConstructorInfo GetMostSpecificConstructor(Type objectType)
    {
        var constructors = objectType
            .GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
            .OrderBy(e => e.GetParameters().Length);

        var mostSpecific = constructors.LastOrDefault();
        return mostSpecific;
    }
}

Here is the complete version with XML documentation as a gist: https://gist.github.com/bjorn-jarisch/80f77f4b6bdce3b434b0f7a1d06baa95

Feedback appreciated.

Solution 4 - C#

The default behaviour of Newtonsoft.Json is going to find the public constructors. If your default constructor is only used in containing class or the same assembly, you can reduce the access level to protected or internal so that Newtonsoft.Json will pick your desired public constructor.

Admittedly, this solution is rather very limited to specific cases.

internal Result() { }

public Result(int? code, string format, Dictionary<string, string> details = null)
{
    Code = code ?? ERROR_CODE;
    Format = format;

    if (details == null)
        Details = new Dictionary<string, string>();
    else
        Details = details;
}

Solution 5 - C#

Based on the answer by Zoltan, I created a variation that lets you use a specific constructor based on its signature.

Usage

return new JsonSerializerSettings
{
	ContractResolver = new DynamicObjectResolver(t =>
	{
		if (t == typeof(QueueProperties))
			return new Type[] { typeof(string) };

		return null;
	})
};

An here is the implementation

using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Concurrent;
using System.Reflection;

namespace ConsoleApp76.Json
{
	class DynamicObjectResolver : DefaultContractResolver
	{
		private readonly Func<Type, Type[]> GetConstructorSignature;
		private readonly ConcurrentDictionary<Type, ConstructorInfo> TypeToConstructorLookup =
			new ConcurrentDictionary<Type, ConstructorInfo>();

		public DynamicObjectResolver(Func<Type, Type[]> getConstructorSignature)
		{
			if (getConstructorSignature is null)
				throw new ArgumentNullException(nameof(getConstructorSignature));
			GetConstructorSignature = getConstructorSignature;
		}

		protected override JsonObjectContract CreateObjectContract(Type objectType)
		{
			var result = base.CreateObjectContract(objectType);
			ConstructorInfo constructor = TypeToConstructorLookup.GetOrAdd(objectType, t => FindConstructorInfo(t));
			if (constructor is null)
				return result;

			result.OverrideCreator = CreateParameterizedConstructor(constructor);
			foreach (var param in CreateConstructorParameters(constructor, result.Properties))
				result.CreatorParameters.Add(param);

			return result;
		}

		private ConstructorInfo FindConstructorInfo(Type objectType)
		{
			Type[] constructorSignature = GetConstructorSignature(objectType);
			if (constructorSignature is null)
				return null;

			return objectType.GetConstructor(
				bindingAttr:
					System.Reflection.BindingFlags.Public
					| System.Reflection.BindingFlags.NonPublic
					| System.Reflection.BindingFlags.Instance,
				binder: null,
				types: new Type[] { typeof(string) },
				modifiers: null);
		}

		private static ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
		{
			if (method is null)
				throw new ArgumentNullException(nameof(method));

			var c = method as ConstructorInfo;
			if (c != null)
				return a => c.Invoke(a);
			return a => method.Invoke(null, a);
		}
	}
}

Solution 6 - C#

Solution:

public Response Get(string jsonData) {
    var json = JsonConvert.DeserializeObject<modelname>(jsonData);
    var data = StoredProcedure.procedureName(json.Parameter, json.Parameter, json.Parameter, json.Parameter);
    return data;
}

Model:

public class modelname {
    public long parameter{ get; set; }
    public int parameter{ get; set; }
    public int parameter{ get; set; }
    public string parameter{ get; set; }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkmacdonaldView Question on Stackoverflow
Solution 1 - C#Brian RogersView Answer on Stackoverflow
Solution 2 - C#Zoltán TamásiView Answer on Stackoverflow
Solution 3 - C#Björn JarischView Answer on Stackoverflow
Solution 4 - C#DarkatoView Answer on Stackoverflow
Solution 5 - C#Peter MorrisView Answer on Stackoverflow
Solution 6 - C#sachinView Answer on Stackoverflow