Andrew Hanlon
Andrew Hanlon

Reputation: 7421

Force constructor parameter value in Json.net deserialization

Is it possible to force Json.net to pass a default value for a constructor parameter?

I have tried using a custom ContractResolver that overrides the CreateConstructorParameters and CreatePropertyFromConstructorParameter methods and sets the returned JsonProperty.DefaultValue property to my desired value and DefaultValueHandling property to DefaultValueHandling.Populate.

By overriding the CreateObjectContract method, I can see that the matching CreatorParameters and Properties both include my default value. However, I am still getting a null value passed to the constructors.

I have even tried setting the Converter on the properties to a converter that simply returns the desired default value, but still no luck.

Below is the ConstractResolver code:

public class DataControllerContractResolver : DefaultContractResolver
{
    ///The desired default value
    private readonly IDataController _dataController;

    private readonly Type _dataControllerType;
    private readonly JsonConverter<IDataController> _converter;

    public DataControllerContractResolver(IDataController dataController)
    {
        _dataController = dataController;
        _dataControllerType = dataController.GetType();
        _converter = SimpleJsonConverter<IDataController>.From((reader, controller, arg3) => _dataController, (writer, controller, arg3) => {});
    }

    protected override IList<JsonProperty> CreateConstructorParameters(ConstructorInfo constructor, JsonPropertyCollection memberProperties)
    {
        var props = base.CreateConstructorParameters(constructor, memberProperties);

        foreach (var prop in memberProperties.Where(p => p.PropertyType.IsAssignableFrom(_dataControllerType)).ToList())
        {
            prop.DefaultValue = _dataController;
            prop.Converter = _converter;
            prop.DefaultValueHandling = DefaultValueHandling.Populate;
            prop.Ignored = false;
        }

        foreach (var prop in props.Where(p => p.PropertyType.IsAssignableFrom(_dataControllerType)).ToList())
        {
            prop.DefaultValue = _dataController;
            prop.Converter = _converter;
            prop.DefaultValueHandling = DefaultValueHandling.Populate;
            prop.Ignored = false;
        }


        return props;
    }

    protected override JsonProperty CreatePropertyFromConstructorParameter(JsonProperty matchingMemberProperty, ParameterInfo parameterInfo)
    {
        var prop = base.CreatePropertyFromConstructorParameter(matchingMemberProperty, parameterInfo);

        if (prop.PropertyType.IsAssignableFrom(_dataControllerType))
        {
            prop.DefaultValue = _dataController;
            prop.Converter = _converter;
            prop.MemberConverter = _converter;
            prop.DefaultValueHandling = DefaultValueHandling.Populate;
            prop.Ignored = false;
        }

        return prop;
    }

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

Upvotes: 2

Views: 2442

Answers (2)

Andrew Hanlon
Andrew Hanlon

Reputation: 7421

After not finding a built-in solution to this issue, I buckled down and added the default value check and a pull-request on Github. JamesNK (the Json.net creator) implemented the solution slightly differently, but it is available in the latest builds.

Upvotes: 1

dbc
dbc

Reputation: 116786

I can make this work if I override property.DefaultValue and property.DefaultValueHandling in CreateProperties(Type type, MemberSerialization memberSerialization). I don't have all your classes (no IDataController), so here's a simple example:

public class DefaultStringValueContractResolver : DefaultContractResolver
{
    public string DefaultStringValue { get; set; }

    public DefaultStringValueContractResolver(string defaultStringValue)
    {
        this.DefaultStringValue = defaultStringValue;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(type, memberSerialization);
        // Set all string properties to have a default value of "this is a default value"
        foreach (var property in properties.Where(p => p.PropertyType == typeof(string)))
        {
            property.DefaultValue = DefaultStringValue;
            property.DefaultValueHandling = DefaultValueHandling.Populate;
        }
        return properties;
    }
}

And then:

public class TestClass
{
    const string DefaultStringValue = "This is a default string value";

    public string Property1 { get; set; }

    public string Property2 { get; set; }

    public static void Test()
    {
        var settings = new JsonSerializerSettings { ContractResolver = new DefaultStringValueContractResolver(DefaultStringValue) };
        var test = JsonConvert.DeserializeObject<TestClass>("{}", settings);
        Debug.Assert(test.Property1 == DefaultStringValue && test.Property2 == DefaultStringValue); // No assert
        Debug.WriteLine(JsonConvert.SerializeObject(test)); // Prints {"Property1":"This is a default string value","Property2":"This is a default string value"}
    }
}

One thing to note: if the default value is a reference type, all instances of the class containing the property will have default values referring to the same default value instance.

Upvotes: 1

Related Questions