2

我在序列化为 JSON 时加密某些字段并在反序列化为特定 C# 类期间解密这些字段的任务失败。

我把问题归结为最基本的问题,就是无法通过操作 value 来自定义特定字段的反序列化,也不知道是什么原因。我正在为每个字段使用自定义合同解析器和自定义值提供程序。我可以看到该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;
    }
}

这是在需要时重现它的示例代码

希望有人可以让我知道我错过了什么:)

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

更新 2:感谢@dbc 的精彩回答,不,我知道反序列化为不可变对象的好方法。接受答案后的最终版本代码

更新 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;
    }
}
4

1 回答 1

2

IValueProvider.SetValue不调用的属性的原因SampleInner是它SampleInner不可变的,因此没有要调用的集合方法。相反,JSON 属性通过名称(模数大小写)与类型的单个参数化构造函数的参数匹配,反序列化为匹配参数的类型,然后按照此处所述传递给构造函数。

即使您要使属性可变,也不会为已传递给构造函数的属性调用 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;
    }       
}

现在有 setter 被调用,你的MyValueProvider.SetValue()will 被调用。演示小提琴#1在这里

其次,如果你不能这样修改你的类型,你可以包装在一些装饰器中调用的构造函数方法,它会进行必要的预处理,但是由于JsonObjectContract.ParameterizedCreatornonpublic ,这使得这变得困难。因此,您无法直接访问 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在这里

于 2019-07-15T05:49:02.333 回答