Searching for a specific JToken by name in a JObject hierarchy
If you are looking for a very specific token and know the path to it, you can navigate to it easily using the built-in SelectToken()
method. For example:
string distance = jObject.SelectToken("routes[0].legs[0].distance.text").ToString();
If you need to find all occurences of a token with a given name in your JSON, no matter where they occur, then yes you'd need a recursive method. Here is one that might do the trick:
public static class JsonExtensions
{
public static List<JToken> FindTokens(this JToken containerToken, string name)
{
List<JToken> matches = new List<JToken>();
FindTokens(containerToken, name, matches);
return matches;
}
private static void FindTokens(JToken containerToken, string name, List<JToken> matches)
{
if (containerToken.Type == JTokenType.Object)
{
foreach (JProperty child in containerToken.Children<JProperty>())
{
if (child.Name == name)
{
matches.Add(child.Value);
}
FindTokens(child.Value, name, matches);
}
}
else if (containerToken.Type == JTokenType.Array)
{
foreach (JToken child in containerToken.Children())
{
FindTokens(child, name, matches);
}
}
}
}
Here is a demo:
class Program
{
static void Main(string[] args)
{
string json = @"
{
""routes"": [
{
""bounds"": {
""northeast"": {
""lat"": 50.4639653,
""lng"": 30.6325177
},
""southwest"": {
""lat"": 50.4599625,
""lng"": 30.6272425
}
},
""legs"": [
{
""distance"": {
""text"": ""1.7 km"",
""value"": 1729
},
""duration"": {
""text"": ""4 mins"",
""value"": 223
}
},
{
""distance"": {
""text"": ""2.3 km"",
""value"": 2301
},
""duration"": {
""text"": ""5 mins"",
""value"": 305
}
}
]
}
]
}";
JObject jo = JObject.Parse(json);
foreach (JToken token in jo.FindTokens("text"))
{
Console.WriteLine(token.Path + ": " + token.ToString());
}
}
}
Here is the output:
routes[0].legs[0].distance.text: 1.7 km
routes[0].legs[0].duration.text: 4 mins
routes[0].legs[1].distance.text: 2.3 km
routes[0].legs[1].duration.text: 5 mins
Searching for specific JToken in JObject hierarchy and store them in another JObject
Based on the sample JSON you've shown in your question, you should be able to get the result you want like this:
JObject obj = JObject.Parse(json);
JArray result = new JArray(
obj.SelectToken("memberdetails[0].properties")
.Select(jt => new JObject(new JProperty((string)jt["alias"],jt["value"]))));
Fiddle: https://dotnetfiddle.net/DOfKaJ
How can I call the value from a Jtoken
You can use SelectToken
wherein you can select your desire value base on their path. See this reference.
JObject json = JObject.Parse(content);
var value = json.SelectToken("role").Value<string>();
Add a path to select role
. The path will depend on your JSON structure.
Example value of content
{
response : {
role : volunteer,
success: true,
token: eyJhbGci...
}
}
JObject json = JObject.Parse(content);
var value = json.SelectToken("response.role").Value<string>();
Read json value using C# without using Property
You could use
var jo = JObject.Parse(json);
var testCaseName = (string)jo.SelectToken($"$.value[?(@id=={idToSearch})].testCase.name");
Update
Base on your question in comment, you could find the ID with test case name using
jo.SelectTokens($"$.value[?(@testCase.name=='{nameToSearch}')].id")
Please note in this case, you would need to use .SelectTokens
as there is chances of duplicates as give in the example in OP. You could get all the results or the first based on your requirement.
var idList = jo.SelectTokens($"$.value[?(@testCase.name=='{nameToSearch}')].id")
.Select(x=> long.Parse(x.ToString()));
var onlyFirst = (long)jo.SelectTokens($"$.value[?(@testCase.name=='{nameToSearch}')].id")
.First();
Iterate over a json input and create the treeview like hierarchy based on the keys taking account of the children keys
What you want to do is to map your deeply nested JSON into a c# tree where each node has two properties -- a string key
and a long T_id
-- as well as a collection of children of the same type.
You could model this as follows, using a list:
public partial class KeyIdObject
{
public string key { get; set; }
public long T_id { get; set; }
public List<KeyIdObject> Children { get; set; }
}
Once you have you data model, you need to use a recursive algorithm to generate your nodes. A related algorithms is shown in Searching for a specific JToken by name in a JObject hierarchy, but you need a two-stage recursion:
Descend through the
JToken
hierarchy until you find aJObject
with aT_id
property.Once you have found a match, construct a
KeyIdObject
for it and populate its list of children by searching the matching JObject's children using a nested recursive search.Then move on to the matches's next sibling in the outer recursive search.
This can be accomplished by introducing an extension method that searches for the topmost descendants of a given JToken
that match a given condition:
public static partial class JsonExtensions
{
/// <summary>
/// Enumerates through all descendants of the given element, returning the topmost elements that match the given predicate
/// </summary>
/// <param name="root"></param>
/// <param name="filter"></param>
/// <returns></returns>
public static IEnumerable<TJToken> TopDescendantsWhere<TJToken>(this JToken root, Func<TJToken, bool> predicate) where TJToken : JToken
{
if (predicate == null)
throw new ArgumentNullException();
return GetTopDescendantsWhere<TJToken>(root, predicate, false);
}
static IEnumerable<TJToken> GetTopDescendantsWhere<TJToken>(JToken root, Func<TJToken, bool> predicate, bool includeSelf) where TJToken : JToken
{
if (root == null)
yield break;
if (includeSelf)
{
var currentOfType = root as TJToken;
if (currentOfType != null && predicate(currentOfType))
{
yield return currentOfType;
yield break;
}
}
var rootContainer = root as JContainer;
if (rootContainer == null)
yield break;
var current = root.First;
while (current != null)
{
var currentOfType = current as TJToken;
var isMatch = currentOfType != null && predicate(currentOfType);
if (isMatch)
yield return currentOfType;
// If a match, skip children, but if not, advance to the first child of the current element.
var next = (isMatch ? null : current.FirstChild());
if (next == null)
// If no first child, get the next sibling of the current element.
next = current.Next;
// If no more siblings, crawl up the list of parents until hitting the root, getting the next sibling of the lowest parent that has more siblings.
if (next == null)
{
for (var parent = current.Parent; parent != null && parent != root && next == null; parent = parent.Parent)
{
next = parent.Next;
}
}
current = next;
}
}
static JToken FirstChild(this JToken token)
{
var container = token as JContainer;
return container == null ? null : container.First;
}
}
Then, you can use it to generate a recursive List<KeyIdObject>
like so:
public partial class KeyIdObject
{
public static List<KeyIdObject> ToIdObjects(JToken root)
{
return root.TopDescendantsWhere<JObject>(o => o["T_id"] != null)
.Select(o => new KeyIdObject { key = ((JProperty)o.Parent).Name, T_id = (long)o["T_id"], Children = ToIdObjects(o) })
.ToList();
}
}
Demo fiddle #1 here, which generates the following structure:
[
{
"key": "Soccer",
"T_id": 0,
"Children": [
{
"key": "ClubA",
"T_id": 1
},
{
"key": "ClubB",
"T_id": 2
},
{
"key": "SubA",
"T_id": 3,
"Children": [
{
"key": "SubE",
"T_id": 3
}
]
},
{
"key": "SubK",
"T_id": 3
}
]
}
]
However, in your JSON some of your object node(s), specifically "Clubs"
and "Subs"
, do not have a T_id
property. Thus, they can't be captured into the node hierarchy as there is no way to populate the long T_id
value. If you do need to capture these nodes, you can modify your data model to have a nullable value for the id and capture the intermediate nodes as follows:
public partial class KeyIdObject
{
public string key { get; set; }
public long? T_id { get; set; }
public List<KeyIdObject> Children { get; set; }
}
public partial class KeyIdObject
{
public static List<KeyIdObject> ToIdObjects(JToken root)
{
return root.TopDescendantsWhere<JObject>(o => true)
.Select(o => new KeyIdObject { key = ((JProperty)o.Parent).Name, T_id = (long?)o["T_id"], Children = ToIdObjects(o) })
.ToList();
}
}
Demo fiddle #2 here.
Finally, if you are sure your keys are unique at any given level, you could use a dictionary instead of a list, like so:
public partial class IdObject
{
public long T_id { get; set; }
public Dictionary<string, IdObject> Children { get; set; }
}
public partial class IdObject
{
public static Dictionary<string, IdObject> ToIdObjects(JToken root)
{
return root.TopDescendantsWhere<JObject>(o => o["T_id"] != null)
.ToDictionary(o => ((JProperty)o.Parent).Name,
o => new IdObject { T_id = (long)o["T_id"], Children = ToIdObjects(o) });
}
}
Demo fiddle #3 here.
Note that, in all cases, I chose long
instead of int
for the T_id
for safety.
Update
If you are going to bind this into a WPF TreeView
or something similar like a Syncfusion.Xamarin.SfTreeView
, you will want to implement INotifyPropertyChanged
and use ObservableCollection<T>
. You may also want to use a different ItemTemplate
for nodes with and without T_id
values, in which case you can define a different c# POCO for each case. The following is one example:
public abstract partial class KeyItemBase : INotifyPropertyChanged
{
public KeyItemBase() : this(null, Enumerable.Empty<KeyItemBase>()) { }
public KeyItemBase(string key, IEnumerable<KeyItemBase> children)
{
this.m_key = key;
this.m_children = new ObservableCollection<KeyItemBase>(children);
}
string m_key;
public string key
{
get { return m_key; }
set
{
m_key = value;
RaisedOnPropertyChanged("key");
}
}
ObservableCollection<KeyItemBase> m_children;
public ObservableCollection<KeyItemBase> Children { get { return m_children; } }
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisedOnPropertyChanged(string _PropertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
changed(this, new PropertyChangedEventArgs(_PropertyName));
}
}
}
public abstract partial class KeyItemBase
{
// Generate clean JSON on re-serialization.
public bool ShouldSerializeChildren() { return Children != null && Children.Count > 0; }
}
public sealed class KeyItem : KeyItemBase
{
// Use for a JSON object with no T_id property.
// Bind an appropriate SfTreeView.ItemTemplate to this type.
public KeyItem() : base() { }
public KeyItem(string key, IEnumerable<KeyItemBase> children) : base(key, children) { }
}
public class KeyIdItem : KeyItemBase
{
// Use for a JSON object with a T_id property.
// Bind an appropriate SfTreeView.ItemTemplate to this type.
public KeyIdItem() : base() { }
public KeyIdItem(string key, IEnumerable<KeyItemBase> children, long t_id) : base(key, children) { this.m_id = t_id; }
long m_id;
public long T_id
{
get { return m_id; }
set
{
m_id = value;
RaisedOnPropertyChanged("T_id");
}
}
}
public static class KeyItemFactory
{
public static KeyItemBase ToKeyObject(string name, long? id, IEnumerable<KeyItemBase> children)
{
if (id == null)
return new KeyItem(name, children);
else
return new KeyIdItem(name, children, id.Value);
}
public static IEnumerable<KeyItemBase> ToKeyObjects(JToken root)
{
return root.TopDescendantsWhere<JObject>(o => true)
.Select(o => ToKeyObject(((JProperty)o.Parent).Name, (long?)o["T_id"], ToKeyObjects(o)));
}
}
Which you would use as follows:
var items = new ObservableCollection<KeyItemBase>(KeyItemFactory.ToKeyObjects(root));
// Now bind items to your ItemsSource
// https://help.syncfusion.com/cr/cref_files/xamarin/Syncfusion.SfTreeView.XForms~Syncfusion.XForms.TreeView.SfTreeView~ItemsSource.html
Demo fiddle #4 here.
Related Topics
Is Int[] a Reference Type or a Value Type
Why Would I Ever Need to Use C# Nested Classes
Most Efficient Way to Test Equality of Lambda Expressions
Format Excel Column to Decimal Doing Export from C#
What Does Null! Statement Mean
Get Mime Type from Filename Extension
String.Split - by Multiple Character Delimiter
A Field Initializer Cannot Reference the Non-Static Field, Method, or Property
Performance Surprise with "As" and Nullable Types
How to Download HTML Source in C#
C# Object Pooling Pattern Implementation
Changing the User Agent of the Webbrowser Control
Single Controller with Multiple Get Methods in ASP.NET Web API