0

JsonObjectContract.ParametrizedConstructor is marked as Obsolete and says that OverrideCreator should be used instead. However OverrideCreator does not set _parametrizedConstructor or _parameterizedCreator meaning that if you need to set those values from the outside you must use ParametrizedConstructor.

Is this intentional? If so why?

Thanks!

Edit 1:

To explain why I'm asking, I have a class in my application that handles serialization (using JSON.Net). This class creates a custom IContractResolver (based on DefaultContractResolver) that has a stock set of JsonConverters that I wrote for my application. This allows the contracts that are generated to check for the converters once and then be cached.

My serialization class allows callers to pass in their own custom converters. When this is done I create a new IContractResolver that takes these new converters and the default contract resolver. What I do then is, when resolving the contract for a type, I first get a contract from the default contract resolver. When I get it I then check and see if any of the new converters can handle the type. If they can I 'clone' the json contract and replace the converter with the new one (and cache it).

In this cloning is where I set the ParametrizedConstructor. I was not doing this originally, as it was marked as deprecated, I set just the OverrideCreator. I ran into an issue when deserializing Tuples. I kept getting an error saying there was no constructor for Tuple. I tried using the stock JsonConvert to round trip a Tuple and it worked perfectly. In looking into why it worked with JsonConvert I found it was because when I 'clone' the contract I was not setting the ParametrizedConstructor.

Edit 2:

Here is a slimmed example of my contract resolver:

class MyContractResolver : DefaultContractResolver
{
    readonly Dictionary<Type, JsonContract> contractDictionary = new Dictionary<Type, JsonContract>();
    readonly List<JsonConverter> converters;
    readonly IContractResolver resolverToExtend;

    public MyContractResolver(List<JsonConverter> converters, IContractResolver resolverToExtend)
    {
        this.converters = converters;
        this.resolverToExtend = resolverToExtend;
    }

    public override JsonContract ResolveContract(Type type)
    {
        JsonContract contract;
        if (!contractDictionary.TryGetValue(type, out contract))
        {
            contract = resolverToExtend.ResolveContract(type);
            var converter = contract.Converter;
            foreach (var baseConverter in converters)
            {
                if (baseConverter.CanConvert(type))
                {
                    contract = CloneJsonContract(contract, type, baseConverter);
                    break;
                }
            }
            contractDictionary.Add(type, contract);
        }
        return contract;
    }

    static JsonContract CloneJsonContract(JsonContract contract, Type type, JsonConverter customConverter)
    {
        JsonContract newContract;

        // Check contract type and create a new one (JsonArrayContract, JsonDictionaryContract etc)
        if (contract is JsonArrayContract)
        {
            newContract = new JsonArrayContract(type);
        }
        //...
        else
        {
            JsonObjectContract jsonObjectContract = contract as JsonObjectContract;
            if (jsonObjectContract != null)
            {
                newContract = CloneJsonObjectContract(new JsonObjectContract(type), jsonObjectContract);
            }
            else
            {
                throw new ArgumentException("Unknown JsonContract type: " + contract.GetType() + ", object: " + contract);
            }
        }
        //Copy properties like IsReference, OnSerializingCallbacks etc. and set the new converter
        return newContract;
    }

    static JsonObjectContract CloneJsonObjectContract(JsonObjectContract newContract, JsonObjectContract oldContract)
    {
        newContract.OverrideCreator = oldContract.OverrideCreator;
        newContract.DefaultCreator = oldContract.DefaultCreator;
        newContract.DefaultCreatorNonPublic = oldContract.DefaultCreatorNonPublic;
        newContract.ParametrizedConstructor = oldContract.ParametrizedConstructor; // If I do no copy this then the behavior of the old and new contract is different.
        CloneJsonPropertyList(newContract.Properties, oldContract.Properties);
        CloneJsonPropertyList(newContract.CreatorParameters, oldContract.CreatorParameters);
        return newContract;
    } 
}
shortspider
  • 1,045
  • 15
  • 34
  • This is really a second question now. You're only doing a shallow clone, not a deep clone, right? If so, you could use the "dark arts" of reflection and do a `MemberwiseClone()`, similarly to how I clone a `JsonProperty` here: https://stackoverflow.com/questions/33155458/json-deserialize-from-legacy-property-names. – dbc Jul 31 '16 at 00:55
  • Can you create a [mcve] for your "updated" question? I'm having some problem visualizing your custom contract resolver. – dbc Jul 31 '16 at 00:56
  • @dbc added a code example. – shortspider Jul 31 '16 at 15:40

1 Answers1

0

The precise function of the two properties OverrideCreator and ParametrizedConstructor(and its derived delegate property ParameterizedCreator) can be seen from the reference source of JsonSerializerInternalReader.CreateNewObject():

  1. If there is an OverrideCreator, use it to create the object using the CreatorParameters.

  2. Else if there is a DefaultCreator and it is either public or private and JsonSerializer.ConstructorHandling says to use it anyway, then use it to create the object.

  3. Else if there is a ParameterizedCreator, use it to create the object.

  4. Otherwise the object cannot be constructed, so throw an exception.

Thus the OverrideCreator takes precedence unconditionally while the ParameterizedCreator is used as a fallback only when there is no DefaultCreator to use instead. I suspect that Newtonsoft marked the latter as obsolete because they want to transition it to being internal. I.e. users of Json.NET will always be able to set an unconditional override creator but should have no need to set a conditional fallback creator that might or might not be called depending on the value of ConstructorHandling.

For instance, if you want to inject a creation method for a specific type T, you could use the following:

public class FactoryContractResolver<T> : DefaultContractResolver
{
    readonly Func<T> creator;

    public FactoryContractResolver(Func<T> creator)
    {
        this.creator = creator;
    }

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var contract = base.CreateObjectContract(objectType);
        if (objectType == typeof(T))
        {
            contract.OverrideCreator = delegate(object[] args) { return creator(); };
            if (contract.CreatorParameters != null)
                contract.CreatorParameters.Clear();
        }
        return contract;
    }
}

Note that the base class DefaultContractResolver.CreateObjectContract will not throw an exception if a constructor could not be found. The exception is only thrown when actually trying to construct an instance of such a type during deserialization.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • Thank you for the detailed answer. The move to make it `internal` makes sense the way you lay it out. If that happens though, would it then be impossible to 'clone' a `JsonContract`? I updated my answer to explain what I am doing. – shortspider Jul 31 '16 at 00:09