22

I am trying to parse a json file using json.net. The file looks like this

{X:
   {
      Title:"foo",
      xxxx:xxxx
   }
}
{Y:
   {ZZ:
        {Title: "bar",...}
    }
}

I am trying to recurse down this structure processing all objects with a Title attribute. But I am confused about JToken, JProperty, JContainer, JValue, JObject. Reading the source code has not left me much wiser and none of the samples help. I want something along the lines of

WalkNode(node, Action<Node> action)
{
    foreach(var child in node.Children)
    {
        Action(child);
        WalkNode(child);
    }
}

Parse()
{
   WalkNode(root, n=>
    {
        if(n["Title"] != null)
        {
           ...
        }
    });
}
1
  • 2
    Some questions: The above example is not valid JSON. Are the containing objects of properties X and Y supposed to be in an array, or did you intend for X and Y to be in the same containing object? Also, will there be arrays anywhere in the JSON hierarchy that you need to traverse, or is it just nested objects and properties? Commented Apr 25, 2013 at 5:49

6 Answers 6

35

The code below should be pretty close to what you are looking for. I made the assumption that there is an outer array, and that arrays can appear anywhere in the hierarchy. (If this is not true, you can simplify the WalkNode method code a bit, but it should work either way.)

using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace JsonRecursiveDescent
{
    class Program
    {
        static void Main(string[] args)
        {
            string json =
            @"[
                {
                    ""X"":
                    {
                        ""Title"":""foo"",
                        ""xxxx"":""xxxx""
                    }
                },
                {
                    ""Y"":
                    {
                        ""ZZ"":
                        {
                            ""Title"":""bar"",
                            ""xxxx"":""xxxx""
                        }
                    }
                }
            ]";

            JToken node = JToken.Parse(json);

            WalkNode(node, n =>
            {
                JToken token = n["Title"];
                if (token != null && token.Type == JTokenType.String)
                {
                    string title = token.Value<string>();
                    Console.WriteLine(title);
                }
            });
        }

        static void WalkNode(JToken node, Action<JObject> action)
        {
            if (node.Type == JTokenType.Object)
            {
                action((JObject)node);

                foreach (JProperty child in node.Children<JProperty>())
                {
                    WalkNode(child.Value, action);
                }
            }
            else if (node.Type == JTokenType.Array)
            {
                foreach (JToken child in node.Children())
                {
                    WalkNode(child, action);
                }
            }
        }

    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

But if I don't know that "Title" exists and I want all the properties?
That is a different question. If you want to post a new question describing what you're trying to do, I will be happy to answer it. Be sure to tag it json.net.
24

Also needed to do something of the sorts. Would like to propose my solution. It has the advantage of:

  • not being recursive
  • no callbacks
  • not assuming any internal structure (arrays)
  • decouples tree traversal from the action needed to be executed

    IEnumerable<JToken> AllTokens(JObject obj) {
        var toSearch = new Stack<JToken>(obj.Children());
        while (toSearch.Count > 0) {
            var inspected = toSearch.Pop();
            yield return inspected;
            foreach (var child in inspected) {
                toSearch.Push(child);
            }
        }
    }
    

    Then you can use linq to filter and perform action:

    var tokens = AllTokens(jsonObj);
    var titles = tokens.Where(t => t.Type == JTokenType.Property && ((JProperty)t).Name == "Title");
    

1 Comment

how-to lowername all nodes from json string or JObject ?
10

I thought I'd include my minor tweaks to @BrianRogers WalkNode method, made it slightly more versatile:

private static void WalkNode(JToken node,
                                Action<JObject> objectAction = null,
                                Action<JProperty> propertyAction = null)
{
    if (node.Type == JTokenType.Object)
    {
        if (objectAction != null) objectAction((JObject) node);

        foreach (JProperty child in node.Children<JProperty>())
        {
            if (propertyAction != null) propertyAction(child);
            WalkNode(child.Value, objectAction, propertyAction);
        }
    }
    else if (node.Type == JTokenType.Array)
    {
        foreach (JToken child in node.Children())
        {
            WalkNode(child, objectAction, propertyAction);
        }
    }
}

Then OP could do something like:

WalkNode(json, null, prop =>
{
     if (prop.Name == "Title" && prop.Value.Type == JTokenType.String)
     {
         string title = prop.Value<string>();
         Console.WriteLine(title);
     }
});

Comments

9

You could also do it with JSONPath: node.SelectTokens("$..*");

Used like this:

var jObjectsWithTitle = node
    .SelectTokens("$..*")
    .OfType<JObject>()
    .Where(x => x.Property("Title") != null);

Or just:

var jObjectsWithTitle = node.SelectTokens("$..[?(@.Title)]");

Comments

1

Try this Method I have written it after some unsuccessful tries:

 private void Traverse(JToken token, TreeNode tn)
    {
        if (token is JProperty)
            if (token.First is JValue)
                tn.Nodes.Add(((JProperty)token).Name + ": " + ((JProperty)token).Value);
            else
                tn = tn.Nodes.Add(((JProperty)token).Name);

        foreach (JToken token2 in token.Children())
            Traverse(token2, tn);
    }

You first have to pass it the complete JSON file like this:

TreeNode rooty= tvu.Nodes.Add("Rooty") // not the Indian bread,just Rooty,  Ok?
JToken token = JToken.Parse(File.ReadAllText(<"Path to json file">));
Traverse(token, rooty);

Done, Boom you got this one: Oh no, I am not allowed to embed pictures. sad.

Comments

0

I wrapped the data with a "root" node, and wrapped it all as an array value. Then this works for me:

    private static void TraverseJson(string jsonText)
    {
        dynamic jsonObject = JsonConvert.DeserializeObject(jsonText);

        JArray ja = (JArray)jsonObject.root;
        int itemCount = ja.Count;

        foreach (JObject jobj in jsonObject.root)
        {
            WalkHierarchy(jobj);
        }
    }

    private static void WalkHierarchy(JObject jobj)
    {
        foreach (JProperty jprop in (JToken)jobj)
        {
            if (jprop.Value.Type == JTokenType.Object)
            {
                WalkHierarchy((JObject)jprop.Value);
            }
            else
            {
                Console.WriteLine(jprop.Value.ToString());
            }
        }
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.