0

I am new to programming and to C#. I'm using Visual Studio (latest) to write my app.

This is a console app that is attempting to read a log (text) file where each line is a separate JSON entry.

This is what a single item JSON entry looks like:

{
    "timestamp": "2022-02-09T07:05:59Z",
    "event": "Color",
    "Hex": "#FFFF00",
    "Name": "Yellow"
}

I establish the full path to the text file and store it in a string 'fileFullPath' and I'm 'using System.Text.Json'.

Each entry in the text file always has two fields - 'timestamp' and 'event'. I created a base class called 'LogHeader':

namespace LogModels
{
    public class LogHeader
    {
        public string TimeStamp { get; set; }
        public string Event { get; set; }

    }
}

Then, I created a class for each event and inherited from LogHeader:

public class Color : LogHeader
{
  public string Hex { get; set;}
  public string Name { get; set;}
}

Next, I read the file and start going through each line and writing to the console using a switch statement based on the 'event' field:

foreach (string line in File.ReadAllLines(fileFullPath))
{
  var logHeader = JsonSerializer.Deserialize<LogHeader>(line,options);

  switch (logHeader.Event)
  {
     case "Color":
       var eventColor = JsonSerializer.Deserialize<LogModels.Event.Color>(line, options);
       Console.WriteLine($"The color is {eventColor.Name}.");
       Console.WriteLine($"The Hex value is {eventColor.Hex}.");
     break;
  }

The console output is:

The color is Yellow.
The Hex value is #FFFF00.

The problem comes in with the nested JSON objects, which look like this:

{
    "timestamp": "2022-02-09T07:07:52Z",
    "event": "Carpet",
    "Shag": [
        {
            "Color": "Green",
            "Count": 3
        },
        {
            "Color": "Black",
            "Count": 104
        }
    ],
    "Pile": [
        {
            "Color": "Blue",
            "Count": 5
        },
        {
            "Color": "Beige",
            "Count": 13
        }
    ],
    "Outdoor": [
        {
            "Color": "Pebble",
            "Count": 300
        },
        {
            "Color": "Astroturf",
            "Count": 12
        }
    ]
}

At this point, I'm lost. I've tried creating a class called "Carpet" with child classes called "Shag", "Pile", etc. I've tried creating separate classes for each of the subtypes.

The desired outcome is for this to appear on the console:

Shag Carpet -
Green: 3
Black: 104
Pile Carpet -
Blue: 5
Beige: 13
Outdoor Carpet -
Pebble: 300
Astroturf: 12

I can't imagine this is as hard to do as I'm making it out to be, but I can't seem to find any examples to help me through it.

Also, I've only posted to this site a couple of times, and I'm not sure of the proper structure of replying to suggestions, etc.

Thank you for your help with this.

dbc
  • 104,963
  • 20
  • 228
  • 340
jchornsey
  • 365
  • 1
  • 4
  • 10
  • The `logHeader` varaible, of data type `LogHeader`, doesn't have properties `.Name` or `.Hex`, so your third code block shouldn't compile. – gunr2171 Feb 15 '22 at 22:57
  • Is there a fixed json property list for each log object? Will there only be "Shag", "Pile", and "Outdoor" along with "timestamp" and "event", or can you dynamically add more? – gunr2171 Feb 15 '22 at 22:59
  • @gunr2171 - You're absolutely right. I had missed a line of code and made the necessary correction. Also, it's a fixed property list and I won't add more dynamically. – jchornsey Feb 15 '22 at 23:13

2 Answers2

2

Since you are new to programming and to C#, I highly recommend you to use Newtonsoft.Json serializer.

You can try this code

Data data = JsonConvert.DeserializeObject<Data>(json);

classes

public class Data
{
    [JsonProperty("timestamp")]
    public DateTime TimeStamp { get; set; }
    [JsonProperty("event")]
    public string Event { get; set; }
    public string Hex { get; set; }
    public string Name { get; set; }
    public List<ColourCount> Shag { get; set; }
    public List<ColourCount> Pile { get; set; }
    public List<ColourCount> Outdoor { get; set; }
}

public class ColourCount
{
    public string Color { get; set; }
    public int Count { get; set; }
}

you can test using reflection

    var props = data.GetType().GetProperties();

    foreach (var prop in props)
    {
        if (!prop.PropertyType.IsCollectible)
        {
            if(prop.GetValue(data)!=null)
            Console.WriteLine($" {prop.Name} : { prop.GetValue(data).ToString()} ");
        }
        else
        {
            Console.WriteLine($" { prop.Name} - ");

            var items = prop.GetValue(data) as IList<ColourCount>;

            foreach (var item in items)
            {
                Console.WriteLine($" {item.Color} : {item.Count}");
            }
        }
    }

result

 TimeStamp : 2022-02-09 7:07:52 AM 
 Event : Carpet 
 Shag - 
 Green : 3
 Black : 104
 Pile - 
 Blue : 5
 Beige : 13
 Outdoor - 
 Pebble : 300
 Astroturf : 12

if you still want to use Text.Json change property name attribute

Data data = System.Text.Json.JsonSerializer.Deserialize<Data>(json);

public class Data
{
    [JsonPropertyName("timestamp")]
    public DateTime TimeStamp { get; set; }
    [JsonPropertyName("event")]
    public string Event { get; set; }
    .....
}
Serge
  • 40,935
  • 4
  • 18
  • 45
  • I was told by some other folks to avoid using Newtonsoft.JSON and go with the built-in .NET library. Is what I'm trying to do not possible in .NET and only possible in Newtonsoft? – jchornsey Feb 15 '22 at 23:34
  • 1
    @jchornsey the same model works using system.text.json, just change the attribute to `JsonPropertyName` – haldo Feb 15 '22 at 23:47
  • I highly recommend you to forget Text.Json. It has more bugs that line of codes. You can do the same using Text.Json but you will need much more code. In many cases you will have to wright custom contract resolver. – Serge Feb 15 '22 at 23:48
  • @Serge I'm not sure you can generalize like this. `System.Text.Json` is here to stay. – tymtam Feb 21 '22 at 07:04
1

Here's how I did it, thanks to Serge's examples:

public class Carpet
{
    public List<ColourCount> Shag { get; set; }
    public List<ColourCount> Pile { get; set; }
    public List<ColourCount> Outdoor { get; set; }
}

public class ColourCount
{
    public string Color { get; set; }
    public int Count { get; set; }
}

In the switch statement:

  case "Carpet":
     var eventCarpet = JsonSerializer.Deserialize<LogModels.Event.Carpet>(line, options);

     foreach (var shag in eventMaterials.Shag)
     {
        Console.WriteLine($" {shag.Color}: {shag.Count}.");
     }

     foreach (var pile in eventMaterials.Pile)
     {
        Console.WriteLine($" {pile.Color}: {pile.Count}.");
     }

     foreach (var outdoor in eventMaterials.Outdoor)
     {
        Console.WriteLine($" {outdoor.Color}: {outdoor.Count}.");
     }

  break;

This gives me the desired output to console (minus the 'fluff'):

Green : 3
Black : 104
Blue : 5
Beige : 13
Pebble : 300
Astroturf : 12

I know this is probably the Fisher-Price version of what you did, but thanks again for your help!

jchornsey
  • 365
  • 1
  • 4
  • 10