0

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();
            }
        }
    }
Jazimov
  • 12,626
  • 9
  • 52
  • 59
  • You do realize that in your JSON, *ALL* of the `DataX` properties hold JArrays except `Data1`? `Data5` is the only array that has more than one child, but the others are still arrays, even though they only have one child each. – Brian Rogers Oct 28 '16 at 16:04
  • Hi Brian, well, my laziness. I just reused my JSON from the previous question you answered. My actual JSON (thousands of bytes) does have DataX elements that aren't arrays. I modified the JSON in my question and updated the fidder to reflect the change by adding another DataX block to the end that isn't an array. Thanks. – Jazimov Oct 28 '16 at 17:16
  • Do you really want parents whose values aren't of type `JArray`, or do you actually want parents whose values aren't a `JArray` with multiple elements? For the former, all the parents turn out to be `"Data1"`, see [here](https://dotnetfiddle.net/P4RHud). For the latter, see [here](https://dotnetfiddle.net/vl5ksi). – dbc Oct 28 '16 at 17:53
  • I need to ponder that after seeing your examples. However, it seems that your two fiddle links (each different) contain the same code and results... Thank you. – Jazimov Oct 28 '16 at 19:46
  • @Jazimov - Either I or dotnetfiddle seem to have gotten confused with multiple forked fiddles open in multiple windows. Let's try https://dotnetfiddle.net/oxHSjU for the second fiddle. – dbc Oct 28 '16 at 20:10
  • 1
    In an unusual turn of events, the second fiddle wins. :) It's exactly what I needed. Please submit as a proposed answer and I will accept and award. Very clever what you did in the lambda with .Count--I will study to customize further if needed. Many thanks. – Jazimov Oct 29 '16 at 02:16

1 Answers1

1

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.

dbc
  • 104,963
  • 20
  • 228
  • 340