0

I am fairly new to C# but i was working on an assignment and came into this problem. I have a json data in a txt file which is stored like:

{"Name":"Ananta Maharjan","Number":"9841564194","Email":"ananta@gmail.com","Sugesstions":"Thik xa maka","DateTime":"1/21/2021 12:12:27 PM","cdata":{"Food Quality":"Excellent","Staff Friendliness":"Average","Cleanliness":"Good","Order Accuracy":"Dissatisfied","Restaurant Ambiance":"Good","Value for Money":"Excellent"}},

Now how do i show this in dataGrid view ive been working on this for a long time but i cannot solve this please help

EDIT: The problem i have is how do i show that datas in cdata the data in cdata were originally stored in a dictionary

I have seperated this into multiple section

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Formatting = Newtonsoft.Json.Formatting;

namespace FoodRating
{
   class Rating
   {
       private string _path = "Customer_Rating.txt";
       
       public string Name { get; set; }
       public string Number { get; set; }
       public string Email { get; set; }
       public string Sugesstions { get; set; }
       public string DateTime { get; set; }
       public string Criteria { get; set; }
       public IDictionary<string, string> cdata = new Dictionary<string, string>();



       public void CriteriaData(string item, string data) 
       {
           cdata.Add(item, data);

       
       }

       public string SaveRating(Rating data)
       {
           string str = JsonConvert.SerializeObject(data, Formatting.None);
           Utility.WriteToFile(_path, str);
           return "Success";
       }

      
       public List<Rating> List()
       {
           string d = Utility.ReadFromFile(_path);
           if (d != null)
           {
               List<Rating> lst = JsonConvert.DeserializeObject<List<Rating>>(d);
               return lst;
           }
           return null;
       }
   }
}

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FoodRating
{
    class Utility
    {
        public static void WriteToFile(string path, string data, bool append = true, int count = 1)
        {
            if (!File.Exists(path))
            {
                var file = File.Create(path);
                file.Close();
            }
            using (StreamWriter writer = new StreamWriter(path, append: append))
            {
                if (!append)
                {
                    //removing the opening bracker "[" from the data 
                    data = data.Trim().Substring(1, data.Trim().Length - 1);
                    //removing the last bracket "]" from the data 
                    data = data.Trim().Substring(0, data.Trim().Length - 1);
                }
                if (count != 0)
                {
                    data = data + ",";
                }
                writer.WriteLine(data);
            }
        }

        public static string ReadFromFile(string path)
        {
            if (File.Exists(path))
            {
                string data;
                using (StreamReader reader = new StreamReader(path))
                {
                    data = reader.ReadToEnd();
                }
                if (data != "")
                {
                    data = "[" + data + "]";
                }
                return data;
            }
            return null;
        }


        public static DataTable ConvertToDataTable<T>(IList<T> data)
        {
            PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
            DataTable table = new DataTable();
            foreach (PropertyDescriptor prop in properties)
                table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
            if (data != null)
            {
                foreach (T item in data)
                {
                    DataRow row = table.NewRow();
                    foreach (PropertyDescriptor prop in properties)
                        row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
                    table.Rows.Add(row);
                }
            }
            return table;
        }
        internal static DataTable ConvertToDataTavke(List<object> result)
        {
            throw new NotImplementedException();
        }
    }
}

namespace FoodRating
{
    public partial class Report : Form
    {
        public Report()
        {
            InitializeComponent();
            BindGrid();
        }
        public string rdata;
        public void BindGrid() 
        {
            Rating obj = new Rating();
            List<Rating> criteria = obj.List();
            DataTable dt = Utility.ConvertToDataTable(criteria);
            dataGridViewReport.DataSource = dt;





        }
  • You have 2 problems here, lets attack the first. do you know how to deserialize json in C#? – TheGeneral Jan 21 '21 at 08:50
  • yes i know, I deseralized the above json and tried to bind it into a datagrid view but data upto suggestion only show up while the ones in c data are not shown – Sudhan Shrestha Jan 21 '21 at 08:51
  • Show your code. – TheGeneral Jan 21 '21 at 08:52
  • check in the post i have added some pics – Sudhan Shrestha Jan 21 '21 at 08:57
  • Paste your code into the question, pictures of code is not very useful for future users. Also It will make this question a lot better – TheGeneral Jan 21 '21 at 08:58
  • 1
    check again i have done it the codes are of seperated classes the actual bind is done in the last one – Sudhan Shrestha Jan 21 '21 at 09:05
  • https://stackoverflow.com/a/27282579/1638261 – jomsk1e Jan 21 '21 at 09:24
  • @jomsk1e i tried to do that method but it gave me an error stating `An unhandled exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll Unexpected JSON token when reading DataTable. Expected StartArray, got StartObject. Path '', line 1, position 1.` – Sudhan Shrestha Jan 21 '21 at 09:29
  • I am using this to get the json data form the txt file and show it in a datagirdview. My project was to create a rating system for a resturant. The data rated by the customer are stored in a txt file in the json format as showen in the above. ` Rating obj = new Rating();` is used to create a object of Rating class which then reads the data and creates a List the data is read form a .txt file – Sudhan Shrestha Jan 21 '21 at 10:08
  • I just need to convert the data shown in the above json format into a datagirdview but i keep getting error. the data of json is a bit complex so please see it first – Sudhan Shrestha Jan 21 '21 at 10:09
  • After further review, using your posted code and correcting my text to the file path shows the data in the grid. What error are you getting and what line of code throws the error? You code works, however, the `cdata` `Dictionary` data will not display in the grid without some extra work. – JohnG Jan 21 '21 at 10:35
  • yes thats the problem cdata is not showing ! the values of cdata were originally a dictionary which was converted into json but now that i try to read and display the data which are stored as json other datas show where as the one form cdata (which contains other multiple data ) is not showing how do i fix this ? – Sudhan Shrestha Jan 21 '21 at 10:41
  • The reason the `cdata` is not displaying is because it is a “collection” type property. All the other properties in the `Rating` class are simple primitive type… `strings`. The grid understands how to add ONE `string` value to ONE cell. However, the grid is not sophisticated enough to add a “collection” of items to a “single” cell. Each item in the `Dictionary` has two items AND the `Dictionary` can have many items. So, the grid ignores “collection” types as you already know. – JohnG Jan 21 '21 at 11:11
  • Depending on what you want to display in the “single” cell, you “could” create a public `string` property in the `Rating` class that returns ALL the `Dictionary` items as one string. Or if there are many items in the dictionary, you could use two (2) grids for a “Master-Detail” structure. The first grid would contain what you have now, the second grid would display ALL the `Dictionary` items of the “selected” `Rating` in the first grid. You would need to convert the `Dictionary` to a `List`, and fortunately this is a built-in method of the `Dictionary` object. I hope that makes sense. – JohnG Jan 21 '21 at 11:12

2 Answers2

0

An example of what I described in my comments is shown below.

I made some changes to your current code. For starters it seems odd that the Rating class will only work with the file in its class. This file defined in the class should be moved out so the class can work with any properly structured json file. So, I move the file out of the class and it is passed in.

I changed the List method in the Rating class. In this case, if you look at the posted code in the form’s BindGrid method, there are two lines of code…

Rating obj = new Rating();
List<Rating> criteria = obj.List();

It should be obvious that the Rating obj created is really never used. After the call to obj.List()obj is not used and goes out of scope. There is no need to “create” this Rating obj to call the List method if we make the List method a static method. This will allow the code to call the method without creating a Rating object first.

Therefore, the change in the Rating class may look something like…

public static List<Rating> List(string _path) {
  string d = Utility.ReadFromFile(_path);
  if (d != null) {
    List<Rating> lst = JsonConvert.DeserializeObject<List<Rating>>(d);
    return lst;
  }
  return null;
}

Next, as described in my comment, one way to display the cdata is to create a public property that returns a single string of all the items (key, value) in the Dictionary cdata. This method will return a string with the following format… KEY: VALUE then a comma (,) as a single string. This property will be displayed in the grid and may look something like…

public string RatingData {
  get {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < cdata.Count; i++) {
      var kv = cdata.ElementAt(i);
      sb.Append(kv.Key + ": " + kv.Value);
      if (i < cdata.Count - 1)
        sb.Append(",");
    }
    return sb.ToString();
  }
}

Using master-Detail with two grids

Next is a “Master-Detail” structure using two (2) grids. Drop a second grid onto the form to display the cdata from the “selected” Rating row in the first grid. The changes made to the main form are such that the _path defining the path to json file is made global.

In addition, as I commented, reading the json file into a List<Rating>… then… converting the list to a DataTable seems unnecessary. A change is made to use the List<Result> as a DataSource to the grid. The BindGrid method is moved to the forms Load event and finally the grids SelectionChanged event is wired up so the code can “change” the cdata in the second grid to the newly selected Rating row in the first grid. These changes may look something like…

private string _path = @"D:\Test\JSON\RatingData.json";

public Form1() {
  InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e) {
  BindGrid();
}

public void BindGrid() {
  List<Rating> criteria = Rating.List(_path);
  dataGridViewReport.DataSource = criteria;
  dataGridView1.DataSource = criteria[0].cdata.ToList();
}

private void dataGridViewReport_SelectionChanged(object sender, EventArgs e) {
  Rating rating = (Rating)dataGridViewReport.CurrentRow.DataBoundItem;
  dataGridView1.DataSource = rating.cdata.ToList();
} 

In the SelectionChanged event, it should be noted that since we are using a List<Rating> as a DataSource to the grid, then getting the selected rows cdata is simplified with the line…

Rating rating = (Rating)dataGridViewReport.CurrentRow.DataBoundItem;

Extra work would be required if we used a DataTable as a DataSource to the grid.

I hope this makes sense.

JohnG
  • 9,259
  • 2
  • 20
  • 29
0

I will approach your problem by creating objects based on your json data:

public class Feedback
{

    public string Name { get; set; }
    public string Number { get; set; }
    public string Email { get; set; }
    public string Suggestions { get; set; }
    public DateTime FeedbackDateTime { get; set; }
    public cdata cdata { get; set; }
    public Feedback() { }
}

public class cdata
{
    public string FoodQuality { get; set; }
    public string StaffFriendliness { get; set; }
    public string Cleanliness { get; set; }
    public string OrderAccuracy { get; set; }
    public string RestaurantAmbiance { get; set; }
    public string ValueForMoney { get; set; }

    public cdata() { }
}

Now using Newtonsoft.Json, you can deserialize your json as your object like this:

string json = @"{
""Name"":""Ananta Maharjan"",
""Number"":""9841564194"",
""Email"":""ananta@gmail.com"",
""Suggestions"":""Thik xa maka"",
""DateTime"":""1/21/2021 12:12:27 PM"",
""cdata"":{
                ""FoodQuality"":""Excellent"",
    ""StaffFriendliness"":""Average"",
    ""Cleanliness"":""Good"",
    ""OrderAccuracy"":""Dissatisfied"",
    ""RestaurantAmbiance"":""Good"",
    ""ValueForMoney"":""Excellent""
    }
        }"; 

var feedback = JsonConvert.DeserializeObject<Feedback>(json);

Now use your ConvertToDataTable method to convert your object list like this;

Feedback feedback = JsonConvert.DeserializeObject<Feedback>(json);

var feedbacks = new List<Feedback>();
feedbacks.Add(feedback);

DataTable dt = ToDataTable(feedbacks);

//and finally
dataGridViewReport.DataSource = dt;
jomsk1e
  • 3,585
  • 7
  • 34
  • 59