Christmas countdown

Turning Any JSON API Into An App

The Internet is fantastic. There are a seemingly infinite array of APIs available for people to use. Many of them are completely free and open to use in pretty much any way you want to. For example, NASA has a wide variety of things available through its public API. I use one of them in Power Automate with a custom connector to post their daily image to Twitter. For my contribution to this year’s C# Advent, I will demonstrate how easily you can implement any JSON-based API into a C# based application.

The Open Trivia Database API

The API I’m going to use for this post is the Open Trivia Database API. But you can apply this approach to any JSON-based API. The Open Trivia Database is a public database of trivia questions on a wide variety of topics of both multiple choice and true/false variety. The categories include general knowledge, various science and entertainment topics, politics, mythology, sports, art, and so forth.

Their API doesn’t require any sort of account or password. Many free APIs do as a means of limiting free use. For those that do, there is usually a key of some sort that you would include in the header metadata, but the method of authentication can vary from API to API.

The Open Trivia Database has full documentation for the API, which is better than a lot of APIs. But even better than that, they have a page that lets you generate a URL with full parameters based on what you want to retrieve from the database. So you can customize the number of questions, the category, difficulty, question type, and encoding type to use for the call.

For our app, we’re going to use mostly the defaults to start with. The only thing we’re going to specify is that we just want multiple-choice questions. We’ll make that selection and then click on “GENERATE API URL” to get our URL. This gives us the following URL that we’ll use as our starting point:

https://opentdb.com/api.php?amount=10

We’ll come back to that in a bit. First, let’s talk about our app.

Setting Up The App

The app we’re creating is going to be a simple trivia app. We’ll create it as a C# console-based application. We’ll ask the user a certain number of questions, keep track of their score, then report their final score at the end. We’ll use Visual Studio 2022 and the latest .NET version (v7) for the app for simplicity.

Go ahead and open VS2022 and create a new project. If you don’t see “Console App” in the list, type “console” in the “Search for templates” box and select the C# version of the Console App template. Click Next to continue.

Give your app a name and click Next to continue. Under Framework, you might see different options depending on what updates you have installed. Select the newest version of .NET you have installed, which is probably 6.0 or 7.0 in VS2022.

Click “Create” to finish the initial setup of your application.

Creating Our API Classes

Now that we have our code stub to start with, the next step is to create a few C# classes to work with the results that come back from our JSON API. You could create these manually. But why go to the trouble of doing that when there are resources to do it for you? There are a number of websites out there that will convert JSON into C# classes. Here are a few to try:

They all work pretty much the same way. You get a sample JSON output from your API, paste it into the tool, and click a button. The tool analyzes the JSON content and structure and generates a set of C# classes that can be used to process the JSON in your application.

But first, we’ll need our sample JSON content. So grab the URL you generated from the API above (https://opentdb.com/api.php?amount=10) and copy and paste the URL into a new browser window. The result will be a plain webpage of text in JSON format that was output from. It will look something like this:

{"response_code":0,"results":[{"category":"General Knowledge","type":"multiple","difficulty":"medium","question":"What name represents the letter "M" in the NATO phonetic alphabet?","correct_answer":"Mike","incorrect_answers":["Matthew","Mark","Max"]},{"category":"Geography","type":"multiple","difficulty":"medium","question":"What is the name of the former country that was succeeded by countries such as Serbia, Croatia and Slovenia?","correct_answer":"Yugoslavia","incorrect_answers":["Czechoslovakia","Abkhazia","South Ossetia"]},{"category":"Entertainment: Music","type":"multiple","difficulty":"medium","question":"What animal is featured on the cover of English electronic music group The Prodigy's album, "The Fat of the Land"?","correct_answer":"Crab","incorrect_answers":["Fox","Elephant","Tiger"]},{"category":"Entertainment: Comics","type":"boolean","difficulty":"easy","question":"Deadpool's identity is Slade Wilson.","correct_answer":"False","incorrect_answers":["True"]},{"category":"Entertainment: Music","type":"multiple","difficulty":"medium","question":"Pink Floyd made this song for their previous lead singer Syd Barrett.","correct_answer":"Shine On You Crazy Diamond","incorrect_answers":["Wish You Were Here","Have A Cigar","Welcome to the Machine"]},{"category":"Science: Computers","type":"boolean","difficulty":"easy","question":"In most programming languages, the operator ++ is equivalent to the statement "+= 1".","correct_answer":"True","incorrect_answers":["False"]},{"category":"History","type":"boolean","difficulty":"easy","question":"In World War ll, Great Britian used inflatable tanks on the ports of Great Britain to divert Hitler away from Normandy\/D-day landing.","correct_answer":"True","incorrect_answers":["False"]},{"category":"Entertainment: Cartoon & Animations","type":"multiple","difficulty":"medium","question":"How many episodes were in season five of Samurai Jack?","correct_answer":"10","incorrect_answers":["12","11","13"]},{"category":"Entertainment: Video Games","type":"boolean","difficulty":"easy","question":"Codemasters is the developer of the Gran Turismo series.","correct_answer":"False","incorrect_answers":["True"]},{"category":"Entertainment: Video Games","type":"multiple","difficulty":"medium","question":"What is a Tetris piece called?","correct_answer":"Tetromino","incorrect_answers":["Tetratile","Tetris","Tetripiece"]}]}

Take that output and copy and paste it into one of the converter tools listed above. I tend to use the JSON2CSharp tool as it has a few handy optional extras for the output to help you create more useful C# classes. For this example, I selected “Use Nullable Types” and “Use JsonPropertyName (.NET Core)”, and I got the following C# classes generated.

// Root myDeserializedClass = JsonSerializer.Deserialize<Root>(myJsonResponse);
    public class Result
    {
        [JsonPropertyName("category")]
        public string category { get; set; }
        [JsonPropertyName("type")]
        public string type { get; set; }
        [JsonPropertyName("difficulty")]
        public string difficulty { get; set; }
        [JsonPropertyName("question")]
        public string question { get; set; }
        [JsonPropertyName("correct_answer")]
        public string correct_answer { get; set; }
        [JsonPropertyName("incorrect_answers")]
        public List<string> incorrect_answers { get; set; }
    }
    public class Root
    {
        [JsonPropertyName("response_code")]
        public int? response_code { get; set; }
        [JsonPropertyName("results")]
        public List<Result> results { get; set; }
    }

This gives us a top-level Root class, as well as a Result class for each of the questions and answer sets. It also gives us an example at the very top of how to instantiate and process our API response. We’ll go ahead and throw those into a file in our app project.

Setting Up Our Game

We’ve got our API classes, so let’s structure the game logic. We’ll want to create a class to hold all the game info, which we’ll call Game. In our Game class we’re going to need variables to store the total number of questions, the number of the current question, the user’s current score, and the list of questions and answers themselves.

In addition to a default constructor, we’ll also add two functions to start with. One of the functions will call the API to get our questions and the other will retrieve the current question and return it to the program flow. It should look something like this:

public class Game
    {
        private Result[] _questions;
        public int TotalQuestions;
        public int CurrentQuestion;
        public int CurrentScore;
        public Game()
        {
            TotalQuestions = 5;
            CurrentQuestion = 0;
            CurrentScore = 0;
        }
        public async Task GetQuestions()
        {
            _questions = new Result[TotalQuestions];
            CurrentQuestion = 0;
            CurrentScore = 0;
            var uri = $"https://opentdb.com/api.php?amount={TotalQuestions}";
            var client = new HttpClient();
            var myJsonResponse = await client.GetFromJsonAsync<Root>(uri);
            if (myJsonResponse != null && myJsonResponse.results.Any())
            {
                _questions = myJsonResponse.results.ToArray();
            }
        }
        public Result GetCurrentQuestion()
        {
            CurrentQuestion++;
            return _questions[CurrentQuestion - 1];
        }
    }

We’ll also need a helper function to shuffle our list of answers so that the correct answer doesn’t always appear in the same position in the list each time a question is asked. It doesn’t need to be fancy or a true random shuffle, so a pseudo-random shuffle like the following extension method will work:

 private static Random rng = new Random();
        public static void Shuffle<T>(this IList<T> list)
        {
            int n = list.Count;
            while (n > 1)
            {
                n--;
                int k = rng.Next(n + 1);
                T value = list[k];
                list[k] = list[n];
                list[n] = value;
            }
        }

The flow of the app we’ll just leave in our main Program.cs file. There, it will give an intro, then when the player is ready, we’ll start shooting questions at them. For each question, we’ll provide the question and the list of answers on the screen and have them respond with their choice. We’ll tell them if they’re right or wrong, then move on to the next question. Once they answer all the questions, we’ll show their final score: Simple and straightforward for our MVP version.

We’ll contain the questions in a simple while loop. Our pseudo-logic for each iteration will be:

  1. Retrieve the current question
  2. Randomize the list of answers
  3. Display the question and the list of answers
  4. Wait for the user input to answer the question
  5. Check the answer against the correct answer
  6. Update the score
  7. Give user feedback on success or failure

And our final loop code will look like this:

//iterate the questions
while (game.CurrentQuestion < game.TotalQuestions)
{
    var question = game.GetCurrentQuestion();
    //build our list of answers
    var strAnswers = new List<string>
    {
        WebUtility.HtmlDecode(question.correct_answer)
    };
    foreach (var item in question.incorrect_answers)
    {
        strAnswers.Add(WebUtility.HtmlDecode(item));
    }
    strAnswers.Shuffle();
    //build the answer output and store which one is correct
    var answers = new Dictionary<int, string>();
    int correct = 0;
    for (var i = 1; i <= strAnswers.Count; i++)
    {
        answers[i] = strAnswers[i - 1];
        if (string.Equals(strAnswers[i - 1], question.correct_answer)) { correct = i; }
    }
    var totalAnswers = strAnswers.Count;
    //ask our question and get the answer
    Console.WriteLine();
    Console.WriteLine("-------------------------------------");
    Console.WriteLine($"Question #{game.CurrentQuestion}");
    Console.WriteLine();
    Console.WriteLine(WebUtility.HtmlDecode(question.question));
    Console.WriteLine();
    for (var a = 1; a <= totalAnswers; a++)
    {
        Console.WriteLine($"{a}: {answers[a]}");
    }
    Console.Write("Please select number of the correct answer: ");
    var userKeyAnswer = Console.ReadLine();
    //check if the answer is correct and process result
    var userAnswer = 0;
    int.TryParse(userKeyAnswer, out userAnswer);
    if (userAnswer == correct && userAnswer != 0)
    {
        game.CurrentScore++;
        Console.WriteLine();
        Console.WriteLine("CORRECT!!!!!");
    }
    else
    {
        Console.WriteLine();
        Console.WriteLine("Sorry, that's wrong!!!!!");
    }
}

Play The Game!

That’s all we need to make the minimum viable product (MVP) of our game work. So hit F5 and answer the questions!

What’s Next?

But what else can we do with our game and API? Well, there are a ton of features you can to your base MVP:

  • Let the user pick categories
  • Let the user pick the number of questions
  • Various multiplayer approaches
  • Add colors and formatting, and make it look nice
  • Add snarky feedback based on their scores
  • Reveal the correct answer when they get it wrong
  • Turn it into a GUI-based game on the web or mobile device or desktop
  • Post the results to social media using Power Automate

There’s really no end to the features you could add. It’s all up to your imagination and level of effort. You can see the final code in my Git repo here. I’ll probably follow this up with another post or video later that implements some of those suggestions. But hopefully, all this shows you just how quickly you can take any JSON-based API that’s available on the internet and turn it into an app.

Leave a Reply