Search code examples
c#jsonjson.netflatten

Flatten nested JSON with JSON.NET in C#


I receive a bill of materials in JSON format via a WebApi, which has a corresponding hierarchy. The hierarchy or the nesting can be any depth.

An example bill of materials is shown below:

{
   "Quantity":0,
   "QuantityUnit":"pcs",
   "PartNumber":"12345",
   "Parent":"",
   "Children":[
      {
         "Quantity":1,
         "QuantityUnit":"pcs",
         "PartNumber":"88774",
         "Parent":"12345",
         "Children":[
            {
               "Quantity":1,
               "QuantityUnit":"pcs",
               "PartNumber":"42447",
               "Parent":"88774"
            },
            {
               "Quantity":0.420,
               "QuantityUnit":"kg",
               "PartNumber":"12387",
               "Parent":"88774"
            }
         ]
      }
   ]
}

How can I resolve this nested structure into a simple structure using JSON.NET in C#?

I want to transform it to:

[
   {
      "Quantity":0,
      "QuantityUnit":"pcs",
      "PartNumber":"12345",
      "Parent":""
   },
   {
      "Quantity":1,
      "QuantityUnit":"pcs",
      "PartNumber":"88774",
      "Parent":"12345"
   },
   {
      "Quantity":1,
      "QuantityUnit":"pcs",
      "PartNumber":"42447",
      "Parent":"88774"
   },
   {
      "Quantity":0.420,
      "QuantityUnit":"kg",
      "PartNumber":"12387",
      "Parent":"88774"
   }
]

For the deserialization I use the following class:

public class Bom
{
    public class TopLevel
    {
        public double Quantity { get; set; }
        public string QuantityUnit { get; set; }
        public string PartNumber { get; set; }
        public string Parent { get; set; }
        public List<Item> Children { get; set; }
    }

    public class Item
    {
        public double Quantity { get; set; }
        public string QuantityUnit { get; set; }
        public string PartNumber { get; set; }
        public string Parent { get; set; }
    }

    public double Quantity { get; set; }
    public string QuantityUnit { get; set; }
    public string PartNumber { get; set; }
    public string Parent { get; set; }
    public IList<TopLevel> Children { get; set; }
}

Furthermore, I use this code to deserialize the JSON to an object:

Bom bom = JsonConvert.DeserializeObject<Bom>(File.ReadAllText(jsonPath));

Solution

  • First let's define a mapper

    JObject Map(JObject source)
    {
        var result = (JObject)source.DeepClone();
        result.Remove("Children");
        return result;
    }
    
    • It simply clones the object and removes the Children property

    Next let's define a recursive function to accumulate the JObjects

    void Flatten(JArray children, JArray accumulator)
    {
        if (children == null) return;
        foreach (JObject child in children)
        {
            accumulator.Add(Map(child));
            Flatten((JArray)child["Children"], accumulator);
        }
    }
    

    And finally let's make use of them

    var semiParsed = JObject.Parse(json);
    
    var accumulator = new JArray();
    accumulator.Add(Map(semiParsed));
    Flatten((JArray)semiParsed["Children"], accumulator);
    

    The ToString call on the accumulator will return this

    [
      {
        "Quantity": 0,
        "QuantityUnit": "pcs",
        "PartNumber": "12345",
        "Parent": ""
      },
      {
        "Quantity": 1,
        "QuantityUnit": "pcs",
        "PartNumber": "88774",
        "Parent": "12345"
      },
      {
        "Quantity": 1,
        "QuantityUnit": "pcs",
        "PartNumber": "42447",
        "Parent": "88774"
      },
      {
        "Quantity": 0.42,
        "QuantityUnit": "kg",
        "PartNumber": "12387",
        "Parent": "88774"
      }
    ]
    

    UPDATE #1

    If your source json contains a deep hierarchy (lets say more than 5 levels) then the DeepClone is not really efficient, since you are copying the whole subtree.

    To fix this problem you just need to rewrite the Map function

    JObject Map(JObject source)
    => JObject.FromObject(new
    {
        Quantity = (double)source["Quantity"],
        QuantityUnit = (string)source["QuantityUnit"],
        PartNumber = (string)source["PartNumber"],
        Parent = (string)source["Parent"]
    });