How to extract all values for all JsonProperty objects with a specified name from a JsonDocument? [closed]

元气小坏坏 提交于 2020-04-17 16:49:29

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!