[SalesForce] How to properly deserialize json into Apex classes and inner classes

I've been reading for A LONG WHILE now, even in the SF documentation to try to solve this problem.

I'm trying to understand how Apex works with Json and the different methods I can use so I tried to make a simple app that recieves a json response and trying to deserialize it using the Apex classes pattern I've seen all around the internet.

This is an example of the json and the part I'm trying to deserialize right now:

{
  "abilities": [
    {
      "ability": {
        "name": "limber",
        "url": "https://pokeapi.co/api/v2/ability/7/"
      },
      "is_hidden": false,
      "slot": 1
    },
    {
      "ability": {
        "name": "imposter",
        "url": "https://pokeapi.co/api/v2/ability/150/"
      },
      "is_hidden": true,
      "slot": 3
    }
  ],

I know it's not well formatted, just an example. To see the full json please refer to the endpoint in the code.

So this is the Apex class I've created:

    public class PokeApiIntegration {
    
    public void basicAuthCallout() {
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://pokeapi.co/api/v2/pokemon/ditto');
        req.setMethod('GET');
        
        // Create a new http object to send the request object
        // A response object is generated as a result of the request  
        Http http = new Http();
        HTTPResponse res = http.send(req);
        String responseBody = res.getBody();
        System.debug(responseBody);
        
        //Parse jsonString
        JSONParser parser = JSON.createParser(res.getBody());

        // Advance to the next token.
        while (parser.nextToken() != null) {
            // Start at the array of Abilities.
            if (parser.getCurrentToken() == JSONToken.START_ARRAY) {
                while (parser.nextToken() != null) {
                    // Advance to the start object marker to
                    //  find next object.
                    if (parser.getCurrentToken() == JSONToken.START_OBJECT) {
                        // Read entire object.
                        Abilities abl = (Abilities)parser.readValueAs(Abilities.class);
                        system.debug('slot : ' + abl.slot);
                        system.debug('is_hidden : ' + abl.is_hidden);
                        system.debug('slot : ' + abl.abilities);
                    }
                }
            }
        } 
    }
    
    
    // Inner classes used for serialization by readValuesAs(). 
    
    public class Abilities {
        List<Ability> abilities;
        Boolean is_hidden;
        Integer slot;
        
        public Abilities(List<Ability> abilities, Boolean is_hidden, Integer slot) {
            this.slot = slot;
            this.is_hidden = is_hidden;
            this.abilities = abilities.clone();
        }
    }  
    
    public class Ability {
        public String name;
        public String url;
    }    
    
}

I've been looking at this part of the SF documentation https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_json_jsonparser.htm ,and trying to do the same here in my code but I'm getting two errors with it.

First one is that I can't understand why system.debug('slot : ' + abl.abilities); this line of code is debugging 'null' while these two system.debug('slot : ' + abl.slot); system.debug('is_hidden : ' + abl.is_hidden); are debugging the expected values for the slot and for the boolean 'is_hidden' value, and I think it's because the Ability class should be nested inside the Abitities class (but then I can't have a nested class inside an already nested class, sience the Abilities class in a inner class already).

Second problem is that I can't find a way to stop the while loop and I can't determine what would be a good stop for it.

enter image description here

It's looping ok the first two times but then it should stop. I was wondering if someone with more experience could help me solving this problems. Thanks a lot for reading

Best Answer

I feel that you may have explored this, but have you tried just deserializing into a typed list of Abilities?

You'd define your inner classes like so:

public class Ability {
    public String name;
    public String url;
}

public class Abilities {
    public Ability ability;
    public Boolean is_hidden;
    public Integer slot;
}

Then just deserialize the whole html response into the Abilities array:

public Abilities[] abilities = (Abilities[])JSON.deserialize(res.getBody(),Abilities[].class);

That's pretty much all you have to do.

Apologies if you were specifically wanting to build your own parser - normally you don't have to do this, unless your JSON has a reserved keyword in Apex.

Easiest way to build the classes is to use JSON2Apex - which can also build your parser to if you need that.