0

I have a discord bot that can get a key from a JSON file and return the value depending on the command a user inputs. I want to be able to access complex JSON keys (like "charQuote") and return the associative value (which would be "Video Game character quote here").

{
"characterKey": 
{
"charName":"Video Game character name here",
"charQuote": "Video Game character quote here", 
"charImageURL": "Video Game character image url here"
}

//I omitted 11 other complex objects that are similar to this one
}

Other answers like:

How do I deserialize a complex JSON object in C# .NET?

How to get Key from nested object with a Json Object and return in array?

how to get the key from json object and convert into an array?

...deserialize and return the complex objects and/or keys as an array. But I want to return a single key value from one complex object.

Here, I have a class that manages a separate JSON file with simple JSON objects:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.IO;

namespace discordbot.Modules
{
    class Utilities
    {
        private static Dictionary<string, string> alerts;
        private const string File_Path = "json files/alerts.json";

        static Utilities()
        {
            string json = File.ReadAllText(File_Path);
            var data = JsonConvert.DeserializeObject<dynamic>(json);
            alerts = data.ToObject<Dictionary<string, string>>();          
        }

        public static string GetAlert(string key) //"hello" == key
        {
            if (alerts.ContainsKey(key))
                return alerts[key]; //returns "Hello World" which is the value
            return "";
        }

This class returns the value from the inputted key. An example asynchronous task method that would use this class would be:

[Command("test")]
        public async Task HelloWorldAsync(string hello = "hello")
        {
            await ReplyAsync(Utilities.GetAlert(hello));
        }

And here's the class that associates with the JSON file that contains complex objects.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;

namespace discordbot.Modules
{
    public static class CharacterUtilities 
    {

        //private static List<Dictionary<string, string>> QuoteAndPicture = new List<Dictionary<string, string>>();
        private static Dictionary<string, string> QuoteAndPicture = new Dictionary<string, string>();

        private const string File_Path = "json files/quotesandimages.json";

        static CharacterUtilities()
        {
            if(!File.Exists(File_Path))
            {
                File.WriteAllText(File_Path, "");
            }

            string json = File.ReadAllText(File_Path); 
            var data = JsonConvert.DeserializeObject<dynamic>(json);
            QuoteAndPicture = data.ToObject<Dictionary<string, string>>();

        }

        public static string GetQuoteImageName(string key) 
        {                        
            if (QuoteAndPicture.ContainsKey(key)))
                return QuoteAndPicture[key];
            return "";
        }

Asynchronous method to invoke GetQuoteImageName

[Command("ex")] [RequireOwner]
        public async Task CharacterAsync(string characterContext)
        {                   
            await ReplyAsync(CharacterUtilities.GetQuoteImageName(characterContext)); 
        }

As it is right now, if I invoke the GetQuoteImageName method, I get the error: The type initializer for 'discordbot.Modules.CharacterUtilities' threw an exception.

So I'm wondering if there's a similar way to return a singular value from a complex object instead of all the complex values, similar to the first class above.

NotNameless
  • 41
  • 3
  • 9

1 Answers1

1

This will search through the json based on the keysToSearchThrough.

        var data = JsonConvert.DeserializeObject<dynamic>(jsonString);
        // The keys you have to search through.
        string[] keysToSearchThrough = new string[] { "characterKey", "charName" };

        string result = string.Empty;

        // The current data it is travesing through
        Dictionary<string, dynamic> currTraversedData = data.ToObject<Dictionary<string, dynamic>>();

        for (int i = 0; i < keysToSearchThrough.Length; ++i) {

            if (currTraversedData.ContainsKey(keysToSearchThrough[i])) {
                // Get the value if the key current exists
                dynamic currTravesedDataValue = currTraversedData[keysToSearchThrough[i]];

                // Check if this 'currTravesedDataValue' can be converted to a 'Dictionary<string, dynamic>'
                if (IsStringDynamicDictionary(currTravesedDataValue)) {
                    // There is still more to travese through
                    currTraversedData = currTravesedDataValue.ToObject<Dictionary<string, dynamic>>();
                } else {
                    // There is no more to travese through. (This is the result)
                    result = currTravesedDataValue.ToString();

                    // TODO: Some error checking in the event that we reached the result early, even though we still have more keys to travase through
                    break;
                }

            } else {
                // One of the keys to search through was probably invalid.
            }
        }

        // ...

        bool IsStringDynamicDictionary(dynamic input) {
            try {
                input.ToObject<Dictionary<string, dynamic>>();
                return true;
            } catch (Exception) {
                return false;
            }
        }

This initially converts the json data to Dictionary<string, dynamic>.

The reason why we have dynamic behind is because the Value can either be string or Dictionary<string, string> at any given point in time.

We then loop through the keysToSearchThrough, checking if the key exists in the current traversed dictionary.

If the key does exists, we then check if the Value of the current traversed dictionary is of Type Dictionary<string, dynamic>.

If it is, that means that we would have more to traverse through.
If not, it means that we probably reached the end.

Example
In "charName":"Character name", the Value is a type of String. Which marks that we reached the end.
In "charName": "Character name", "charQuote": "Character Quote", the Value would be a type of Dictionary<string, dynamic>. Which marks that we can still traverse through the data.

If it isn't obvious enough, the major downside is this code looks really messy.

WQYeo
  • 3,973
  • 2
  • 17
  • 26
  • Since I'll have 11 other complex objects, wouldn't I have to create 11 classes just for all of them? – NotNameless Aug 05 '19 at 13:37
  • 1
    @UchennaOnuigbo Sorry, I didn't originally get what you meant. I edited my answer to suit your question. Hope it helps – WQYeo Aug 05 '19 at 15:32