LGS*_*Son 10 c# system.text.json asp.net-core-6.0
在我之前的一个问题中,我询问如何使用 System.Text.Json填充现有对象。
一个很好的答案展示了一种解决方案,用 解析 json 字符串JsonDocument并用 枚举它EnumerateObject。
随着时间的推移,我的 json 字符串不断演变,现在还包含一个对象数组,当使用链接答案中的代码解析它时,它会抛出以下异常:
The requested operation requires an element of type 'Object', but the target element has type 'Array'.
Run Code Online (Sandbox Code Playgroud)
我发现人们可以以一种方式或另一种方式寻找JsonValueKind.Array, 并做这样的事情
if (json.ValueKind.Equals(JsonValueKind.Array))
{
foreach (var item in json.EnumerateArray())
{
foreach (var property in item.EnumerateObject())
{
await OverwriteProperty(???);
}
}
}
Run Code Online (Sandbox Code Playgroud)
但我无法做到这一点。
如何做到这一点并作为通用解决方案?
我想获得“结果 1”,其中添加/更新数组项,以及“结果 2”(当传递变量时),其中整个数组被替换。
对于“结果2”,我假设可以if (JsonValueKind.Array))在OverwriteProperty方法中检测到,以及在哪里/如何传递“replaceArray”变量?...在迭代数组或对象时?
一些示例数据:
Json 字符串首字母
{
"Title": "Startpage",
"Links": [
{
"Id": 10,
"Text": "Start",
"Link": "/index"
},
{
"Id": 11,
"Text": "Info",
"Link": "/info"
}
]
}
Run Code Online (Sandbox Code Playgroud)
要添加/更新的 Json 字符串
{
"Head": "Latest news",
"Links": [
{
"Id": 11,
"Text": "News",
"Link": "/news"
},
{
"Id": 21,
"Text": "More News",
"Link": "/morenews"
}
]
}
Run Code Online (Sandbox Code Playgroud)
结果1
{
"Title": "Startpage",
"Head": "Latest news"
"Links": [
{
"Id": 10,
"Text": "Start",
"Link": "/indexnews"
},
{
"Id": 11,
"Text": "News",
"Link": "/news"
},
{
"Id": 21,
"Text": "More news",
"Link": "/morenews"
}
]
}
Run Code Online (Sandbox Code Playgroud)
结果2
{
"Title": "Startpage",
"Head": "Latest news"
"Links": [
{
"Id": 11,
"Text": "News",
"Link": "/news"
},
{
"Id": 21,
"Text": "More News",
"Link": "/morenews"
}
]
}
Run Code Online (Sandbox Code Playgroud)
课程
public class Pages
{
public string Title { get; set; }
public string Head { get; set; }
public List<Links> Links { get; set; }
}
public class Links
{
public int Id { get; set; }
public string Text { get; set; }
public string Link { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
C#代码:
public async Task PopulateObjectAsync(object target, string source, Type type, bool replaceArrays = false)
{
using var json = JsonDocument.Parse(source).RootElement;
if (json.ValueKind.Equals(JsonValueKind.Array))
{
foreach (var item in json.EnumerateArray())
{
foreach (var property in item.EnumerateObject())
{
await OverwriteProperty(???, replaceArray); //use "replaceArray" here ?
}
}
}
else
{
foreach (var property in json.EnumerateObject())
{
await OverwriteProperty(target, property, type, replaceArray); //use "replaceArray" here ?
}
}
return;
}
public async Task OverwriteProperty(object target, JsonProperty updatedProperty, Type type, bool replaceArrays)
{
var propertyInfo = type.GetProperty(updatedProperty.Name);
if (propertyInfo == null)
{
return;
}
var propertyType = propertyInfo.PropertyType;
object parsedValue;
if (propertyType.IsValueType)
{
parsedValue = JsonSerializer.Deserialize(
updatedProperty.Value.GetRawText(),
propertyType);
}
else if (replaceArrays && "property is JsonValueKind.Array") //pseudo code sample
{
// use same code here as in above "IsValueType" ?
}
else
{
parsedValue = propertyInfo.GetValue(target);
await PopulateObjectAsync(
parsedValue,
updatedProperty.Value.GetRawText(),
propertyType);
}
propertyInfo.SetValue(target, parsedValue);
}
Run Code Online (Sandbox Code Playgroud)
我将大量使用我对链接问题的回答中的现有代码:.Net Core 3.0 JsonSerializer populate existing object。
正如我提到的,浅拷贝的代码可以工作并产生结果 2。因此我们只需要修复深拷贝的代码并让它产生结果 1。
PopulateObject在我的机器上,当propertyTypeis typeof(string),sincestring既不是值类型也不是 JSON 中的对象表示的东西时,代码就会崩溃。我在原来的答案中修复了这个问题,如果必须是:
if (elementType.IsValueType || elementType == typeof(string))
Run Code Online (Sandbox Code Playgroud)
好的,第一个问题是识别某个东西是否是集合。目前,我们会查看要覆盖的属性的类型来做出决定,因此现在我们将执行相同的操作。逻辑如下:
if (elementType.IsValueType || elementType == typeof(string))
Run Code Online (Sandbox Code Playgroud)
因此,我们唯一考虑集合的东西是ICollection<T>为某些实现的东西T。我们将通过实现一种新方法来完全单独地处理集合PopulateCollection。我们还需要一种方法来构造一个新集合 - 也许初始对象中的列表是null,因此我们需要在填充它之前创建一个新集合。为此,我们将寻找它的无参数构造函数:
private static bool IsCollection(Type type) =>
type.GetInterfaces().Any(x => x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(ICollection<>));
Run Code Online (Sandbox Code Playgroud)
我们允许它存在private,因为为什么不呢。
现在我们进行一些更改OverwriteProperty:
private static object Instantiate(Type type)
{
var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, Array.Empty<Type>());
if (ctor is null)
{
throw new InvalidOperationException($"Type {type.Name} has no parameterless constructor.");
}
return ctor.Invoke(Array.Empty<object?>());
}
Run Code Online (Sandbox Code Playgroud)
最大的变化是声明的第二个分支if。我们找出集合中元素的类型并从对象中提取现有集合。如果它为空,我们创建一个新的空的。然后我们调用新方法来填充它。
该PopulateCollection方法与 非常相似OverwriteProperty。
private static void OverwriteProperty(object target, JsonProperty updatedProperty, Type type)
{
var propertyInfo = type.GetProperty(updatedProperty.Name);
if (propertyInfo == null)
{
return;
}
if (updatedProperty.Value.ValueKind == JsonValueKind.Null)
{
propertyInfo.SetValue(target, null);
return;
}
var propertyType = propertyInfo.PropertyType;
object? parsedValue;
if (propertyType.IsValueType || propertyType == typeof(string))
{
parsedValue = JsonSerializer.Deserialize(
updatedProperty.Value.GetRawText(),
propertyType);
}
else if (IsCollection(propertyType))
{
var elementType = propertyType.GenericTypeArguments[0];
parsedValue = propertyInfo.GetValue(target);
parsedValue ??= Instantiate(propertyType);
PopulateCollection(parsedValue, updatedProperty.Value.GetRawText(), elementType);
}
else
{
parsedValue = propertyInfo.GetValue(target);
parsedValue ??= Instantiate(propertyType);
PopulateObject(
parsedValue,
updatedProperty.Value.GetRawText(),
propertyType);
}
propertyInfo.SetValue(target, parsedValue);
}
Run Code Online (Sandbox Code Playgroud)
首先我们获取Add集合的方法:
private static void PopulateCollection(object target, string jsonSource, Type elementType)
Run Code Online (Sandbox Code Playgroud)
这里我们期望一个实际的 JSON 数组,所以是时候枚举它了。对于数组中的每个元素,我们需要执行与 in 中相同的操作OverwriteProperty,具体取决于我们是否有值、数组或对象,我们有不同的流程。
foreach (var property in json.EnumerateArray())
{
object? element;
if (elementType.IsValueType || elementType == typeof(string))
{
element = JsonSerializer.Deserialize(jsonSource, elementType);
}
else if (IsCollection(elementType))
{
var nestedElementType = elementType.GenericTypeArguments[0];
element = Instantiate(elementType);
PopulateCollection(element, property.GetRawText(), nestedElementType);
}
else
{
element = Instantiate(elementType);
PopulateObject(element, property.GetRawText(), elementType);
}
addMethod.Invoke(target, new[] { element });
}
Run Code Online (Sandbox Code Playgroud)
现在我们有一个问题。当前的实现将始终添加到集合中,无论其当前内容如何。所以返回的结果既不是 Result 1 也不是 Result 2,而是 Result 3:
var addMethod = target.GetType().GetMethod("Add", new[] { elementType });
Run Code Online (Sandbox Code Playgroud)
我们有一个带有链接 10 和 11 的数组,然后添加了另一个带有链接 11 和 12 的数组。没有明显的自然方法来处理这个问题。我在这里选择的设计决策是:集合决定元素是否已经存在。Contains我们将调用集合上的默认方法,并当且仅当它返回时添加false。它要求我们重写Equals方法来Links比较Id:
foreach (var property in json.EnumerateArray())
{
object? element;
if (elementType.IsValueType || elementType == typeof(string))
{
element = JsonSerializer.Deserialize(jsonSource, elementType);
}
else if (IsCollection(elementType))
{
var nestedElementType = elementType.GenericTypeArguments[0];
element = Instantiate(elementType);
PopulateCollection(element, property.GetRawText(), nestedElementType);
}
else
{
element = Instantiate(elementType);
PopulateObject(element, property.GetRawText(), elementType);
}
addMethod.Invoke(target, new[] { element });
}
Run Code Online (Sandbox Code Playgroud)
现在需要的更改是:
Contains方法:var containsMethod = target.GetType().GetMethod("Contains", new[] { elementType });
Run Code Online (Sandbox Code Playgroud)
element:var contains = containsMethod.Invoke(target, new[] { element });
if (contains is false)
{
addMethod.Invoke(target, new[] { element });
}
Run Code Online (Sandbox Code Playgroud)
Pages我向您和班级添加了一些内容Links,首先我覆盖了ToString这样我们可以轻松检查我们的结果。然后,如前所述,我Equals覆盖Links:
{
"Title": "Startpage",
"Head": "Latest news"
"Links": [
{
"Id": 10,
"Text": "Start",
"Link": "/indexnews"
},
{
"Id": 11,
"Text": "News",
"Link": "/news"
},
{
"Id": 11,
"Text": "News",
"Link": "/news"
},
{
"Id": 21,
"Text": "More news",
"Link": "/morenews"
}
]
}
Run Code Online (Sandbox Code Playgroud)
和测试:
public override bool Equals(object? obj) =>
obj is Links other && Id == other.Id;
public override int GetHashCode() => Id.GetHashCode();
Run Code Online (Sandbox Code Playgroud)
结果:
Initial:
Pages { Title = Startpage, Head = , Links = Links { Id = 10, Text = Start, Link = /index }, Links { Id = 11, Text = Info, Link = /info } }
Update:
Pages { Title = Startpage, Head = Latest news, Links = Links { Id = 10, Text = Start, Link = /index }, Links { Id = 11, Text = Info, Link = /info }, Links { Id = 21, Text = More News, Link = /morenews } }
Run Code Online (Sandbox Code Playgroud)
你可以在这个小提琴中找到它。
Add方法,因此这不适用于 .NET 数组的属性,因为您无法Add操作它们。它们必须单独处理,首先创建元素,然后构造一个适当大小的数组并填充它。Contains对我来说有点不确定。如果能够更好地控制添加到集合中的内容,那就太好了。但这很简单并且有效,所以对于一个 SO 答案来说就足够了。var containsMethod = target.GetType().GetMethod("Contains", new[] { elementType });
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
6134 次 |
| 最近记录: |