0

Below is the sample json

{
"Count": 185,
"Message": "Results returned successfully",
"SearchCriteria": "Make ID:474 | ModelYear:2016",
"Results": [{
        "Make_ID": 474,
        "Make_Name": "Honda",
        "Model_ID": 1861,
        "Model_Name": "i10",
        "owners": [{
                "name": "Balaji",
                "address": [{
                        "city": "kcp",
                        "pincode": "12345"
                    }
                ]
            }, {
                "name": "Rajesh",
                "address": [{
                        "city": "chennai",
                        "pincode": "12346"
                    }
                ]
            }
        ]
    }, {
        "Make_ID": 475,
        "Make_Name": "Honda",
        "Model_ID": 1862,
        "Model_Name": "i20",
        "owners": [{
                "name": "Vijay",
                "address": [{
                        "city": "madurai",
                        "pincode": "12347"
                    }
                ]
            }, {
                "name": "Andrej",
                "address": [{
                        "city": "Berlin",
                        "pincode": "12348"
                    }
                ]
            }
        ]
    }
]}

Below is the XML config to build data table (assume getting from user)

<DataTableConfig Name="CityInfo">
<Property Name="Make_ID" Path="Results[*].Make_ID"/>
<Property Name="Model_ID" Path="Results[*].Model_ID" /> 
<Property Name="owner" Path="Results[*].owners[*].name"/>
<Property Name="city" Path="Results[*].owners[*].address[*].city"/></DataTableConfig>

The expected result is as below

enter image description here

With the below code I am trying to parse the json and building the data table based on the config.

I am getting column values in separate row and not working as expected

string xmlConfig = File.ReadAllText(@"C:\Temp\xmlConfig.txt");

        XmlSerializer serializer = new XmlSerializer(typeof(DataTableConfig));
        StringReader stringReader = new StringReader(xmlConfig);
        DataTableConfig config = (DataTableConfig)serializer.Deserialize(stringReader);
        DataTable dataTable = new DataTable();
        foreach (Property p in config.Property)
        {
            dataTable.Columns.Add(p.Name);
        }

        dataTable.Columns.Add("Path"); //can be removed after building the table
        string jdata = File.ReadAllText(@"C:\Temp\json.txt");
        JObject json = JObject.Parse(jdata);
        foreach (Property p in config.Property)
        {

            var jTokens = json.SelectTokens(p.Path);

            foreach (JToken token in jTokens)
            {

                string parentPath = token.Parent.Parent.Path;
                string searchExpression = $"Path = '{parentPath}'";
                DataRow[] foundRows = dataTable.Select(searchExpression);                    

                if (foundRows.Count() > 0)
                {
                    string value = token.Value<string>();
                    foundRows[0][p.Name] = value;
                }
                else
                {
                    string value = token.Value<string>();
                    DataRow toInsert = dataTable.NewRow();
                    toInsert[p.Name] = value;
                    toInsert["Path"] = parentPath;
                    dataTable.Rows.Add(toInsert);
                }

            }
        }

        foreach (DataRow row in dataTable.Rows)
        {
            StringWriter sw = new System.IO.StringWriter();
            foreach (DataColumn col in dataTable.Columns)
                sw.Write(row[col].ToString() + "\t");
            string output = sw.ToString();
            Console.WriteLine(output);
        }
        Console.ReadLine();

Any hints/tips are much appreciated. Thanks.

1
  • Here is the solution dotnetfiddle.net/TCnfVs , still looking for better one. Commented Jan 6, 2020 at 8:56

3 Answers 3

1

This is the code i used to create the table you are looking for. I created a ToList() method within Results to get the data you need to add to the table as rows.

    string jdata = File.ReadAllText(@"C:\Temp\json.txt");

    Response response = JsonConvert.DeserializeObject<Response>(jdata);
    response.Results.ForEach(x => x.ToList().ForEach(owner => dataTable.Rows.Add(owner.ToArray())));

    dataTable
        .Rows
        .Cast<DataRow>().ToList()
        .ForEach(x =>
        {
            dataTable.Columns.Cast<DataColumn>().ToList()
                .ForEach(y => Console.Write($"{x[y].ToString()}\t")); Console.WriteLine();
        });

And these are the classes i used. Class attributes should start with Capital letter and to conform to the standards, I used JsonProperty.

    public class Response
    {
        public int Count { get; set; }
        public string Message { get; set; }
        public string SearchCriteria { get; set; }
        public List<Result> Results { get; set; }
    }

    public class Result
    {
        public List<List<string>> ToList()
        {
            List<List<string>> response = new List<List<string>>();
            Owners.ForEach(x => response.Add(new List<string>() { MakeId, ModelId, x.Name, string.Join(",", x.Addresses.Select(address => address.City)) }));
            return response;
        }

        [JsonProperty("Make_ID")]
        public string MakeId { get; set; }
        [JsonProperty("Model_ID")]
        public string ModelId { get; set; }
        [JsonProperty("Make_Name")]
        public string MakeName { get; set; }
        [JsonProperty("Model_Name")]
        public string ModelName { get; set; }
        [JsonProperty("owners")]
        public List<Owner> Owners { get; set; }

    }

    public class Owner
    {
        [JsonProperty("name")]
        public string Name { get; set; }
        [JsonProperty("address")]
        public List<Address> Addresses { get; set; }
    }

    public class Address
    {
        [JsonProperty("city")]
        public string City { get; set; }
        [JsonProperty("pincode")]
        public string PinCode { get; set; }
    }

Output

474     1861    Balaji  kcp
474     1861    Rajesh  chennai
475     1862    Vijay   madurai
475     1862    Andrej  Berlin

Advantages

  1. Short and concise.. Based on classes you are defining so always will have the data in the correct place.
  2. You dont need the Paths any more... works without the XML.
  3. Easy to update the data returned from the Result.ToList()
Sign up to request clarification or add additional context in comments.

1 Comment

May be I forgot to mention that the json is dynamic so can't have class models
1

I don't know you still looking for better solution. Here is a solution using Cinchoo ETL, an open source library.

You may need to revisit your configuration parameters to suit the needs.

But the sample below shows the way to get the job done

using (var r = new ChoJSONReader("*** YOUR JSON FILE ***")
    .WithJSONPath("$..Results[*]")
    )
{
    var r1 = r.FlattenBy("owners", "address");
    var dt = r1.AsDataTable(selectedFields: new string[] { "Make_ID", "Model_ID", "name", "city" });
    Console.WriteLine(dt.DumpAsJson());
}

Output:

enter image description here

Hope it helps.

Comments

0

From my understanding it looks like you need a solution where the Columns are dynamic and depended on the configuration Xml.

You could do the following (comments inline)

var regex = new Regex(Regex.Escape("*"));
var jo = JObject.Parse(response);

var count = jo.SelectTokens("Results[*].Make_ID").Count();
// Parse the Json to form collections of KeyValuePairs
var dataCollection = Enumerable.Range(0,count)
                               .Select(x=>config.Property
                               .Select(prop=>new KeyValuePair<string,List<string>> 
                               (
                                prop.Name,
                                jo.SelectTokens(regex.Replace(prop.Path, x.ToString(), 1)).Select(c=>c.Value<string>()).ToList())));

// Flatten the collection and Create DataRows
foreach(var data in dataCollection)
{
    for(var i=0;i<data.Select(x=>x.Value).Max(x=>x.Count);i++)
    {
        var innerList = new List<object>();
        foreach(var prop in config.Property)
        {
            var currentData = data.Where(x=>x.Key == prop.Name).SelectMany(x=>x.Value).ToList();

            innerList.Add(i>=currentData.Count ? currentData.Last():currentData[i]);
        }

        dataTable.Rows.Add(innerList.ToArray());
    }
}

Working Demo

Output

enter image description here

1 Comment

This is not working as expected if there is no address defined for the user "Balaji" and can we get rid of this line "var count = jo.SelectTokens("Results[*].Make_ID").Count();". bcz path is dynamic

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.