2

This post is an effort to ask a more direct question related to my other recent post (Picking Out Simple Properties from Hierarchical JSON Part II):

Given nested JSON document, as such:

{
  "Array1": {
    "Array1A": [
      { "Item1": "1" },
      { "Item2": "2" },
      { "Item3": "3" }
    ],
    "Array1B": [
      { "Item1": "1" },
      { "Item2": "2" },
      { "Item3": "3" }
    ]
  },
  "Array2": {
    "Array2A": [
      { "Item1": "1" },
      { "Item2": "2" },
      { "Item3": "3" }
    ]
  },
  "Array3": {
    "Array3A": [
      { "Item1": "1" },
      { "Item2": "2" },
      { "Item3": "3" }
    ],
    "Array3B": [
      { "Item1": "1" },
      { "Item2": "2" },
      {
        "Array3B1": [
          { "Item1": "1" },
          { "Item2": "2" },
          { "Item3": "3" }
        ]
      }
    ],
    "Array3C": [
      { "Item1": "1" },
      { "Item2": "2" },
      { "Item3": "3" }
    ]
  }
}

(Note: Above was validated with JSLint.)

Notice that the JSON is dynamic--I do not know in advance how many arrays there will be or how deep the arrays will nest.

Goal: My goal is to represent each array (that is, Array1, Array2, Array3, Array3A, Array3B, and Array3B1) in a List<JObject> object. Each item in the list will be a collection of JProperty objects containing that array's string properties. Because the List doesn't itself model hierarchical data, I would need to add a synthetic property to each List<JObject> item that references the parent of that array. Thus, the parent of Array1 is an empty string; Array2 is Array1, Array3 is Array2, Array3A is Array3, Array3B is Array3, and Array 3B1 is Array3B...

Question: 1. How can I use C# Linq to create a List<JObject> object that looks like this:

list[0]: 
{"Name":"Array1","Parent":""}

list[1]:
{"Name":"Array1A","Item1":"1","Item2":"2","Item3":"3","Parent":"Array1"}

list[2]:
{"Name":"Array1B","Item1":"1","Item2":"2","Item3":"3","Parent":"Array1"}

list[3]:
{"Name":"Array2","Parent":""}

list[4]:
{"Name":"Array2A","Item1":"1","Item2":"2","Item3":"3","Parent":"Array2"}

list[5]:
{"Name":"Array3","Parent":""}

list[6]:
{"Name":"Array3A","Item1":"1","Item2":"2","Item3":"3","Parent":"Array3"}

list[7]:
{"Name":"Array3B","Item1":"1","Item2":"2","Parent":"Array3"}

list[8]:
{"Name":"Array3B1","Item1":"1","Item2":"2","Item3":"3","Parent":"ArrayB"}

list[9]:
{"Name":"Array3C","Item1":"1","Item2":"2","Item3":"3","Parent":"Array3"}

Please note that:

  • Each List<JObject> contains only string properties.
  • At list[7], the JSON token after Item2 is missing because it's an array. Instead, that item is expressed in list[8] with the correct parent reference.

1 Answer 1

9

How about something like this:

List<JObject> list = 
    JObject.Parse(json)
           .Descendants()
           .Where(jt => jt.Type == JTokenType.Property && ((JProperty)jt).Value.HasValues)
           .Cast<JProperty>()
           .Select(prop =>
           {
               var obj = new JObject(new JProperty("Name", prop.Name));
               if (prop.Value.Type == JTokenType.Array)
               {
                   var items = prop.Value.Children<JObject>()
                                         .SelectMany(jo => jo.Properties())
                                         .Where(jp => jp.Value.Type == JTokenType.String);
                   obj.Add(items);
               }
               var parentName = prop.Ancestors()
                                    .Where(jt => jt.Type == JTokenType.Property)
                                    .Select(jt => ((JProperty)jt).Name)
                                    .FirstOrDefault();
               obj.Add("Parent", parentName ?? "");
               return obj;
           })
           .ToList();

Fiddle: https://dotnetfiddle.net/FMxzls

If you're not that familiar with LINQ-to-JSON, here's how it breaks down:

  1. Parse the json string into a JObject

    JObject.Parse(json)
    
  2. From that JObject, get all of its descendant JTokens

           .Descendants()
    
  3. Filter that list to only JProperties whose values have children

           .Where(jt => jt.Type == JTokenType.Property && ((JProperty)jt).Value.HasValues)
    
  4. Cast the JTokens to JProperties to make them easier to work with in the next step

           .Cast<JProperty>()
    
  5. Now, for each JProperty we selected, transform it as follows:

           .Select(prop =>
           {
    
  6. Create a new JObject and add the JProperty's name as the Name property of the new object

               var obj = new JObject(new JProperty("Name", prop.Name));
    
  7. If the value of the JProperty is an array...

               if (prop.Value.Type == JTokenType.Array)
               {
    
  8. Get all the direct children of the array which are JObjects

                   var items = prop.Value.Children<JObject>()
    
  9. From those JObjects, get all the JProperties

                                         .SelectMany(jo => jo.Properties())
    
  10. Filter those JProperties to include only the ones whose values are strings)

                                         .Where(jp => jp.Value.Type == JTokenType.String);
    
  11. Add these item JProperties to the new JObject we created earlier

                    obj.Add(items);
                }
    
  12. Next, find the first ancestor JProperty of the current JProperty and get its name

                var parentName = prop.Ancestors()
                                     .Where(jt => jt.Type == JTokenType.Property)
                                     .Select(jt => ((JProperty)jt).Name)
                                     .FirstOrDefault();
    
  13. Add the parent name to the JObject we're are building; use an empty string if there was no parent

                obj.Add("Parent", parentName ?? "");
    
  14. Continue with the next transform

                return obj;
            })
    
  15. Lastly put all the JObjects we built into a list.

            .ToList();
    
Sign up to request clarification or add additional context in comments.

1 Comment

Worked perfectly. I'm new to Linq so aside from the helpful breakdown of your processing logic, I enjoyed learning how to create variable within a select, how to use SelectMany, how to work with Ancestors(), and how to nest query logic within Linq. I am confident that this answer will prove to be phenomenally helpful to many other C#/JSON.NET/Linq developers...well done!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.