Determine if JToken is leaf - c#

I'm trying to dynamically find the names of leaf nodes of a JSON object whose structure isn't known in advance. First, I'm parsing the string into a list of JTokens, like this:
string req = #"{'creationRequestId':'A',
'value':{
'amount':1.0,
'currencyCode':'USD'
}
}";
var tokens = JToken.Parse(req);
Then I'd like to identify which are leaves. In the above example, 'creationRequestId':'A', 'amount':1.0, and 'currencyCode':'USD' are the leaves, and the names are creationRequestId, amount, and currencyCode.
Attempt that works, but is slightly ugly
The below example recursively traverses the JSON tree and prints the leaf names:
public static void PrintLeafNames(IEnumerable<JToken> tokens)
{
foreach (var token in tokens)
{
bool isLeaf = token.Children().Count() == 1 && !token.Children().First().Children().Any();
if (token.Type == JTokenType.Property && isLeaf)
{
Console.WriteLine(((JProperty)token).Name);
}
if (token.Children().Any())
PrintLeafNames(token.Children<JToken>());
}
}
This works, printing:
creationRequestId
amount
currencyCode
However, I'm wondering if there's a less ugly expression for determining whether a JToken is a leaf:
bool isLeaf = token.Children().Count() == 1 && !token.Children().First().Children().Any();
Incidentally, this is a one-liner in XML.

It looks like you've defined a leaf as any JProperty whose value does not have any child values. You can use the HasValues property on the JToken to help make this determination:
public static void PrintLeafNames(IEnumerable<JToken> tokens)
{
foreach (var token in tokens)
{
if (token.Type == JTokenType.Property)
{
JProperty prop = (JProperty)token;
if (!prop.Value.HasValues)
Console.WriteLine(prop.Name);
}
if (token.HasValues)
PrintLeafNames(token.Children());
}
}
Fiddle: https://dotnetfiddle.net/e216YS

Related

Null check in foreach loop of JArray.Children()

I want to check if JArray.Children() is null in the foreach loop.
I can do:
if (jArrayJson == null)
{
return;
}
but I want to do it in the foreach.
This is the different things I have tried:
JArray jArrayJson = (JArray)token.SelectToken("text");
foreach (JToken item in jArrayJson?.Children() ?? Enumerable.Empty<T>()) // Wouldn't compile
foreach (JToken item in jArrayJson?.Children()) // This will fail and not stop a null value
I saw this post about exention method, but I could not integrate the metod:
Check for null in foreach loop
public static IList<T> OrEmptyIfNull<T>(this IList<T> source)
{
return source ?? Array.Empty<T>();
}
foreach (JToken item in jArrayJson?.Children().OrEmptyIfNull()) // Wouldn't compile
Apparently, Children() returns a custom JEnumerable type that is actually a struct, so cannot be null, which makes the null propagation awkward. So you could make this work using your first attempt, with a JToken in the type parameter (like Johnathan Barclay already suggested):
foreach (JToken item in jArrayJson?.Children() ?? Enumerable.Empty<JToken>())
{
}
Or I tweaked your extension method to work off a JToken itself:
public static JEnumerable<JToken> ChildrenOrEmptyIfNull(this JToken token)
{
if(token == null)
{
return new JEnumerable<JToken>();
}
return token.Children();
}
which you could use like:
foreach (JToken item in jArrayJson?.ChildrenOrEmptyIfNull())
{
}

How can I implement custom serialisation of json appending the types to the property names?

I am trying to serialize an object to JSON using newtonsoft.json. The only thing is that I cannot append the json type to the field name. Consider this example:
var item = new {
value = "value",
data = new []{"str", "str"},
b = true
};
I want to convert that to
{
"value.string" : "value",
"data.array" : ["str", "str"],
"b.bool" : true
}
or something similar. The idea is to append the json type (not the c# type) to the json field. The reason I don't want to append the C# type is because it could be complex (sometimes the type is anonymous, sometimes it is IEnumerable, etc.)
I have seen many solutions that can convert to C# type such as implementing a IContractResolver. Unfortunately that doesn't work for this case.
I also do not know the type that I will convert before hand.
The closest I could get to is
public JObject Convert(JObject data)
{
var queue = new Queue<JToken>();
foreach (var child in data.Children())
{
queue.Enqueue(child);
}
while (queue.Count > 0)
{
var token = queue.Dequeue();
if (token is JProperty p)
{
if (p.Value.Type != JTokenType.Object)
{
token.Replace(new JProperty(
$"{p.Name}.{p.Value.Type}",
p.Value
));
}
}
foreach (var child in token.Children())
{
queue.Enqueue(child);
}
}
return data;
}
But it does not work for nested objects like
var result = convertor.Convert(JObject.FromObject(new { nested = new { item = "str"}}));
For some reason, Replace does not work for the nested objects. Not sure if it is a bug or not.
Your main problem is that, when you add a child JToken to a parent, and the child already has a parent, the child is cloned and the clone is added to the parent -- in this case your new JProperty. Then when you replace the original property with the new property, the cloned value hierarchy replaces the original value hierarchy in the overall JToken tree. And finally, when you do
foreach (var child in token.Children())
{
queue.Enqueue(child);
}
You end up looping through the original children that have already been cloned and replaced. While this doesn't matter when the property value is a primitive, it causes the problem you are seeing if the value is an array or other container.
(A secondary, potential issue is that you don't handle the possibility of the root container being an array.)
The fix is to prevent the wholesale cloning of property values by removing the property value from the old property before adding it to the new property, then later looping through the new property's children:
public static class JsonExtensions
{
public static TJToken Convert<TJToken>(this TJToken data) where TJToken : JToken
{
var queue = new Queue<JToken>();
foreach (var child in data.Children())
{
queue.Enqueue(child);
}
while (queue.Count > 0)
{
var token = queue.Dequeue();
if (token is JProperty)
{
var p = (JProperty)token;
if (p.Value.Type != JTokenType.Object)
{
var value = p.Value;
// Remove the value from its parent before adding it to a new parent,
// to prevent cloning.
p.Value = null;
var replacement = new JProperty(
string.Format("{0}.{1}", p.Name, value.Type),
value
);
token.Replace(replacement);
token = replacement;
}
}
foreach (var child in token.Children())
{
queue.Enqueue(child);
}
}
return data;
}
}
Working .Net fiddle.
Why does Json.NET clone the value when adding it to the new JProperty? This happens because there is a bi-directional reference between parents and children in the JToken hierarchy:
JToken.Children() iterates through all child tokens of a given token;
JToken.Parent gets the parent of a given token.
Thus a JToken cannot have two parents -- i.e., it cannot exist in two locations in a JToken hierarchy simultaneously. So when you add the property value to a new JProperty, what should happen to the previous parent? Possibilities include:
The previous parent is unmodified and a clone of the child is added to the new parent.
The previous parent is modified by replacing the child with a clone of its child.
The previous parent is modified by replacing the child with a null JValue.
As it turns out, Json.NET takes option #1, resulting in your bug.

Querying and Filtering Array of JObjects with Linq

I suppose this is another entry in my series of questions, but I'm stuck again. This time, I'm having trouble working with a JArray of JObjects and determining the Property.Value type for each element in the JArray...
My code is here:
https://dotnetfiddle.net/bRcSAQ
The difference between my previous questions and this question is that my outer Linq query gets both JObject and JArray tokens, so that's why I have an if (jo is JObject) at Line 40 and a if (jo is JArray) at Line 48.
Once I know I have a JArray of <JObjects>, I have code that looks like this (Lines 48-):
if (jo is JArray)
{
var items = jo.Children<JObject>();
// return a JObject object
}
When I use a debugger and look at items, I see that it contains 3 JObject objects--one for Item_3A1, Item_3A2, and Item3A3. But I need to know the JTokenType for each JProperty.Value because I am interested only in Property values of type JTokenType.String.
So I tried:
// doesn't work :(
var items = jo.Children<JObject>()
.Where(p => p.Value.Type == JTokenType.String);
The compiler red-lines the Value property with the error CS0119 'JToken.Value<T>(object)' is a method, which is not valid in the given context.
I realize that "p" in the Linq express is not a JProperty. I guess it's a JObject. And I don't know how to cast "p" so that I can examine the type of JProperty object it represents.
Ultimately, I need the code for JArray processing (starting at Line 48) to add an return a JObject that contains an JSON array composed only of JProperty objects of type JTokenType.String. This means that given the sample JSON, it first should return a JObject holding these JSON properties:
{ ""Item_3A1"": ""Desc_3A1"" },
{ ""Item_3A2"": ""Desc_3A2"" },
{ ""Item_3A3"": ""Desc_3A3"" }
On the next iteration, it should return a JObject holding these JSON properties (notice that the nested Array3B1 properties are omitted because Array3B1 is not a JProperty with a Value type of JTokenType.String):
{ ""Item_3B1"": ""Desc_3B1"" },
{ ""Item_3B2"": ""Desc_3B2"" },
The third iteration would contain:
{ ""Item_3B11"": ""Desc_3B11"" },
{ ""Item_3B12"": ""Desc_3B12"" },
{ ""Item_3B13"": ""Desc_3B13"" }
And the fourth (final) iteration would contain:
{ ""Item_3C1"": ""Desc_3C1"" },
{ ""Item_3C2"": ""Desc_3C2"" },
{ ""Item_3C3"": ""Desc_3C3"" }
This might be my final hurdle in this "series".
Sincere thanks to anyone who can and will help--and a special thanks again to users "Brian Rogers" and "dbc" for their truly amazing JSON.NET/Linq knowledge.
This produces the output you require:
var root = (JContainer)JToken.Parse(json);
var query = root.Descendants()
.Where(jt => (jt.Type == JTokenType.Object) || (jt.Type == JTokenType.Array))
.Select(jo =>
{
if (jo is JObject)
{
if (jo.Parent != null && jo.Parent.Type == JTokenType.Array)
return null;
// No help needed in this section
// populate and return a JObject for the List<JObject> result
// next line appears for compilation purposes only--I actually want a populated JObject to be returned
return new JObject();
}
if (jo is JArray)
{
var items = jo.Children<JObject>().SelectMany(o => o.Properties()).Where(p => p.Value.Type == JTokenType.String);
return new JObject(items);
}
return null;
})
.Where(jo => jo != null)
.ToList();
Here I use SelectMany() to flatten the nested enumeration of properties of the enumeration of child objects of jo to a single enumeration of all properties of child objects. o => o.Properties() is a lambda expression mapping the JObject o to its collection of properties, and p => p.Value.Type == JTokenType.String is another lambda mapping a property p (generated by the previous SelectMany clause) to a true/false value indicating whether the property has a string value. Both o and p are lambda input parameters that are implicitly typed.
Also, in the // No help needed in this section section, objects whose parents are arrays are skipped, since they will get collected by the (jo is JArray) clause.
Note that if different children of the jo array happen to have identical property names, the JObject constructor may throw a duplicated key exception.
Forked fiddle.

using c# parse and iterate through json object to address each field

This is a very simple question but can't seem to find a direct answer. I read in a single JSON object. I then want to parse it and be able to directly address a token or a value and then format it for writing a file output, which I will use in another application. I am using C# and the Newtonsoft library.
My code:
JsonTextReader reader = new JsonTextReader(re);
while (reader.Read())
{
if (reader.Value != null)
Console.WriteLine("Value: {0}", "This is the value <Tags>: " + reader.Value);
}
How can I address each line? for example, desc and then Gets a reference to the game world. This must be so commoplace.
Thanks,
johnh
Use the JArray and JObject objects instead, like this:
var json = System.IO.File.ReadAllText("YourJSONFilePath");
var objects = JArray.Parse(json);
foreach(JObject root in objects)
{
foreach(KeyValuePair<String, JToken> tag in root)
{
var tagName = tag.Key;
Console.WriteLine("Value: {0}", "This is the value <Tags>: " + tagName);
}
}
Given a JToken token:
if (token.Type == JTokenType.Object)
{
foreach (var pair in token as JObject)
{
string name = pair.Key;
JToken child = pair.Value;
//do something with the JSON properties
}
}
else if (token.Type == JTokenType.Array)
{
foreach (var child in token.Children())
{
//do something with the JSON array items
}
}
else
{
//do something with a JSON value
}
Have a look at the properties of the reader as it's reading the string. Especially at the TokenType and Value properties. If you really need to read it sequentially that's the way to go. TokenType will be, in order, StartObject, PropertyName, String etc. depending on the node being read. Basically each time you see a PropertyName, the next one will be the property value.
Take note that you'd probably be better off using other techniques but it all depends.
I see that this thread is a bit old... However, #Karl Anderson, your answer was helpful. I just added a little bit to it which was way better than the 3 or 4 nested foreach loops that I had going on... see code below. Thank you for the help!
JArray jsonResponse = JArray.Parse(content);
Debug.WriteLine("\n\njsonResponse: \n" + jsonResponse);
foreach (JObject root in jsonResponse)
{
foreach (KeyValuePair<String, JToken> tag in root)
{
var tagName = tag.Key;
var variable = tag.Value;
Debug.WriteLine("Key: " + tagName + " Value: " + variable);
}
}

Parsing complex JSON Object with C#

I have a JSON like this one:
{
"name" : "MyCustomName",
"my_node" : {
"name" : "my_node_name"
},
"dict1":"value1",
"dict2":"value2",
"dict3":"value3",
...
}
and an object:
class Node{
string value;
}
class Sample:IDictionary<string, string>{
Node node;
string name;
}
Node and Name in Sample class are always present.
The thing is I don't know how many "dictN" fields will be... and that's the point.
And the question is:
How to Deserialize that JSON to this Object?
Edit: apparently even with field names harmonized, your deserializer just can't cope with specific fields combined with general dictionary fields.
In which case, I'd just advise deserializing as a Dictionary<string, object> and building with the result:
var d = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(json);
Sample s = new Sample();
s.name = (string)d["name"];
Node n = new Node();
n.value = (string)((Dictionary<string, object>)d["my_node"])["name"];
foreach (var key in d.Keys.Except(new string[] { "name", "my_node" }))
{
s.Add(key, (string)d[key]);
}
INITIAL IDEA
The following is a dictionary serializer. It has one special case of not accepting empty string.
private void SerializePerinatalModel<T>(IDictionary<string, object> dataModel, T perinatalModel)
{
Type sourceType = typeof(T);
foreach (PropertyInfo propInfo in (sourceType.GetProperties()))
{
if (dataModel.ContainsKey(propInfo.Name))
{
// if an empty string has been returned don't change the value
if (dataModel[propInfo.Name].ToNullSafeString() != String.Empty)
{
try
{
Type localType = propInfo.PropertyType;
localType = Nullable.GetUnderlyingType(localType) ?? localType;
propInfo.SetValue(perinatalModel, Convert.ChangeType(dataModel[propInfo.Name], localType), null);
}
catch (Exception e)
{
// ToDo: log update value errors
}
}
}
}
}
but could be made null safe. It does deal with nullable types.
As JSON is essentially a dictionary type then iterating through the top level types should get you there.
This is written in haste so is only a sketch of an idea.
BETTER IDEA
Also try using
foreach (var item in JsonData.Where(m => m.Key.Substring(0,4) == "dict"))
{
// add item to collection
}
might also do the biz.
You can simply have the output in the form of Dictionary<string, object>, try this piece of code instead.
System.Web.Script.Serialization.JavaScriptSerializer s =
new System.Web.Script.Serialization.JavaScriptSerializer();
var nodes = s.Deserialize<Dictionary<string, object>>(jsonString);
var dictNodes = nodes.Where(w => w.Key.StartsWith("dict"));

Categories

Resources