Search code examples
c#json.netdeserialization

Issues deserializing a JSON response from an API call. .NET 6


using System.Net.Http.Headers;
using static System.Math;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Net.Http.Json;
using System.Net;

using HttpClient client = new();

await ProcessRepositoriesAsync(client);

static async Task ProcessRepositoriesAsync(HttpClient client)
{
    string apiUrl = "<<API CALL URL WITH AUTHENTICATION KEY>>";
    try
    {
        string jsonResponse = await client.GetStringAsync(apiUrl);
        var jsonDocument = JsonDocument.Parse(jsonResponse);

        if (jsonDocument.RootElement.TryGetProperty("response", out var responseData))
        {
            
            string jsonContent = File.ReadAllText(responseData.ToString());

            List<FlightData> flightList = JsonSerializer.Deserialize<List<FlightData>>(jsonContent);

            foreach (var flight in flightList)
            {
                Console.WriteLine($"Flight Number: {flight.flag}, Altitude: {flight.alt}");
            }
        }
        else
        {
            Console.WriteLine("No 'response' field found in JSON.");
        }
    }
    catch (HttpRequestException ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
    }
    catch (JsonException ex)
    {
        Console.WriteLine($"JSON Deserialization Error: {ex.Message}");
    }
}
internal class FlightData
{
    public string? hex { get; set; }
    public string? reg_number { get; set; }
    public string? flag { get; set; }
    public string? lat { get; set; }
    public string? lng { get; set; }
    public string? alt { get; set; }
    public string? dir { get; set; }
    public string? speed { get; set; }
    public string? v_speed { get; set; }
    public string? squawk { get; set; }
    public string? flight_number { get; set; }
    public string? flight_icao { get; set; }
    public string? flight_iata { get; set; }
    public string? dep_icao { get; set; }
    public string? dep_iata { get; set; }
    public string? airline_icao { get; set; }
    public string? airline_iata { get; set; }
    public string? aircraft_icao { get; set; }
    public string? updated { get; set; }
    public string? status { get; set; }

    public FlightData(string hex, string reg_number, string flag, string lat, string lng, string alt, string dir, string speed, string v_speed, string flight_number, string flight_icao, string flight_iata, string dep_icao, string dep_iata, string airline_icao,
        string airline_iata, string aircraft_icao, string updated, string status)
    {
        this.hex = hex;
        this.reg_number = reg_number;
        this.flag = flag;
        this.lat = lat;
        this.lng = lng;
        this.alt = alt;
        this.dir = dir;
        this.speed = speed;
        this.v_speed = v_speed;
        this.flight_number = flight_number;
        this.flight_icao = flight_icao;
        this.flight_iata = flight_iata;
        this.dep_icao = dep_icao;
        this.dep_iata = dep_iata;
        this.airline_icao = airline_icao;
        this.airline_iata = airline_iata;
        this.aircraft_icao = aircraft_icao;
        this.updated = updated;
        this.status = status;

    }
}

(I also don't need or want EVERY single property that is in the constructor. I just added all to see if that would help.) The code may be a bit wonky and it might not look good. I successfully did this in Python but I need to do it in C#. This is all new to me and I've spent hours on Microsoft's documentation, YouTube, GitHub, Stack Overflow, etc.

I am calling an API that gives me real time flight data in a JSON response. My plan is to take that and convert every JSON entry into the FlightData object. The issue I am having is (I think) the top of the JSON response has a ton of meta data in it. I don't care about any of it until it says "response" and following that is my data. I think it's trying to parse the meta data into the object, and it's hitting the second catch because the attributes don't line up. The if statement gets me past that, but then it doesn't work properly. It freezes my computer. I have successfully called the API and gotten the data.

In python all I did was

allPlanes = json.loads(data)
for i in allPlanes['response']:
    try: ....

and this worked well.

I appreciate any help and I apologize if I did a bad job at explaining. If I let out important information please don't hesitate to tell me.

I tried so many different methods found online and the solution mentioned above is the closest I've gotten. I know that the if statement part works because I output it to a .txt and it did separate the meta data and the response that I actually wanted.


Edit: Here's what the error is when it hits the second catch:

JSON Deserialization Error: The JSON value could not be converted to FlightData. Path: $[0].lat | LineNumber: 0 | BytePositionInLine: 68.


Edit 2: thanks for the speedy replies you guys. Don't know how I forgot to add a sample JSON.

Here is one without the header/metadata (I am honestly not sure what the exact term for it is):

[
   {
      "hex":"142329",
      "reg_number":"RA-09001",
      "flag":"RU",
      "lat":56.33128,
      "lng":79.944679,
      "alt":10980,
      "dir":272,
      "speed":872,
      "v_speed":0,
      "squawk":"5257",
      "flight_number":"9634",
      "flight_icao":"GZP9634",
      "flight_iata":"4G9634",
      "dep_icao":"UNTT",
      "dep_iata":"TOF",
      "airline_icao":"GZP",
      "airline_iata":"4G",
      "aircraft_icao":"F900",
      "updated":1692074910,
      "status":"en-route"
   },
   {
      "hex":"152000",
      "reg_number":"RA-73728",
      "flag":"RU",
      "lat":56.401016,
      "lng":45.294384,
      "alt":10355,
      "dir":260,
      "speed":779,
      "v_speed":0,
      "squawk":"5527",
      "flight_number":"1437",
      "flight_icao":"AFL1437",
      "flight_iata":"SU1437",
      "dep_icao":"USSS",
      "dep_iata":"SVX",
      "arr_icao":"UUEE",
      "arr_iata":"SVO",
      "airline_icao":"AFL",
      "airline_iata":"SU",
      "aircraft_icao":"A321",
      "updated":1692074910,
      "status":"en-route"
   }
]

And here is the top part with an entry:

{
   "request":{
      "lang":"en",
      "currency":"USD",
      "time":33,
      "id":"6u6cys0kh2o",
      "server":"l",
      "host":"airlabs.co",
      "pid":1867027,
      "key":{
         "id":26237,
         "api_key":"2354963e-6fc3-4226-a220-46cb2bab6633",
         "type":"free",
         "expired":"2023-09-10T00:00:00.000Z",
         "registered":"2023-08-11T04:11:07.000Z",
         "limits_by_hour":2500,
         "limits_by_minute":250,
         "limits_by_month":1000,
         "limits_total":884
      },
      "params":{
         "lang":"en"
      },
      "version":9,
      "method":"flights",
      "client":{
         "ip":"2600:6c5c:6c00:4d9:804e:66ab:1add:d35",
         "geo":{
            "country_code":"US",
            "country":"United States",
            "continent":"North America",
            "city":"Kingsport",
            "lat":36.5491,
            "lng":-82.5584,
            "timezone":"America/New_York"
         },
         "connection":{
            "isp_code":20115,
            "isp_name":"Charter Communications"
         },
         "device":{
            
         },
         "agent":{
            
         },
         "karma":{
            "is_blocked":false,
            "is_crawler":false,
            "is_bot":false,
            "is_friend":false,
            "is_regular":true
         }
      }
   },
   "response":[
      {
         "hex":"142329",
         "reg_number":"RA-09001",
         "flag":"RU",
         "lat":56.307404,
         "lng":81.793709,
         "alt":10492,
         "dir":268,
         "speed":861,
         "v_speed":3.3,
         "squawk":"5257",
         "flight_number":"9634",
         "flight_icao":"GZP9634",
         "flight_iata":"4G9634",
         "dep_icao":"UNTT",
         "dep_iata":"TOF",
         "airline_icao":"GZP",
         "airline_iata":"4G",
         "aircraft_icao":"F900",
         "updated":1692074433,
         "status":"en-route"
      },
      {
         "hex":"152000",
         "reg_number":"RA-73728",
         "flag":"RU",
         "lat":56.593597,
         "lng":46.8978,
         "alt":10363,
         "dir":250,
         "speed":759,
         "v_speed":0,
         "squawk":"5527",
         "flight_number":"1437",
         "flight_icao":"AFL1437",
         "flight_iata":"SU1437",
         "dep_icao":"USSS",
         "dep_iata":"SVX",
         "arr_icao":"UUEE",
         "arr_iata":"SVO",
         "airline_icao":"AFL",
         "airline_iata":"SU",
         "aircraft_icao":"A321",
         "updated":1692074433,
         "status":"en-route"
      }
   ]
}

Solution

  • So assuming that second JSON is the response string you need to fix the types in the destination class (as I wrote in the comments):

    public class FlightData
    {
        [JsonPropertyName("hex")] 
        public string Hex { get; set; }
    
        [JsonPropertyName("reg_number")] 
        public string RegNumber { get; set; }
    
        [JsonPropertyName("flag")] 
        public string Flag { get; set; }
    
        [JsonPropertyName("lat")] 
        public double Lat { get; set; }
    
        [JsonPropertyName("lng")] 
        public double Lng { get; set; }
    
        [JsonPropertyName("alt")] 
        public long Alt { get; set; }
    
        [JsonPropertyName("dir")] 
        public long Dir { get; set; }
    
        [JsonPropertyName("speed")]
        public long Speed { get; set; }
    
        [JsonPropertyName("v_speed")] 
        public double VSpeed { get; set; }
    
        [JsonPropertyName("squawk")] 
        public string Squawk { get; set; }
    
        [JsonPropertyName("flight_number")] 
        public string FlightNumber { get; set; }
    
        [JsonPropertyName("flight_icao")] 
        public string FlightIcao { get; set; }
    
        [JsonPropertyName("flight_iata")]
        public string FlightIata { get; set; }
    
        [JsonPropertyName("dep_icao")]
        public string DepIcao { get; set; }
    
        [JsonPropertyName("dep_iata")]
        public string DepIata { get; set; }
    
        [JsonPropertyName("airline_icao")]
        public string AirlineIcao { get; set; }
    
        [JsonPropertyName("airline_iata")]
        public string AirlineIata { get; set; }
    
        [JsonPropertyName("aircraft_icao")]
        public string AircraftIcao { get; set; }
    
        [JsonPropertyName("updated")]
        public long Updated { get; set; }
    
        [JsonPropertyName("status")]
        public string Status { get; set; }
    
        [JsonPropertyName("arr_icao")]
        public string ArrIcao { get; set; }
    
        [JsonPropertyName("arr_iata")]
        public string ArrIata { get; set; }
    }
    

    And then you can introduce the Root one and just use it:

    class Root
    {
        [JsonPropertyName("response")]
        public List<FlightData> Response { get; set; }
    }
    

    And usage:

    var deserialize = JsonSerializer.Deserialize<Root>(jsonResponse);
    

    Or just:

    var result = await client.GetFromJsonAsync<Root>(...)
    

    If you are adamant on the property check then use:

    using var jsonDocument = JsonDocument.Parse(jsonResponse); // DO NOT FORGET USING!
    if (jsonDocument.RootElement.TryGetProperty("response", out var responseData))
    {
        var result = responseData.Deserialize<List<FlightData>>();
    }
    

    P.S.

    There are a lot of tools allowing to convert JSON into C# types, some are build in in IDEs, or you can use online ones like app.quicktype.io