How to do recursive descent of json using json.net? - c#

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)
{
...
}
});
}

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);
}
}
}
}
}

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");

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);
}
});

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)]");

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.

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());
}
}
}

Related

Modify each string property in flat object, complex object, list, etc

I need to write a generic method that will take any object, loop over its values, and, for each string, modify it (HtmlEncode it in this case).
(this need to execute on all objects submitted to my.net 4.7 API controllers)
So I created an ActionFilterAttribute, and added it to my global filters, and it works fine on "flat" objects.
However, the object can be something like an object with an IList member (each of which will need to be modified) or a nested object/s with its own string properties.
Anyone done anything like this? Got like 10 commented out failed experiments. lol
public override void OnActionExecuting(HttpActionContext actionContext)
{
// Decode strings from PUT or POST requests
if (actionContext.Request.Method.ToString() == WebRequestMethods.Http.Post
|| actionContext.Request.Method.ToString() == WebRequestMethods.Http.Put)
{
// For each of the items in the PUT/POST
foreach (var item in actionContext.ActionArguments.Values)
{
try
{
var type = item.GetType();
// For each property of this object, html decode it if it is of type string
foreach (PropertyInfo propertyInfo in type.GetProperties())
{
var prop = propertyInfo.GetValue(item);
if (prop is string str)
{
propertyInfo.SetValue(item, WebUtility.HtmlEncode(str));
}
}
}
catch (Exception)
{
// ignored
}
}
}
base.OnActionExecuting(actionContext);
}
UPDATE:
Currently testing this:
update: no good...an Int will be identified as Object
public class HttpStringDecodeFilter : ActionFilterAttribute
{
private void HtmlEncodeAllStringsInObject(Object obj)
{
var type = obj.GetType();
var properties = type.GetProperties();
foreach (var property in properties)
{
var propValue = property.GetValue(obj);
if (property.PropertyType.IsAssignableFrom(typeof(string)))
{
property.SetValue(obj, WebUtility.HtmlEncode(propValue?.ToString()));
}
else if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))
{
if (propValue is not List<string> list) continue;
for (var i = 0; i < list.Count; i++)
{
list[i] = WebUtility.HtmlEncode(list[i]);
}
property.SetValue(obj, list);
} else
{
var typeCode = Type.GetTypeCode(property.PropertyType);
if (typeCode.Equals(TypeCode.Object))
{
HtmlEncodeAllStringsInObject(property);
}
}
}
}

Traverse up a list using Linq

I have a class as follows:
public class SiloNode
{
public string Key { get; private set; }
public string ParentKey { get; private set; }
public string Text { get; private set; }
public string Url { get; private set; }
}
All nodes are contained in a list:
List<SiloNode> nodes = new List<SiloNode>();
As you can see, the class contains a ParentKey property, so it's possible to find the item's parent, grandparent etc, until the top of the list is hit.
At present, I need to traverse up 2 levels, and you can see from the code below, it's already looking quite clunky. Now I need to modify the code to traverse up 3 levels and I'm concerned it's getting messy.
Is there a cleaner way to achieve what I want?
string GetStartGroup(string currentUrl)
{
string startGroup = null;
var currentNode = Silos.Silo.SingleOrDefault(x => x.Url == currentUrl);
if (currentNode != null)
{
var parentNode = Silos.Silo.SingleOrDefault(x => x.Key == currentNode.ParentKey);
if (parentNode != null) startGroup = parentNode.ParentKey;
}
return startGroup;
}
Repeatedly using SingleOrDefault on a list makes the algorithm rather slow: finding parents for n nodes requires an O(n2) time.
You should make a Dictionary<string,SiloNode> first, and then traverse up the hierarchy through the dictionary:
var lookup= nodes.ToDictionary(n => n.Key);
...
SiloNode FindParent(SiloNode node, int levelsUp, IDictionary<string,SiloNode> lookup) {
while (node != null && levelsUp != 0) {
if (node.ParentKey == null || !lookup.TryGetValue(node.ParentKey, out var parent)) {
return node;
}
node = parent;
levelsUp--;
}
return node;
}
This will look up a parent up to levelsUp levels up. If you are looking for the last possible parent, modify the code as follows:
SiloNode FindParent(SiloNode node, IDictionary<string,SiloNode> lookup) {
while (true) {
if (node?.ParentKey == null || !lookup.TryGetValue(node.ParentKey, out var parent)) {
return node;
}
node = parent;
}
}
or recursively
SiloNode FindParent(SiloNode node, IDictionary<string,SiloNode> lookup) {
return node?.ParentKey != null && lookup.TryGetValue(node.ParentKey, out var parent)
? FindParent(parent, lookup)
: node;
}
You can do this with recursion.
string GetStartGroup(string currentUrl)
{
var node = nodes.Single(x => x.Url == currentUrl);
if (node.ParentKey == null)
return node.Key;
return GetStartGroup(nodes.Single(x => x.Key == node.ParentKey).Url);
}
Alternatively:
string GetStartGroup(string currentUrl)
{
return GetStartNode(nodes.Single(x => x.Url == currentUrl)).Key;
}
SiloNode GetStartNode(SiloNode node)
{
if (node.ParentKey == null)
return node;
return GetStartNode(nodes.Single(x => x.Key == node.ParentKey));
}
You can change
if (parentNode != null) startGroup = parentNode.ParentKey;
to
if (parentNode != null) startGroup = GetStartGroup(currentNode.parentUrl /*or something similar*/);
However, it would be better to use an iterative loop. I do not know about your problem enough to give you a hint, but the pseudocode would look like this:
while (parentNode != null) {
currentNode = currentNode.parentNode;
parentNode = currentNode.parentNode;
}
You might need to call SingleOrDefault, but if you have a direct reference, you should use that instead.
just put it into a method:
public static Silo Up(Silo current, IEnumerable<Silo> collection)
{
return collection.FirstOrDefault((it) => it.ParentKey == it.Key);
}
or as Extension method:
public static SiloExtensions
{
public static Silo Up(this Silo current, IEnumerable<Silo> collection)
{
return collection.FirstOrDefault((it) => it.ParentKey == it.Key);
}
}
so you can just do silo.Up()?.Up()
Please note that this is rather slow.
Depending on what you actually do, you may want to introduce actual Parent-Object as a field or a wrapper object providing access to it.
Such a wrapper object might look like this:
public class SiloWrapper
{
public Silo Wrapped { get; }
public Silo Parent { get; }
private SiloWrapper(Silo silo, Silo parent)
{
this.Wrapped = silo;
this.Parent = parent;
}
public IEnumerable<SiloWrapper> Map(IEnumerable<Silo> silos)
{
var dict = silos.ToDictionary((s) => s.Key);
foreach(var s in silos)
{
yield return new SiloWrapped(s, s.ParentKey == null ? null : dict[s.ParentKey]);
}
}
}
to then traverse up and down, you just would need to call SiloWrapped.Map(<methodToGetSiloCollection>) and have all wrapped silos ready for usage.
If GarbageCollection may be a concern, you also can use WeakReference<Silo> ParentWeak instead

How to iterate through XML file

How do I iterate over all the elements of the XML tree, and access to them? What I mean is that the input gets unknown xml file, and the code itself it iterates not knowing the number of elements and branches.
I know that was a lot of answers on such topics and the solution was ready to implement the code, where the known structures xml.
This is a code which i am using:
class Program
{
static void Process(XElement element)
{
if (!element.HasElements)
{
Console.WriteLine(element.GetAbsoluteXPath());
}
else
{
foreach (XElement child in element.Elements())
{
Process(child);
}
}
}}
static void Main()
{
var doc = XDocument.Load("C:\\Users\\Błażej\\Desktop\\authors1.xml");
List<string> elements = new List<string>();
Program.Process(doc.Root);
Console.ReadKey();
}
public static class XExtensions
{
public static string GetAbsoluteXPath(this XElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
Func<XElement, string> relativeXPath = e =>
{
int index = e.IndexPosition();
string name = e.Name.LocalName;
return (index == -1) ? "/" + name : string.Format
(
"/{0}[{1}]",
name,
index.ToString()
);
};
var ancestors = from e in element.Ancestors()
select relativeXPath(e);
return string.Concat(ancestors.Reverse().ToArray()) +
relativeXPath(element);
}
public static int IndexPosition(this XElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
if (element.Parent == null)
{
return -1;
}
int i = 1;
foreach (var sibling in element.Parent.Elements(element.Name))
{ // czyli patrzymy na rodzeństwo, czy występują podobne np. au_id[1], au_id[2]
if (sibling == element)
{
return i;
}
else
{
i++;
}
}
throw new InvalidOperationException
("element has been removed from its parent.");
}
Now how can i add some elements to exisitng nodes by iterate ? It's possible ?
As #strongbutgood mentioned, this is very generic.
May be below generic code helps
Dim xdoc As New XDocument();
xdoc = XDocument.Parse(myXML);
// Adding new element
xdoc.Element(<Your_Parent_Node_Name>).Add(new XElement("<New_Element_Name>", "<New_Element_Value>"));
// Deleting existing element
xdoc.Descendants().Where(s =>s.Value == "<Element_To_Be_Removed>").Remove();
Hope this helps.
Please note that this is a generic answer and you will need to re-write as per your needs

How to recursively populate a TreeView with JSON data

I have a winforms treeview, I can read data automatically, (a node that is equal to key, and a node inside that is equal to value), but when reading object type, the values inside it are not going to be child of object node (key of object), (maybe I couldnt explain well, here is a screenshot and my methods.)
layer0 needs to be inside textures and scale needs to be inside display
My Json:
{
"parent": "builtin/generated",
"textures": {
"layer0": "mm:items/iron_dust"
},
"display": {
"scale": [ 1.7, 1.7, 1.7 ]
}
}
My method to auto detect(not all mine actually)
private void Form1_Load(object sender, EventArgs e)
{
StreamReader reader = new StreamReader(path);
string json = reader.ReadToEnd();
reader.Close();
JObject obj = JObject.Parse(json);
getAllProperties(obj);
}
void getAllProperties(JToken children)
{
TreeNode mainNode = treeView1.Nodes[0];
mainNode.Text = Path.GetFileNameWithoutExtension(path);
foreach (JToken child in children.Children())
{
var property = child as JProperty;
if (property != null)
{
if (property.Value.Type == JTokenType.String)
{
TreeNode keyNode = mainNode.Nodes.Add(property.Name);
keyNode.Nodes.Add(property.Value.ToString());
}
if (property.Value.Type == JTokenType.Array)
{
JArray array = (JArray)property.Value;
TreeNode node = mainNode.Nodes.Add(property.Name);
for (int i = 0; i < array.Count; i++)
{
node.Nodes.Add(array[i].ToString());
}
}
if (property.Value.Type == JTokenType.Object)
{
TreeNode topNode = mainNode.Nodes.Add(property.Name.ToString());
foreach (var item in property)
{
if (item.Type == JTokenType.String)
{
if (property.Value.Type == JTokenType.String)
{
TreeNode keyNode = topNode.Nodes.Add(property.Name);
keyNode.Nodes.Add(property.Value.ToString());
}
if (property.Value.Type == JTokenType.Array)
{
JArray array = (JArray)property.Value;
TreeNode node = topNode.Nodes.Add(property.Name);
for (int i = 0; i < array.Count; i++)
{
node.Nodes.Add(array[i].ToString());
}
}
}
}
}
// Console.WriteLine(property.Name + ":" + property.Value);//print all of the values
}
getAllProperties(child);
}
}
}
I tried to get parent, but it didnt have name and value properties :S.
Any help?
(Sorry for language mistakes)
The problem is that, as you recursively descend the JToken hierarchy, you also need to recursively descend the TreeNode hierarchy you are creating, adding child nodes to the parent node just created, rather than the root node, along the lines of Recursion, parsing xml file with attributes into treeview c#.
Thus if you do:
private void Form1_Load(object sender, EventArgs e)
{
using (var reader = new StreamReader(path))
using (var jsonReader = new JsonTextReader(reader))
{
var root = JToken.Load(jsonReader);
DisplayTreeView(root, Path.GetFileNameWithoutExtension(path));
}
}
private void DisplayTreeView(JToken root, string rootName)
{
treeView1.BeginUpdate();
try
{
treeView1.Nodes.Clear();
var tNode = treeView1.Nodes[treeView1.Nodes.Add(new TreeNode(rootName))];
tNode.Tag = root;
AddNode(root, tNode);
treeView1.ExpandAll();
}
finally
{
treeView1.EndUpdate();
}
}
private void AddNode(JToken token, TreeNode inTreeNode)
{
if (token == null)
return;
if (token is JValue)
{
var childNode = inTreeNode.Nodes[inTreeNode.Nodes.Add(new TreeNode(token.ToString()))];
childNode.Tag = token;
}
else if (token is JObject)
{
var obj = (JObject)token;
foreach (var property in obj.Properties())
{
var childNode = inTreeNode.Nodes[inTreeNode.Nodes.Add(new TreeNode(property.Name))];
childNode.Tag = property;
AddNode(property.Value, childNode);
}
}
else if (token is JArray)
{
var array = (JArray)token;
for (int i = 0; i < array.Count; i++)
{
var childNode = inTreeNode.Nodes[inTreeNode.Nodes.Add(new TreeNode(i.ToString()))];
childNode.Tag = array[i];
AddNode(array[i], childNode);
}
}
else
{
Debug.WriteLine(string.Format("{0} not implemented", token.Type)); // JConstructor, JRaw
}
}
You will get the following tree view structure:
Here is my crack at it. The output is identical to Notepad++'s JSTool plug-in:
The code is structured as a TreeView extension:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Windows.Forms;
namespace TestDLApp.Utilities.Extensions
{
public static class ObjectToTreeView
{
private sealed class IndexContainer
{
private int _n;
public int Inc() => _n++;
}
private static void FillTreeView(TreeNode node, JToken tok, Stack<IndexContainer> s)
{
if (tok.Type == JTokenType.Object)
{
TreeNode n = node;
if(tok.Parent != null)
{
if(tok.Parent.Type == JTokenType.Property)
{
n = node.Nodes.Add($"{((JProperty)tok.Parent).Name} <{tok.Type.ToString()}>");
}
else
{
n = node.Nodes.Add($"[{s.Peek().Inc()}] <{tok.Type.ToString()}>");
}
}
s.Push(new IndexContainer());
foreach (var p in tok.Children<JProperty>())
{
FillTreeView(n, p.Value, s);
}
s.Pop();
}
else if (tok.Type == JTokenType.Array)
{
TreeNode n = node;
if(tok.Parent != null)
{
if (tok.Parent.Type == JTokenType.Property)
{
n = node.Nodes.Add($"{((JProperty)tok.Parent).Name} <{tok.Type.ToString()}>");
}
else
{
n = node.Nodes.Add($"[{s.Peek().Inc()}] <{tok.Type.ToString()}>");
}
}
s.Push(new IndexContainer());
foreach (var p in tok)
{
FillTreeView(n, p, s);
}
s.Pop();
}
else
{
var name = string.Empty;
var value = JsonConvert.SerializeObject(((JValue)tok).Value);
if (tok.Parent.Type == JTokenType.Property)
{
name = $"{((JProperty)tok.Parent).Name} : {value}";
}
else
{
name = $"[{s.Peek().Inc()}] : {value}";
}
node.Nodes.Add(name);
}
}
public static void SetObjectAsJson<T>(this TreeView tv, T obj)
{
tv.BeginUpdate();
try
{
tv.Nodes.Clear();
var s = new Stack<IndexContainer>();
s.Push(new IndexContainer());
FillTreeView(tv.Nodes.Add("ROOT"), JsonConvert.DeserializeObject<JToken>(JsonConvert.SerializeObject(obj)), s);
s.Pop();
}
finally
{
tv.EndUpdate();
}
}
}
}
You can call it as so:
treeView1.SetObjectAsJson(new MyNeatObject());

Json.net rename properties

I have a string representing JSON and I want to rename some of the properties using JSON.NET. I need a generic function to use for any JSON. Something like:
public static void Rename(JContainer container, Dictiontionary<string, string> mapping)
{
foreach (JToken el in container.Children())
{
JProperty p = el as JProperty;
if(el != null && mapping.ContainsKey(p.Name))
{
// **RENAME THIS NODE!!**
}
// recursively rename nodes
JContainer pcont = el as JContainer;
if(pcont != null)
{
Rename(pcont, mapping);
}
}
}
How to do it??
I would suggest reconstructing your JSON with renamed properties. I don't think you should worry about speed penalties as it's usually not an issue. Here's how you can do it.
public static JToken Rename(JToken json, Dictionary<string, string> map)
{
return Rename(json, name => map.ContainsKey(name) ? map[name] : name);
}
public static JToken Rename(JToken json, Func<string, string> map)
{
JProperty prop = json as JProperty;
if (prop != null)
{
return new JProperty(map(prop.Name), Rename(prop.Value, map));
}
JArray arr = json as JArray;
if (arr != null)
{
var cont = arr.Select(el => Rename(el, map));
return new JArray(cont);
}
JObject o = json as JObject;
if (o != null)
{
var cont = o.Properties().Select(el => Rename(el, map));
return new JObject(cont);
}
return json;
}
And here's an example of usage:
var s = #"{ ""A"": { ""B"": 1, ""Test"": ""123"", ""C"": { ""Test"": [ ""1"", ""2"", ""3"" ] } } }";
var json = JObject.Parse(s);
var renamed = Rename(json, name => name == "Test" ? "TestRenamed" : name);
renamed.ToString().Dump(); // LINQPad output
var dict = new Dictionary<string, string> { { "Test", "TestRenamed"} };
var renamedDict = Rename(json, dict);
renamedDict.ToString().Dump(); // LINQPad output
We use this approach. You can find the property you want using JObject's SelectToken(). Yes it does support JsonPath.
public static class NewtonsoftExtensions
{
public static void Rename(this JToken token, string newName)
{
var parent = token.Parent;
if (parent == null)
throw new InvalidOperationException("The parent is missing.");
var newToken = new JProperty(newName, token);
parent.Replace(newToken);
}
}

Categories