如何从 JsonDocument 中提取具有指定名称的所有 JsonProperty 对象的所有值?
How to extract all values for all JsonProperty objects with a specified name from a JsonDocument?
我有一个自由格式 JsonDocument
,其中包含名称为 internalName
的 JsonProperty
。我的问题是我没有模型来解析它(因为没有预定义的 JSON 模式)并且 属性 可以出现在任何级别,父级,子级等。
如何获取 JSON 属性 internalName
的所有 JsonElement
值?
到目前为止我尝试过的是这样的
var namesUsedInLayout = Layout.RootElement.EnumerateObject().Where(x => x.Name == "internalName").Select(x => x.Value.ToString());
这似乎只为我提供了顶层,而不是子层或子层的子层。
您有一些 JSON 已加载到 JsonElement
(or JsonDocument
) and would like to recursively find all property values for a given property name. As of asp.net-core-3.1 JsonElement
has neither a method equivalent to DescendantsAndSelf()
nor support for JSONPath queries 中,因此您需要编写自己的递归算法。以下是其中之一:
public static partial class JsonExtensions
{
public static IEnumerable<JsonElement> DescendantPropertyValues(this JsonElement element, string name, StringComparison comparison = StringComparison.Ordinal)
{
if (name == null)
throw new ArgumentNullException();
return DescendantPropertyValues(element, n => name.Equals(n, comparison));
}
public static IEnumerable<JsonElement> DescendantPropertyValues(this JsonElement element, Predicate<string> match)
{
if (match == null)
throw new ArgumentNullException();
var query = RecursiveEnumerableExtensions.Traverse(
(Name: (string)null, Value: element),
t =>
{
switch (t.Value.ValueKind)
{
case JsonValueKind.Array:
return t.Value.EnumerateArray().Select(i => ((string)null, i));
case JsonValueKind.Object:
return t.Value.EnumerateObject().Select(p => (p.Name, p.Value));
default:
return Enumerable.Empty<(string, JsonElement)>();
}
}, false)
.Where(t => t.Name != null && match(t.Name))
.Select(t => t.Value);
return query;
}
}
public static partial class RecursiveEnumerableExtensions
{
// Rewritten from the answer by Eric Lippert https://whosebug.com/users/88656/eric-lippert
// to "Efficient graph traversal with LINQ - eliminating recursion"
// to ensure items are returned in the order they are encountered.
public static IEnumerable<T> Traverse<T>(
T root,
Func<T, IEnumerable<T>> children, bool includeSelf = true)
{
if (includeSelf)
yield return root;
var stack = new Stack<IEnumerator<T>>();
try
{
stack.Push(children(root).GetEnumerator());
while (stack.Count != 0)
{
var enumerator = stack.Peek();
if (!enumerator.MoveNext())
{
stack.Pop();
enumerator.Dispose();
}
else
{
yield return enumerator.Current;
stack.Push(children(enumerator.Current).GetEnumerator());
}
}
}
finally
{
foreach (var enumerator in stack)
enumerator.Dispose();
}
}
}
然后像这样使用它:
List<JsonElement> elements;
using (var doc = JsonDocument.Parse(jsonString))
{
elements = doc.RootElement.DescendantPropertyValues(internalName, comparison)
.Select(e => e.Clone()) // Clone the elements before disposing the JsonDocument
.ToList(); // Materialize the query before disposing the JsonDocument
}
备注:
JsonDocument
是一次性的,实际上必须根据docs. If you need the results of your search to survive the lifetime of the document, you must clone元素和具体化查询进行处理以避免内存泄漏。
出于性能原因,我避免多次枚举每个对象并使用显式堆栈而不是嵌套递归枚举。
演示 fiddle here.
我有一个自由格式 JsonDocument
,其中包含名称为 internalName
的 JsonProperty
。我的问题是我没有模型来解析它(因为没有预定义的 JSON 模式)并且 属性 可以出现在任何级别,父级,子级等。
如何获取 JSON 属性 internalName
的所有 JsonElement
值?
到目前为止我尝试过的是这样的
var namesUsedInLayout = Layout.RootElement.EnumerateObject().Where(x => x.Name == "internalName").Select(x => x.Value.ToString());
这似乎只为我提供了顶层,而不是子层或子层的子层。
您有一些 JSON 已加载到 JsonElement
(or JsonDocument
) and would like to recursively find all property values for a given property name. As of asp.net-core-3.1 JsonElement
has neither a method equivalent to DescendantsAndSelf()
nor support for JSONPath queries 中,因此您需要编写自己的递归算法。以下是其中之一:
public static partial class JsonExtensions
{
public static IEnumerable<JsonElement> DescendantPropertyValues(this JsonElement element, string name, StringComparison comparison = StringComparison.Ordinal)
{
if (name == null)
throw new ArgumentNullException();
return DescendantPropertyValues(element, n => name.Equals(n, comparison));
}
public static IEnumerable<JsonElement> DescendantPropertyValues(this JsonElement element, Predicate<string> match)
{
if (match == null)
throw new ArgumentNullException();
var query = RecursiveEnumerableExtensions.Traverse(
(Name: (string)null, Value: element),
t =>
{
switch (t.Value.ValueKind)
{
case JsonValueKind.Array:
return t.Value.EnumerateArray().Select(i => ((string)null, i));
case JsonValueKind.Object:
return t.Value.EnumerateObject().Select(p => (p.Name, p.Value));
default:
return Enumerable.Empty<(string, JsonElement)>();
}
}, false)
.Where(t => t.Name != null && match(t.Name))
.Select(t => t.Value);
return query;
}
}
public static partial class RecursiveEnumerableExtensions
{
// Rewritten from the answer by Eric Lippert https://whosebug.com/users/88656/eric-lippert
// to "Efficient graph traversal with LINQ - eliminating recursion"
// to ensure items are returned in the order they are encountered.
public static IEnumerable<T> Traverse<T>(
T root,
Func<T, IEnumerable<T>> children, bool includeSelf = true)
{
if (includeSelf)
yield return root;
var stack = new Stack<IEnumerator<T>>();
try
{
stack.Push(children(root).GetEnumerator());
while (stack.Count != 0)
{
var enumerator = stack.Peek();
if (!enumerator.MoveNext())
{
stack.Pop();
enumerator.Dispose();
}
else
{
yield return enumerator.Current;
stack.Push(children(enumerator.Current).GetEnumerator());
}
}
}
finally
{
foreach (var enumerator in stack)
enumerator.Dispose();
}
}
}
然后像这样使用它:
List<JsonElement> elements;
using (var doc = JsonDocument.Parse(jsonString))
{
elements = doc.RootElement.DescendantPropertyValues(internalName, comparison)
.Select(e => e.Clone()) // Clone the elements before disposing the JsonDocument
.ToList(); // Materialize the query before disposing the JsonDocument
}
备注:
JsonDocument
是一次性的,实际上必须根据docs. If you need the results of your search to survive the lifetime of the document, you must clone元素和具体化查询进行处理以避免内存泄漏。出于性能原因,我避免多次枚举每个对象并使用显式堆栈而不是嵌套递归枚举。
演示 fiddle here.