Mec-Eng
Mec-Eng

Reputation: 199

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));

Upvotes: 1

Views: 2879

Answers (2)

Peter Csala
Peter Csala

Reputation: 22809

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"]
});

Upvotes: 1

Robert Harvey
Robert Harvey

Reputation: 180808

Deserialize the original list, flatten it with Enumerable.SelectMany, and serialize the resulting sequence.

Upvotes: 0

Related Questions