我正在准备从 ASP.NET Core 2.2 迁移到 3.0。
因为我没有使用更高级的 JSON 特性(但可能如下所述),并且 3.0 现在带有一个内置的 JSON 命名空间/类,System.Text.Json
,我决定看看是否可以删除以前的默认值 Newtonsoft.Json
.
请注意,我知道 System.Text.Json
不会完全取代 Newtonsoft.Json
.
我设法在任何地方都做到了,例如
var obj = JsonSerializer.Parse<T>(jsonstring);
var jsonstring = JsonSerializer.ToString(obj);
但在一个地方,我填充了一个现有对象。
与 Newtonsoft.Json
一个可以做到
JsonConvert.PopulateObject(jsonstring, obj);
内置 System.Text.Json
namespace 有一些额外的类,比如 JsonDocumnet
, JsonElement
和 Utf8JsonReader
,尽管我找不到任何将现有对象作为参数的对象。
我也没有足够的经验来了解如何利用现有的。
可能有 a possible upcoming feature in .Net Core (感谢 Mustafa Gursel 的链接),但与此同时(如果没有怎么办)...
...我现在想知道,是否有可能实现与 PopulateObject
类似的东西? ?
我的意思是,是否可以使用其他任何一个 System.Text.Json
类来完成相同的任务,并仅更新/替换属性集?,...或其他一些巧妙的解决方法?
这是我正在寻找的示例输入/输出,它需要是通用的,因为传递给反序列化方法的对象是 <T>
类型的).我有 2 个 Json 字符串要解析成一个对象,其中第一个设置了一些默认属性,第二个设置了一些默认属性,例如
请注意,属性值可以是除 string
之外的任何其他类型.
Json 字符串 1:
{
"Title": "Startpage",
"Link": "/index",
}
Json 字符串 2:
{
"Head": "Latest news"
"Link": "/news"
}
使用上面的 2 个 Json 字符串,我想要一个对象导致:
{
"Title": "Startpage",
"Head": "Latest news",
"Link": "/news"
}
如上例所示,如果第二个属性有值/已设置,它将替换第一个中的值(如“Head”和“Link”),如果没有,则现有值保持不变(如“Title” )
最佳答案
因此,假设 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);
}
我们可以在这里看到我们正在做的是浅更新。如果该对象包含另一个复杂对象作为其属性,则该对象将作为一个整体被复制和覆盖,而不是更新。如果您需要深度更新,则需要更改此方法以提取属性的当前值,然后如果属性的类型是引用类型(即还需要接受 Type
作为 PopulateObject
中的参数。
将它们结合在一起我们得到:
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
版本的性能相比如何,您必须自己测试。根据设计,它还会默默地忽略不在目标类型中的属性。我认为这是最明智的方法,但您可能不这么认为,在那种情况下,属性 null-check 必须替换为异常抛出。
编辑:
我继续实现深拷贝:
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
}
在浅拷贝的情况下,我们会在复制的 child 中得到 Id = 0
。
编辑 3:
正如@ldam 指出的那样,这在稳定的 .NET Core 3.0 中不再有效,因为 API 已更改。 Parse
方法现在是 Deserialize
,您必须深入挖掘才能获得 JsonElement
的值。有 an active issue in the corefx repo允许直接反序列化 JsonElement
。现在最接近的解决方案是使用 GetRawText()
。我继续编辑上面的代码以使其工作,留下旧版本删除线。
关于c# - .Net Core 3.0 JsonSerializer 填充现有对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56835040/