Jazimov Jazimov - 1 month ago 11
C# Question

Linq-To-JSON Walking Up Ancestor Tree To Obtain JProperty for Nearest Parent

I have a fiddle here:
https://dotnetfiddle.net/x6u5wH

I have two questions but to be fair I'll split them into two posts. I will add a link to the second question at the top of this post once it's posted.

Summary
If you run the above fiddle, you will see that the results show which JProperty is being processed. Fine.

The problem is the value of "Immediate Ancestor Property Name", which currently displays the expected value. But what if I want the value of the ancestor to show the JProperty name of the ancestor that is a the nearest parent? I will explain what I mean by "nearest parent" because there might be an actual JSON term for it that I don't know.

To illustrate, let's focus on the Data7 property in my JSON. Notice that it is the second element in JArray Data5 (Data6 is the first).

QUESTION
What I would like to do is to create a Linq query that looks at the Current Property and then walks up the ancestor tree for the nearest parent property. Starting with Data8, the nearest parent would be Data7. However, the nearest parent to Data7 would be Data4, not Data5. I don't consider Data5 to be the nearest parent because Data5 is a JArray containing Data6 and Data7. When JArrays are involved, I want the parent of the JArray, whether that's a JArray or a JObject. The parent of Data6 would also be Data4.

The parent of Data4 would be Data3; for Data3 the parent would be Data2; and for Data2 the parent would be Data1 (Data would have a null/empty parent).

Thanks.

P.S. In case the fiddle ever goes away, here is the code in question:

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public class Program
{
public static void Main()
{
string json = @"{
""Data1"": {
""SPECIAL"": ""QQ01"",
""AA"": ""QQ"",
""BB"": ""QQ"",
""Data2"": [
{
""SPECIAL"": ""QQ02"",
""AA"": ""QQ"",
""BB"": ""QQ"",
""CC"": ""QQ"",
""Data3"": [
{
""SPECIAL"": ""QQ03"",
""AA"": ""QQ"",
""CC"": ""QQ"",
""Data4"": [
{
""SPECIAL"": ""QQ04"",
""AA"": ""QQ"",
""CC"": ""QQ"",
""Data5"": [
{
""SPECIAL"": ""QQ05"",
""AA"": ""QQ"",
""CC"": ""QQ"",
""Data6"": [
{
""SPECIAL"": ""QQ06"",
""AA"": ""QQ"",
""CC"": ""QQ""
}
]
},
{
""SPECIAL"": ""QQ07"",
""AA"": ""QQ"",
""CC"": ""QQ"",
""DD"": ""QQ"",
""Data7"": [
{
""SPECIAL"": ""QQ08"",
""AA"": ""QQ"",
""CC"": ""QQ"",
""Data8"": [
{
""SPECIAL"": ""QQ09"",
""AA"": ""QQ"",
""BB"": ""QQ"",
""CC"": ""QQ""
}
]
}
]
}
]
}
]
}
]
}
]
},
""Data9"":
{
""SPECIAL"": ""QQ10"",
""AA"": ""QQ"",
""BB"": ""QQ"",
""CC"": ""QQ"",
""Data10"":
{
""SPECIAL"": ""QQ11"",
""AA"": ""QQ"",
""BB"": ""QQ"",
""CC"": ""QQ"",
""Data11"":
{
""SPECIAL"": ""QQ12"",
""AA"": ""QQ"",
""BB"": ""QQ"",
""CC"": ""QQ""
}
}
}
}";

List<JObject> list =
JObject.Parse(json)
.Descendants()
.Where(jt => jt.Type == JTokenType.Property && ((JProperty)jt).Value.HasValues)
.Cast<JProperty>()
.Select(prop =>
{

var parentName = prop.Ancestors()
.Where(jt => jt.Type == JTokenType.Property)
.Select(jt => ((JProperty)jt).Name)
.FirstOrDefault();
var obj = new JObject(new JProperty("Current Property", prop.Name));

obj.Add(new JProperty("Immediate Ancestor Property Name", parentName ?? ""));


// HERE*** (Question 2)
// here I would like to be able to add a new JObject property (just like I do above with "Parent")
// but this time I want to add the property values for Item1 and Item2 for a specific ancestor.
// To achieve this, I want to grab all of the properties for an ancestor.

return obj;
})
.ToList();


for (int i = 0; i < list.Count; i++)
{
Console.WriteLine("list[" + i + "]:");
Console.WriteLine(list[i].ToString(Formatting.None));
Console.WriteLine();
}
}
}

dbc dbc
Answer

It sounds as though you want the lowest parent JProperty whose value either isn't an array, or is an array with just one item. If that's so, you can do:

var list = 
    JObject.Parse(json)
    .Descendants()
    .OfType<JProperty>()
    .Where(prop => prop.Value.HasValues)
    .Select(prop =>
    {

        var parentName = prop.Ancestors()
                             .OfType<JProperty>()
                             .Where(p => !(p.Value is JArray && ((JArray)p.Value).Count > 1))
                             .Select(p => p.Name)
                             .FirstOrDefault();
        var obj = new JObject(new JProperty("Current Property", prop.Name));

        obj.Add(new JProperty("Immediate Ancestor Property Name", parentName ?? ""));


        return obj;
    })
    .ToList();

This gives the required parent listings. Prototype fiddle.

Comments