0

First of all, of course I have searched around for about two days but just can't get this working if it's possible.

This helped a little: Get property value from C# dynamic object by string (reflection?) As well as this: C# Iterate through Class properties

Background information: The class was generated using a JSON string from TMDB. API keys are free so if you want to see the raw json, get a key and use this: https://api.themoviedb.org/3/tv/456?api_key=YOURKEY&append_to_response=season/1,season/2,season/3&language=en-US I had 20 seasons on it but shortened it here. Only 20 at a time is allowed. And yes, I can get one season at a time and make 27 api calls but 2 is better. Getting one season works just fine, no help needed for that.

Here is part of the class that the json generated using VS2015 special paste with a few adjustments I made:

namespace TvDb.Tv
{
class MultiSeasons
{      
 
    public Season[] seasons { get; set; }    
    public Season1 season1 { get; set; }
    public Season2 season2 { get; set; }
    public Season3 season3 { get; set; }

    //and so on
}

public class Season1 : CommonProperties { }
public class Season2 : CommonProperties { }
public class Season3 : CommonProperties { }

//and so on

public class CommonProperties
{
    public string _id { get; set; }
    public string air_date { get; set; }
    public Episode[] episodes { get; set; }
    public string name { get; set; }
    public string overview { get; set; }
    public string poster_path { get; set; }
    public int season_number { get; set; }
}

public class Season
{
    public string air_date { get; set; }
    public int episode_count { get; set; }
    public int id { get; set; }
    public string name { get; set; }
    public string overview { get; set; }
    public string poster_path { get; set; }
    public int season_number { get; set; }
}

public class Episode
{
    public string air_date { get; set; }
    public int episode_number { get; set; }
    public int id { get; set; }
    public string name { get; set; }
    public string overview { get; set; }
    public string production_code { get; set; }
    public int season_number { get; set; }
    public string still_path { get; set; }
    public float vote_average { get; set; }
    public int vote_count { get; set; }
}

}

I can get the information I need from deserializing the json, but as can be seen, I know it can be better written getting the classes dynically. I just can't seem to find a way....

        string json = "It's stored in a file for testing";
        //the json from TMDB returns wrong to name or find classes
        //such as season/1, season/2, etc.
        json = json.Replace("season/", "season");

        MultiSeasons ser = JsonConvert.DeserializeObject<MultiSeasons>(json);

        DataTable dt = new DataTable();
        //columns added here


        //int numOfSeasons = ser.number_of_seasons;

        //Type type = Type.GetType("TvDb.Tv.Series, TvDb");
        string str = "TvDb.Tv.Season1" + ", " + "TvDb";

        //dynamic d = 
        Type type = Type.GetType(str);

        string st = "";
        if (type != null)
        {
            var cls = Utilities.Classes.PropertyReader.getProperties(type);
            //PropertyReader site https://answers.unity.com/questions/1092425/how-to-get-a-list-of-variables-in-a-class.html


            for (int i = 0; i < cls.Length; i++)
            {

                if(cls[i].name == "episodes")
                {
                    //Episode e = 
                }

                var nameOfProperty = cls[i].name;
                var propertyInfo = cls.GetType().GetProperty(nameOfProperty);
                var value = propertyInfo.GetValue(cls, null);


                //st += cls.GetType().GetProperty(cls[i].name).GetValue(cls, null);
                //st += cls[i].name.ToString() + Environment.NewLine;
               
            }
        }

        DataRow dr;           

        //write 50 seperate foreach statements? I don't think so !!!!!!!!

        if (ser.season1 != null)
        {
            foreach (var r in ser.season1.episodes)
            {
                dr = dt.NewRow();
                dr["id"] = ser.id;
                dr["firstAired"] = r.air_date;
                dr["season"] = r.season_number;
                //and so on                    
                dt.Rows.Add(dr);
            }
        }

        if (ser.season2 != null)
        {
            foreach (var r in ser.season2.episodes)
            {
                dr = dt.NewRow();
                dr["id"] = ser.id;
                dr["firstAired"] = r.air_date;
                dr["season"] = r.season_number;
                //and so on
                dt.Rows.Add(dr);
            }
        }

Any input is appreciated or point me to a post that I missed.

EDIT: For those who want to see the json, I found a place to view it...

https://codebeautify.org/online-json-editor/cbaebcbd

Zath
  • 547
  • 2
  • 10
  • 25
  • 1
    Why are `Season1`, `Season2`, and `Season3` different types? – John Wu Jul 27 '21 at 14:58
  • I'm guessing that Season1, Season2 (etc) are different types because that is structure that JsonConvert.DeserializeObject expects in this case. Whatever the case, I would try to eliminate that need, if possible. Maybe use your "json.Replace" line to get rid of the season number all together (so 'season/1' becomes just 'season'), and then the actual season number lives in the 'season_number' property of the Season class. – Leetheus Jul 27 '21 at 15:46
  • @Leetheus that was a good idea. I did try it but even adding a property to season (Episode[]) it was null. I even copied the adjusted json and did a special paste in VS and sure enough, there were the season1, season2, etc. – Zath Jul 27 '21 at 16:54
  • @john Wu `Season[] seasons` properties are just the season information, not the episodes. – Zath Jul 27 '21 at 17:07
  • I think what is happening here is Visual Studio's "Paste as JSON" feature is inferring the type from the JSON text and is not clever enough to figure out `Season1` `Season2` and `Season3` are the same type. Pretty sure it is safe to manually edit the c# and merge those types into one, which will greatly simplify the problem. – John Wu Jul 27 '21 at 17:43
  • I tried to request an API key to actually see how the JSON actually looks - but they want a complete address and phone number to request an API key for private use? This seems excessive. – Christoph Sonntag Jul 27 '21 at 18:12
  • @Compufreak I edited my post with a link to view the json online. – Zath Jul 27 '21 at 18:42
  • So, you only want to have the information contained in the "season/1", "season/2" and so on nodes and nothing else from the document, right? – Christoph Sonntag Jul 27 '21 at 18:49
  • Yes. What I am doing with the info I just need season number, episode number, and episode name. – Zath Jul 27 '21 at 18:54
  • 1
    Give me some minutes :) – Christoph Sonntag Jul 27 '21 at 18:57
  • I've been messing around with this for a few days. Take your time. I wouldn't have to do it if thetvdb isn't about to start charging to use their api. – Zath Jul 27 '21 at 19:06
  • Done - check out my answer and tell me if it fits your needs :) – Christoph Sonntag Jul 27 '21 at 19:16

1 Answers1

2

This will do the job - please feel free to provide feedback if something does not work as expected.

I first wanted to use a JsonPath query to find the matching children - but it does not seem to support wildcards for property names. At least I did not find any documentation.

Thus we loop through all child properties to find the ones starting with "season/" - this should be fine from a performance perspective as JsonPath probably would do the same and with that small amount of data it doesn't really matter on a modern system anyways.


    static IReadOnlyList<SeasonDetails> GetSeasons(string json)
    {
        List<SeasonDetails> seasons = new List<SeasonDetails>();
        JObject jObject = JObject.Parse(json);
        foreach (var child in jObject.Children<JProperty>())
        {
            if (!child.Name.StartsWith("season/"))
            {
                continue;
            }

            seasons.Add(child.Value.ToObject<SeasonDetails>());
        }

        return seasons;
    }

    public class SeasonDetails
    {
        public string _id { get; set; }
        public string air_date { get; set; }
        public Episode[] episodes { get; set; }
        public string name { get; set; }
        public string overview { get; set; }
        public string poster_path { get; set; }
        public int season_number { get; set; }
    }

    public class Episode
    {
        public string air_date { get; set; }
        public int episode_number { get; set; }
        public int id { get; set; }
        public string name { get; set; }
        public string overview { get; set; }
        public string production_code { get; set; }
        public int season_number { get; set; }
        public string still_path { get; set; }
        public float vote_average { get; set; }
        public int vote_count { get; set; }
    }

Afterwards you can do your logic by looping through the list of seasons:

foreach(SeasonDetails season in seasons) {
 // Fill your datatable.
}
Christoph Sonntag
  • 4,459
  • 1
  • 24
  • 49
  • This worked great! I've been working with .Net since it came out and would never have thought to use `IReadOnlyList` for the solution. Your solution is much appreciated. – Zath Jul 28 '21 at 12:34