假设Core 3不支持此功能,让我们尝试解决这个问题。那么,我们的问题是什么?
我们需要一种方法,可以使用来自JSON字符串的属性覆盖现有对象的某些属性。因此,我们的方法将具有以下签名:
void PopulateObject<T>(T target, string jsonSource) where T : class
我们不希望进行任何自定义解析,因为这会很麻烦,所以我们将尝试明显的方法——反序列化
jsonSource
并将结果属性复制到我们的对象中。但是我们不能只去{{这样做}}。
T updateObject = JsonSerializer.Parse<T>(jsonSource);
CopyUpdatedProperties(target, updateObject);
这是因为对于一种类型
class Example
{
int Id { get; set; }
int Value { get; set; }
}
以及一个JSON
{
"Id": 42
}
我们将得到{{updateObject.Value == 0}}。现在我们不知道{{0}}是新的更新值还是它没有被更新,因此我们需要确切地知道{{jsonSource}}包含哪些属性。
幸运的是,{{System.Text.Json}} API允许我们检查已解析JSON的结构。
using var json = JsonDocument.Parse(jsonSource).RootElement
现在我们可以枚举所有属性并进行复制。
foreach (var property in json.EnumerateObject())
{
OverwriteProperty(target, property);
}
我们将使用反射来复制该值:
void OverwriteProperty<T>(T target, JsonProperty updatedProperty) where T : class
{
var propertyInfo = typeof(T).GetProperty(updatedProperty.Name);
if (propertyInfo == null)
{
return;
}
var propertyType = propertyInfo.PropertyType;
v̶a̶r̶ ̶p̶a̶r̶s̶e̶d̶V̶a̶l̶u̶e̶ ̶=̶ ̶J̶s̶o̶n̶S̶e̶r̶i̶a̶l̶i̶z̶e̶r̶.̶P̶a̶r̶s̶e̶(̶u̶p̶d̶a̶t̶e̶d̶P̶r̶o̶p̶e̶r̶t̶y̶.̶V̶a̶l̶u̶e̶,̶ ̶p̶r̶o̶p̶e̶r̶t̶y̶T̶y̶p̶e̶)̶;̶
var parsedValue = JsonSerializer.Deserialize(
updatedProperty.Value.GetRawText(),
propertyType);
propertyInfo.SetValue(target, parsedValue);
}
我们可以看到这里我们正在进行的是一个浅层更新。如果对象包含另一个复杂对象作为其属性,那么该对象将被整体复制并覆盖,而不是更新。如果您需要深度更新,则需要更改此方法以提取属性的当前值,然后在属性类型为引用类型时递归调用 PopulateObject(这也需要在 PopulateObject 中接受 Type 作为参数)。
将所有内容组合在一起,我们得到:
void PopulateObject<T>(T target, string jsonSource) where T : class
{
using var json = JsonDocument.Parse(jsonSource).RootElement;
foreach (var property in json.EnumerateObject())
{
OverwriteProperty(target, property);
}
}
void OverwriteProperty<T>(T target, JsonProperty updatedProperty) where T : class
{
var propertyInfo = typeof(T).GetProperty(updatedProperty.Name);
if (propertyInfo == null)
{
return;
}
var propertyType = propertyInfo.PropertyType;
v̶a̶r̶ ̶p̶a̶r̶s̶e̶d̶V̶a̶l̶u̶e̶ ̶=̶ ̶J̶s̶o̶n̶S̶e̶r̶i̶a̶l̶i̶z̶e̶r̶.̶P̶a̶r̶s̶e̶(̶u̶p̶d̶a̶t̶e̶d̶P̶r̶o̶p̶e̶r̶t̶y̶.̶V̶a̶l̶u̶e̶,̶ ̶p̶r̶o̶p̶e̶r̶t̶y̶T̶y̶p̶e̶)̶;̶
var parsedValue = JsonSerializer.Deserialize(
updatedProperty.Value.GetRawText(),
propertyType);
propertyInfo.SetValue(target, parsedValue);
}
这个有多健壮?对于JSON数组,它肯定不会执行任何明智的操作,但我不确定你期望
PopulateObject
方法如何开始处理数组。我不知道它在性能上与
Json.Net
版本相比如何,您需要自己测试。出于设计考虑,它还会默默地忽略目标类型中不存在的属性。我认为这是最明智的方法,但您可能持有不同看法,在这种情况下,属性空值检查必须替换为异常抛出。
编辑: 我继续实现了深层复制:
void PopulateObject<T>(T target, string jsonSource) where T : class =>
PopulateObject(target, jsonSource, typeof(T));
void OverwriteProperty<T>(T target, JsonProperty updatedProperty) where T : class =>
OverwriteProperty(target, updatedProperty, typeof(T));
void PopulateObject(object target, string jsonSource, Type type)
{
using var json = JsonDocument.Parse(jsonSource).RootElement;
foreach (var property in json.EnumerateObject())
{
OverwriteProperty(target, property, type);
}
}
void OverwriteProperty(object target, JsonProperty updatedProperty, Type type)
{
var propertyInfo = type.GetProperty(updatedProperty.Name);
if (propertyInfo == null)
{
return;
}
var propertyType = propertyInfo.PropertyType;
object parsedValue;
if (propertyType.IsValueType || propertyType == typeof(string))
{
̶p̶a̶r̶s̶e̶d̶V̶a̶l̶u̶e̶ ̶=̶ ̶J̶s̶o̶n̶S̶e̶r̶i̶a̶l̶i̶z̶e̶r̶.̶P̶a̶r̶s̶e̶(̶u̶p̶d̶a̶t̶e̶d̶P̶r̶o̶p̶e̶r̶t̶y̶.̶V̶a̶l̶u̶e̶,̶ ̶p̶r̶o̶p̶e̶r̶t̶y̶T̶y̶p̶e̶)̶;̶
parsedValue = JsonSerializer.Deserialize(
updatedProperty.Value.GetRawText(),
propertyType);
}
else
{
parsedValue = propertyInfo.GetValue(target);
P̶o̶p̶u̶l̶a̶t̶e̶O̶b̶j̶e̶c̶t̶(̶p̶a̶r̶s̶e̶d̶V̶a̶l̶u̶e̶,̶ ̶u̶p̶d̶a̶t̶e̶d̶P̶r̶o̶p̶e̶r̶t̶y̶.̶V̶a̶l̶u̶e̶,̶ ̶p̶r̶o̶p̶e̶r̶t̶y̶T̶y̶p̶e̶)̶;̶
PopulateObject(
parsedValue,
updatedProperty.Value.GetRawText(),
propertyType);
}
propertyInfo.SetValue(target, parsedValue);
}
为了使其更加健壮,您需要拥有一个单独的PopulateObjectDeep方法或使用具有深度/浅层标志的PopulateObjectOptions或类似选项。
编辑2:
深度复制的目的是,如果我们有一个对象
{
"Id": 42,
"Child":
{
"Id": 43,
"Value": 32
},
"Value": 128
}
并使用{{}}填充它
{
"Child":
{
"Value": 64
}
}
我们需要
{
"Id": 42,
"Child":
{
"Id": 43,
"Value": 64
},
"Value": 128
}
在浅拷贝情况下,我们会在被复制的子对象中得到
Id = 0
。
编辑3:
正如@ldam所指出的那样,在稳定的.NET Core 3.0中,这种方法已经不再适用,因为API已经改变。现在
Parse
方法是
Deserialize
,你必须深入挖掘才能获得
JsonElement
的值。在corefx repo中有
一个活跃的问题允许直接反序列化
JsonElement
。目前最接近的解决方案是使用
GetRawText()
。我已经编辑了上面的代码使其工作,并将旧版本划掉。