如何在反序列化期间使用协定解析程序和值提供程序自定义值设置



我在序列化为 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;
}
}

下面是在需要时重现它的示例代码。

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

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

不为SampleInner的属性调用IValueProvider.SetValue的原因是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()将被调用。 演示小提琴#1在这里。

其次,如果你不能修改你的类型,你可以将调用的构造函数方法包装在某个进行必要预处理的装饰器中,但是由于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在这里。

相关内容

  • 没有找到相关文章

最新更新