JSON-i parsimine

JSON on andmeformaat, mis sobib hästi dünaamiliste ja/või mahukate andmete hoidmiseks. Näiteks eri mängutasemel erinevate tegelaste atribuudid, tegelaste dialoogid, jutuvestija räägitav narratiiv.

Järgnevas koodinäites loome keeruka JSON-i struktuuri, defineerime ühe JSON-i atribuudi põhjal enumi, parsime JSON-i, loome sellest Java objektid ning kasutame casting'ut, et määrata objektid vastavatesse pärivatesse klassidesse.

Loome kolme eri kaarditüüpi JSON-i objektiks:
  • DECISION: mängija valikust sõltub, kas tema skoor suureneb või väheneb.

  • NOTHING: kaardi saamisel ei juhtu midagi.

  • FREE_POINTS: mängija skoor automaatselt suureneb või väheneb.

type võtme asemel võib ka mõne muu välja valida ja selle väärtused Enum-klassis defineerida.

[
    {
        "type": "DECISION",
        "id": 190,
        "question": "Are you going on a trip to Yemen or Oman?",
        "badAnswer": "Oman",
        "goodAnswer": "Yemen",
        "badAnswerScore": -100,
        "goodAnswerScore": 100,
        "badAnswerOutput": "Oh man",
        "goodAnswerOutput": "Ye men"
    },
    {
        "type": "NOTHING",
        "id": 200
    },
    {
        "type": "FREE_POINTS",
        "id": 360,
        "text": "You win the Nobel Peace Prize.",
        "score": 100
    }
]

Defineerime enumi iga kaarditüübi jaoks. See lihtsustab hiljem põhilises mänguklassis kaarditüüpide erikäitlemise ning ka Screenide vahetamise kaarditüübi järgi.

public enum CardType  {

    DECISION,
    FREE_POINTS,
    NOTHING
}

Loome abstraktse klassi, kuhu paigutame kõik muutujad ja nende get-meetodid, mida iga päriv klass vajab. set-meetodid pole selles näites vaja.

public abstract class BaseCard {

    private CardType  type;
    private int id;

    public int getId() {
        return id;
    }

    public CardType  getType() {
        return type;
    }
}

NothingCard on ainus kaarditüüp, mis vastab täpselt abstraktsele klassile, teised kaarditüübid vajavad lisamuutujaid ja get-meetodeid nendele muutujatele.

public class NothingCard extends BaseCard {
}

public class FreePointsCard extends BaseCard {

    private String text;
    private int score;

    public String getText() {
        return text;
    }
    public int getScore() {
        return score;
    }
}

public class DecisionCard extends BaseCard {

   private String question;
   private String badAnswer;
   private String goodAnswer;
   private String badAnswerOutput;
   private String goodAnswerOutput;
   private int badAnswerScore;
   private int goodAnswerScore;

   public String getQuestion() {
       return question;
   }

   public String getBadAnswer() {
       return badAnswer;
   }

   public String getGoodAnswer() {
       return goodAnswer;
   }

   public String getBadAnswerOutput() {
       return badAnswerOutput;
   }

   public String getGoodAnswerOutput() {
       return goodAnswerOutput;
   }

   public int getBadAnswerScore() {
       return badAnswerScore;
   }

   public int getGoodAnswerScore() {
       return goodAnswerScore;
   }
  1. Põhilises mänguklassis kasutame Map<Integer, BaseCard> muutujat, et kaardid nende ID-de järgi leida. Kuigi BaseCard klassis on ID muutuja primitiivne andmetüüp int, vajab Map võtmeks Integer andmetüüpi.

private final Map<Integer, BaseCard> cardId = new HashMap<>();

Loome põhilises mänguklassi meetodi parseJson, mis:

  1. Loeb assets kaustast JSON-faili string-iks.

  2. Salvestab iga JSON-i objekti (loogeliste sulgude sisu) eraldi objektiks ja lisab need massiivi.

  3. Käib läbi massiivi ja otsib type võtme abil väärtusi, mis on ka Enum-klassis.

  4. Kasutab ObjectMapper -it, et teisendada JSON-i objektid Java objektideks.

  5. Loob switch-case tingimuslause abil iga kaarditüübi jaoks vastava BaseCardi päriva klassi objekti.

  6. Lisab loodud objektid Map -i, kasutades kaardi ID-d võtmena.

private void parseJson() {

String jsonData = Gdx.files.internal("cards.json").readString();
try {
                JSONArray jsonArray = new JSONArray(jsonData);
                for (int i = 0; i < jsonArray.length(); i++) {
                        JSONObject jsonObject = jsonArray.getJSONObject(i);
                        CardType  type = CardType .valueOf(jsonObject.getString("type"));

                        ObjectMapper mapper = new ObjectMapper();

                        BaseCard card = switch (type) {
                                case FREE_POINTS -> mapper.readValue(jsonObject.toString(), FreePointsCard.class);
                                case NOTHING -> mapper.readValue(jsonObject.toString(), NothingCard.class);
                                case DECISION -> mapper.readValue(jsonObject.toString(), DecisionCard.class);
        };
                        cardId.put(card.getId(), card);
                }
        } catch (IOException e) {
                e.printStackTrace();
        }
}

Nüüd peame parseJson(); põhilises mänguklassi konstruktoris välja kutsuma.

Lõpetuseks peame sama klassi render meetodis määrama mängija saadud kaarditüübi ning suunama mängija screenManager abiga kaarditüübile vastavale Screen'ile, et eri muutujatega erinevaid nuppe näiteks kasutada. Et anda uute Screen'ide konstruktoritele päritava klassi muutujad, peame tegema casting-ut, sest andmeid hoidvas Map-is on kõik väärtused abstraktse klassina. Casting muudab meie Map-i väärtuse õigeks päritavaks klassiks. Uuele Screen-ile mängija viimine tähendab ka seda, et järgnevat koodi peab kirjutama enne batch.begin() osa. Uues Screen-is anname peale igale kaarditüübile omasele muutujale kaasa ka hetkese klassi ja mängija objekti.

BaseCard card = cardId.get(currentCardId);
    if (card.getType() == CardType.NOTHING) {
        break;

    } else if (card.getType() == CardType.FREE_POINTS) {
        int score = ((FreePointsCard) card).getScore();
        String text = ((FreePointsCard) card).getText();

        screenManager.switchToFreePointsScreen(text, score, player, this);

    } else if (card.getType() == CardType.DECISION) {
        String card = ((DecisionCard) card).getQuestion();
        String goodAnswer = ((DecisionCard) card).getGoodAnswer();
        String badAnswer = ((DecisionCard) card).getBadAnswer();
        String goodAnswerOutput = ((DecisionCard) card).getGoodAnswerOutput();
        String badAnswerOutput = ((DecisionCard) card).getBadAnswerOutput();
        int goodScore = ((DecisionCard) card).getGoodAnswerScore();
        int badScore = ((DecisionCard) card).getBadAnswerScore();

        screenManager.switchToDecisionsScreen(card, goodAnswer, badAnswer, goodScore, badScore, goodAnswerOutput, badAnswerOutput, player, this);
    }