问题
I have a free-form JsonDocument which contains a JsonProperty
with name internalName
. My problem is that that I don't have model to parse it into (because there is no predefined JSON schema) and the property can appear at any level, parent, child etc.
How would one go about getting all the JsonElement
values for the JSON property internalName
?
What I have tried so far is something like this
var namesUsedInLayout = Layout.RootElement.EnumerateObject().Where(x => x.Name == "internalName").Select(x => x.Value.ToString());
This though seem to only provide me the top layer and not the child or the child of child layers.
回答1:
You have some JSON that you have loaded into a 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, so you will need to write your own recursive algorithm. The following is one such:
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://stackoverflow.com/users/88656/eric-lippert
// to "Efficient graph traversal with LINQ - eliminating recursion" https://stackoverflow.com/questions/10253161/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();
}
}
}
And then use it like:
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
}
Notes:
JsonDocument
is disposable and in fact must needs be disposed to avoid a memory leak, according to the docs. If you need the results of your search to survive the lifetime of the document, you must clone the elements and materialize the query.For performance reasons I avoid enumerating each object more than once and use an explicit stack rather than nested recursive enumerables.
Demo fiddle here.
来源:https://stackoverflow.com/questions/60994574/how-to-extract-all-values-for-all-jsonproperty-objects-with-a-specified-name-fro