如何在反序列化期间使用合同解析器和价值提供者自定义值设置

时间:2019-07-14 18:56:40

标签: c# json.net json-deserialization value-provider

我无法完成序列化为JSON时加密某些字段并在反序列化为特定C#类的过程中解密这些字段的任务。

我将问题简化为最基本的问题,这是我无法通过操纵值来自定义特定字段的反序列化,我也不知道原因。我为每个字段使用自定义合同解析器和自定义值提供程序。我可以看到执行了GetValue函数,但是从未执行过SetValue

代码示例:

class Program
{
    static void Main(string[] args)
    {

        var text = "This is text";
        var number = 1;
        var anotherText = "This is another text";
        var anotherNumber = 2;
        var sampleInner = new SampleInner(anotherText, anotherNumber);
        var sample = new SampleMessage(text, number, sampleInner);

        var myCustomContractResolver = new MyCustomContractResolver();
        var jsonSettings = GetJsonSettings(myCustomContractResolver);

        Console.WriteLine("Serializing..");
        var json = JsonConvert.SerializeObject(sample, jsonSettings);
        Console.WriteLine(json);

        Console.WriteLine("Deserializing..");
        var sampleDeserialized = JsonConvert.DeserializeObject(json, typeof(SampleMessage), jsonSettings);
        Console.WriteLine(sampleDeserialized);

        Console.ReadLine();
    }

    private static JsonSerializerSettings GetJsonSettings(IContractResolver contractResolver)
    {
        var jsonSettings =
            new JsonSerializerSettings
            {
                ContractResolver = contractResolver
            };
        return jsonSettings;
    }
}

自定义合同解析器:

public class MyCustomContractResolver
    : DefaultContractResolver
{
    public MyCustomContractResolver()
    {
        NamingStrategy = new CamelCaseNamingStrategy();
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var jsonProperties = base.CreateProperties(type, memberSerialization);

        foreach (var jsonProperty in jsonProperties)
        {
            var propertyInfo = type.GetProperty(jsonProperty.UnderlyingName);
            var defaultValueProvider = jsonProperty.ValueProvider;
            jsonProperty.ValueProvider = new MyValueProvider(defaultValueProvider);
        }

        return jsonProperties;
    }
}

以及其SetValue从未在反序列化期间执行的自定义值提供程序:

public class MyValueProvider
    : IValueProvider
{
    private readonly IValueProvider _valueProvider;

    public MyValueProvider(IValueProvider valueProvider)
    {
        _valueProvider = valueProvider;
    }

    public void SetValue(object target, object value)
    {
        //This is not executed during deserialization. Why?
        _valueProvider.SetValue(target, value);
        Console.WriteLine($"Value set: {value}");
    }

    public object GetValue(object target)
    {
        var value = _valueProvider.GetValue(target);
        Console.WriteLine($"Value get: {value}");
        return value;
    }
}

Here is the sample code to reproduce it,以备不时之需。

希望有人可以让我知道我想念的是什么:)

更新1:我序列化/反序列化的对象是不可变的(没有公共设置器),这是一个要求,因为我需要支持此类对象。正如评论指出的那样,那么没有SetValue被执行

更新2:感谢@dbc的出色回答,我不知道将反序列化为不可变对象的一种很好的解决方法。 The final version code after the accepted answer

更新3:对于该问题,所选答案绝对正确。但是,在进一步研究之后,我决定采用一种稍有不同的方法,这种方法适用于不变类和可变类,以防有人处于类似情况。取而代之的是使用值提供程序,我现在使用合同解析器和json转换器的组合,以便我可以使用合同解析器根据类型决定如何序列化/反序列化,并且使用json转换器,我可以在序列化/反序列化过程中访问值根据需要。

基本上,在我的合同解析器上,我重写了用于创建属性的方法(可以在其中访问原始Type属性),并有选择地指定要使用的json转换器。

protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
    var jsonProperties = base.CreateProperties(type, memberSerialization);

    //Filter here based on type, attribute or whatever and if want to customize a specific property type:
    foreach (var jsonProperty in jsonProperties)
    {
        jsonProperty.Converter = new MyJsonConverter();
    }

    return jsonProperties;
}

并且在MyJsonConverter中,我们可以选择写入json或从json读取时要做的事情:

public class MyJsonConverter
    : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        //I can do whatever with value
        var finalValue = $"{value}-edited";
        writer.WriteValue(finalValue);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // I can do whatever with the value
        var value = (string)reader.Value;
        var newValue = "whatever";
        return newValue;
    }

    public override bool CanWrite => true;

    public override bool CanRead => true;

    public override bool CanConvert(Type objectType)
    {
        return true;
    }
}

1 个答案:

答案 0 :(得分:1)

IValueProvider.SetValue的属性未调用SampleInner的原因是SampleInner不可变的,因此没有没有设置方法来被称为。取而代之的是,JSON属性通过名称(模数大小写)与类型的单个参数化构造函数的参数匹配,反序列化为匹配参数的类型,然后按照here的说明传递给构造函数。

即使您要使属性可变,对于已传递到构造函数中的属性,也不会调用setter,因为Json.NET做出(合理的)假设,即将属性值传递给构造函数足以设置属性的值。

那么,您有什么选择?

首先,您可以使用默认构造函数使类型可变。设置器和构造器可以是私有的,只要它们带有适当的属性即可:

public class SampleInner
{
    [JsonProperty] // Adding this attribute informs Json.NET that the private setter can be called.
    public string AnotherText { get; private set; }

    [JsonProperty]
    public int AnotherNumber { get; private set; }

    [JsonConstructor] // Adding this attribute informs Json.NET that this private constructor can be called
    private SampleInner() { }

    public SampleInner(string anotherText, int anotherNumber)
    {
        this.AnotherText = anotherText;
        this.AnotherNumber = anotherNumber;
    }       
}

现在有一些设置器要调用,您的MyValueProvider.SetValue()将被调用。演示小提琴#1 here

第二,如果您不能修改类型,则可以包装在装饰器中调用的constructor方法,该方法执行必要的预处理,但是由于JsonObjectContract.ParameterizedCreator 非公开。因此,您无法直接访问Json.NET选择的参数化构造函数来装饰它。但是,您可以确定其自变量,它们由JsonObjectContract.CreatorParameters指定。填充此集合后,将设置OverrideCreator或(秘密)ParameterizedCreator。这样可以插入必要的逻辑,如下所示:

public class MyCustomContractResolver : DefaultContractResolver
{
    public MyCustomContractResolver() { NamingStrategy = new CamelCaseNamingStrategy(); }

    static ObjectConstructor<Object> GetParameterizedConstructor(JsonObjectContract contract)
    {
        if (contract.OverrideCreator != null)
            return contract.OverrideCreator;

        // Here we assume that JsonSerializerSettings.ConstructorHandling == ConstructorHandling.Default
        // If you would prefer AllowNonPublicDefaultConstructor then you need to remove the check on contract.DefaultCreatorNonPublic
        if (contract.CreatorParameters.Count > 0 && (contract.DefaultCreator == null || contract.DefaultCreatorNonPublic))
        {
            // OK, Json.NET has a parameterized constructor stashed away in JsonObjectContract.ParameterizedCreator
            // https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs#L100
            // But, annoyingly, this value is internal so we cannot get it!
            // But because CreatorParameters.Count > 0 and OverrideCreator == null we can infer that such a constructor exists, and so call it using Activator.CreateInstance

            return (args) => Activator.CreateInstance(contract.CreatedType, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, args, CultureInfo.InvariantCulture);
        }

        return null;
    }

    static ObjectConstructor<Object> CustomizeConstructor(JsonObjectContract contract, ObjectConstructor<Object> constructor)
    {
        if (constructor == null)
            return null;
        return (args) =>
        {
            // Add here your customization logic.
            // You can match creator parameters to properties by property name if needed.
            foreach (var pair in args.Zip(contract.CreatorParameters, (a, p) => new { Value = a, Parameter = p }))
            {
                // Get the corresponding property in case you need to, e.g., check its attributes:
                var property = contract.Properties[pair.Parameter.PropertyName];

                if (property == null)
                    Console.WriteLine("Argument {0}: Value {1}", pair.Parameter.PropertyName, pair.Value);
                else
                    Console.WriteLine("Argument {0} (corresponding to JsonProperty {1}): Value {2}", pair.Parameter.PropertyName, property, pair.Value);
            }
            return constructor(args);
        };
    }

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

        contract.OverrideCreator = CustomizeConstructor(contract, GetParameterizedConstructor(contract));

        return contract;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var jsonProperties = base.CreateProperties(type, memberSerialization);

        foreach (var jsonProperty in jsonProperties)
        {
            var defaultValueProvider = jsonProperty.ValueProvider;
            jsonProperty.ValueProvider = new MyValueProvider(defaultValueProvider);
        }

        return jsonProperties;
    }
}

注意:

  • 如果默认构造函数存在但不公开,则上述合同解析器将假定未使用它。如果您希望使用非公共默认构造函数,则需要设置JsonSerializerSettings.ConstructorHandling == ConstructorHandling.AllowNonPublicDefaultConstructor并修改上面GetParameterizedConstructor()中的代码以取消对contract.DefaultCreatorNonPublic的检查:

        if (contract.CreatorParameters.Count > 0 && contract.DefaultCreator == null)
    
  • 请求进行增强以允许访问JsonObjectContract.ParameterizedCreator和对其进行自定义是合理的。

    (我想您可以尝试通过反射直接访问JsonObjectContract.ParameterizedCreator ...)

演示小提琴#2 here