0

I try to make a code to display organisms phylum, order, and kingdom, in lblOrganismIdentity. Firstly, the user must input the species name in the tbNameSpecies. And then click btProcess to run the method on Organism class. And the name is used to make the API link. The API data is in this link for example. I dont know how to make the data to become an array, and then display it to lblOrganismIdentity.

The exception error message is

System.Collections.Generic.KeyNotFoundException: 'The given key was not present in the dictionary.'

The exception error occurs in this code

JsonObject kingdom = (JsonObject)gbifObj["kingdom"];

I'm using RestSharp and SimpleJson for the references. I've tried to search for the solutions, but I didn't get that.

This is the source code.

The GetOrganismList method in Organism.cs class

public static List<string> GetOrganismList(string link)
    {
        List<string> returnList = new List<string>();
        var client = new RestClient(link);
        var request = new RestRequest(Method.GET);
        IRestResponse response = client.Execute(request);
        JsonObject gbifObj = (JsonObject)SimpleJson.SimpleJson.DeserializeObject(response.Content);
        Console.WriteLine(gbifObj); 
        JsonObject kingdom = (JsonObject)gbifObj["kingdom"];
        JsonObject phylum = (JsonObject)gbifObj["phylum"];
        JsonObject order = (JsonObject)gbifObj["order"];
        returnList.Add((string)kingdom["kingdom"]);
        returnList.Add((string)phylum["phylum"]);
        returnList.Add((string)order["order"]);
        return returnList;
    }

Form1.cs

    public Form1()
    {
        InitializeComponent();
    }

    List<string> identityList = new List<string>();
    private IEnumerable<string> identitylist;

    private void btProcess_Click(object sender, EventArgs e)
    {
        DisplayInfo(identityList);

    }

    private void DisplayInfo(List<string> identityList)
    {
        string link = "http://api.gbif.org/v1/species/match?&name=" + tbNameSpecies.Text.ToString();
        identityList = Organism.GetOrganismList(link);
        lblOrganismIdentity.Text = "";
        foreach (string item in identityList)
        {
            lblOrganismIdentity.Text += "- " + item + Environment.NewLine;
        }
    }

I expect the lblOrganismIdentity can display data of the species from the user input. For example:

kingdom: Animalia

phylum: Chordata

order: Carnivora

  • Hi! It would be very helpful to see what specific line you were getting that exception on. Additionally, if possible, could you provide a sample of the JSON the request is returning? The issue might be in the parser. – FlutterDashie May 03 '19 at 17:21
  • Is that JSON structure always the same or can it contain different properties? If it's constant (or some of the properties are) deserialize it to a class structure and use that class with standard .Net methods. If you need to show specific results as text, you can insert one or more overrides of `ToString()` and return a *sum* of the properties values you need. Note that Visual Studio allows to `Paste Special => Paste JSON as classes`. That JSON is quite simple, so it won't mess up the result :) – Jimi May 03 '19 at 17:25
  • @FlutterDashie oh sorry i forgot to put where the exception error occurs. I've edited the post. – salomo hutapea May 03 '19 at 17:34
  • @salomohutapea Thank you much! This leads me to believe that the issue is somewhere in either the JSON or the parser. Do you have a piece of sample JSON on hand? The exception along with its location seems to reflect that the data is being pulled in, but that it does not have a `"kingdom"` field, or that said field is not visible from the first layer of the JSON. – FlutterDashie May 03 '19 at 17:39
  • @Jimi Thanks . I'll try it first. – salomo hutapea May 03 '19 at 17:45
  • @FlutterDashie it has kingdom field by the way. But i dont know what you mean that field is not visible from the first layer of the JSON. Actually i'm new to this API things, so I don't really understand. – salomo hutapea May 03 '19 at 17:47
  • @salomohutapea No worries. What I mean by first layer is that the field is not inside of any other field or array, or has no "parent". [This Question](https://stackoverflow.com/questions/183702/access-parents-parent-from-javascript-object) delves a little bit into the parent-child relationship in JSON. The reason this is relevant is that `gbifObj[]` will only work if `` exists and is not inside any other node/field/data item. – FlutterDashie May 03 '19 at 17:55
  • @Jimi After I paste special, what i'm going to do next? Am I going to make a new object to that class? – salomo hutapea May 03 '19 at 17:58
  • That class is the target of the deserialization. For example, if you name the class `Organism`, then: `var myOrganisms = JsonConvert.DeserializeObject([JSON string]);`. After that, you can access the class properties as usual. – Jimi May 03 '19 at 18:06
  • @Jimi thanks for your help. Now I can display the data. – salomo hutapea May 03 '19 at 18:28
  • @FlutterDashie thanks for your help too. Now I know a little bit about the concept of API. – salomo hutapea May 03 '19 at 18:28
  • @salomohutapea That's great to hear! If you would, could you answer the question with what you found and then accept the answer? That way, anyone else who has a similar problem will know what they can do to fix it. – FlutterDashie May 03 '19 at 18:30

2 Answers2

1

You must first deserialize the JSON to a class, for example Organism class. Copy first our JSON file example. And then, we can use Edit --> Paste Special --> Paste JSON as classes.

So The class will be like this.

public class Organism
{
    public int usageKey { get; set; }
    public string scientificName { get; set; }
    public string canonicalName { get; set; }
    public string rank { get; set; }
    public string status { get; set; }
    public int confidence { get; set; }
    public string matchType { get; set; }
    public string kingdom { get; set; }
    public string phylum { get; set; }
    public string order { get; set; }
    public string family { get; set; }
    public string genus { get; set; }
    public string species { get; set; }
    public int kingdomKey { get; set; }
    public int phylumKey { get; set; }
    public int classKey { get; set; }
    public int orderKey { get; set; }
    public int familyKey { get; set; }
    public int genusKey { get; set; }
    public int speciesKey { get; set; }
    public bool synonym { get; set; }
    public string _class { get; set; }
}

After that, we can now access the class properties to that object as usual.

    public static List<string> GetOrganismList(string link)
    {
        List<string> returnList = new List<string>();
        var client = new RestClient(link);
        var request = new RestRequest(Method.GET);
        IRestResponse response = client.Execute(request);
        var listOrganism = JsonConvert.DeserializeObject<Organism>(response.Content);

        returnList.Add(" Kingdom: " + listOrganism.kingdom.ToString());
        returnList.Add(" Phylum: " + listOrganism.phylum.ToString());
        returnList.Add(" Order: " + listOrganism.order.ToString());
        returnList.Add(" Family: " + listOrganism.family.ToString());
        returnList.Add(" Genus: " + listOrganism.genus.ToString());
        returnList.Add(" Species: " + listOrganism.species.ToString());

        return returnList;
    }
  • A couple of notes: Visual Studio's `Paste Special` tool works when the JSON structure is very simple (like in this case). When it's not, the results are not exactly reliable. There are on-line resource that perform a much better *analysis*. At this time, [QuickType](https://app.quicktype.io/) (not affiliated in any way) is probably the most significant. -- Your properties (`listOrganism.kingdom` and the others you're considering here) are already strings, so you don't need `.ToString()`. Not that much important, it's just redundant. – Jimi May 04 '19 at 12:35
0

I made it work in a simple console app by Deserializing to expected object: notice i displayed value in a console app since i couldn't be bothered to create a new win form:

create a class for the return object. It's more clean.

class Animal {
    public int usageKey { get; set; }
    public int acceptedUsageKey { get; set; }
    public string scientificName { get; set; }
    public string canonicalName { get; set; }
    public string rank { get; set; }
    public string status { get; set; }
    public int confidence { get; set; }
    public string matchType { get; set; }
    public string kingdom { get; set; }
    public string phylum { get; set; }
    public int kingdomKey { get; set; }
    public int phylumKey { get; set; }
    public bool synonym { get; set; }
}

Here is the method to get the value from api and do something with it

async void AsyncToConsole(Uri uri, Action<Animal> action) {

    using (var client = new HttpClient())
    {
        var getReg = await client.GetAsync(uri);
        var json = await getReg.Content.ReadAsStringAsync();                
        Animal animal;
        try
        {
            animal = (SimpleJson.DeserializeObject<Animal>(json));
            action(animal);
        }
        catch (Exception)
        {
            action(new Animal() { kingdom = "error", confidence = 0, phylum = "error" });
        }
    }
}

Main method, kinda like your form click event handler

static void Main(string[] args)
{
    var species = "Lycopodiophyta";
    var uri = new Uri($"http://api.gbif.org/v1/species/match?&name={ species }");

    // callable method to display the values. like your foreach loop
    // it will be passed to the async method, who will then call it instead.
    Action<Animal> populateLabel = (Animal animal) =>
    {
        Console.WriteLine($"- {animal.kingdom}");
        Console.WriteLine($"- {animal.phylum}");
        Console.WriteLine($"- {animal.rank}");
    };
    // I need to use new Program(), but you dont need to do that
    new Program().AsyncToConsole(uri, populateLabel );
    Console.ReadLine();
    return;
}
siggi_pop
  • 568
  • 7
  • 10