Overriding a property value in custom JSON.net contract resolver
Asked Answered
S

1

12

I am attempting to implement a custom JSON.net IContractResolver that will replace all null property values with a specified string. I'm aware that this functionality is available via attributes on member of types that get serialized; this is an alternative route that we're considering.

My resolver implementation so far is as follows. StringValueProvider is a simple implementation of IValueProvider that doesn't affect the problem, which is that I can't figure out how to get the value of property as I have no knowledge in this method of the instance that supplied member so I can't pass it in as an argument to GetValue() (marked as WHAT-GOES-HERE? in the code sample).

Is there a way that I can get what I need from member or from property?

public class NullSubstitutionPropertyValueResolver : DefaultContractResolver
{
    private readonly string _substitutionValue;

    public NullSubstitutionPropertyValueResolver(string substitutionValue)
    {
        _substitutionValue = substitutionValue;
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty result = base.CreateProperty(member, memberSerialization);

        PropertyInfo property = member as PropertyInfo;

        if (property == null)
        {
            return result;
        }

        // What do I use here to get the property value?
        bool isNull = property.GetValue(WHAT-GOES-HERE?) == null;

        if (isNull)
        {
            result.ValueProvider = new StringValueProvider(_substitutionValue);
        }

        return result;
    }
}
Stoker answered 27/10, 2017 at 14:53 Comment(1)
That's wrong place to do that, because there is no instance at this point. Intead you can do that in StringValueProvider. There you have access to both instance and value and can check if value is null and use default value instead.Sacramental
H
17

The contract resolver is not concerned with instances, it is concerned with types. The value provider is concerned with instances. In the contract resolver, you decide whether the value provider should be applied to the property based on the property type (for example, maybe you only want to use a StringValueProvider on string properties?) Then, you make the value provider store a reference to the property (pass it in the constructor along with the substitution value). In the value provider, you can read the value from the object instance, check if it is null and do the appropriate value substitution.

The code should look something like this:

public class NullSubstitutionPropertyValueResolver : DefaultContractResolver
{
    private readonly string _substitutionValue;

    public NullSubstitutionPropertyValueResolver(string substitutionValue)
    {
        _substitutionValue = substitutionValue;
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty result = base.CreateProperty(member, memberSerialization);

        PropertyInfo property = member as PropertyInfo;

        if (property.PropertyType == typeof(string))
        {
            result.ValueProvider = new StringValueProvider(property, _substitutionValue);
        }

        return result;
    }
}

public class StringValueProvider : IValueProvider
{
    private PropertyInfo _targetProperty;
    private string _substitutionValue;

    public StringValueProvider(PropertyInfo targetProperty, string substitutionValue)
    {
        _targetProperty = targetProperty;
        _substitutionValue = substitutionValue;
    }

    // SetValue gets called by Json.Net during deserialization.
    // The value parameter has the original value read from the JSON;
    // target is the object on which to set the value.
    public void SetValue(object target, object value)
    {
        _targetProperty.SetValue(target, value);
    }

    // GetValue is called by Json.Net during serialization.
    // The target parameter has the object from which to read the value;
    // the return value is what gets written to the JSON
    public object GetValue(object target)
    {
        object value = _targetProperty.GetValue(target);
        return value == null ? _substitutionValue : value;
    }
}

Here is a working demo: https://dotnetfiddle.net/PAZULK

Hughmanick answered 27/10, 2017 at 15:47 Comment(1)
Rather than passing the PropertyInfo into the custom value provider, consider using the decorator pattern by passing in the default IValueProvider created by Json.NET, as in this answer or this one. Since Json.NET's value providers use runtime-generated delegates their performance should be better than pure reflection.Lemire

© 2022 - 2024 — McMap. All rights reserved.