What's the most Pythonic way to pull from structured data with an inconsistent maximum "depth"?

Viewed 42

I've got a JSON file holding different dialog lines for a Discord bot to use, sorted by which bot command triggers the line. It looks something like this:

{
    "!remind": {
        "responses": {
            "welcome": "Reminder set for {time}.",
            "reminder": "At {time} you asked me to remind you {thing}."
        },
        "errors": {
            "R0": "Invalid reminder time.",
            "R1": "Reminder time is in the past."
        },
        "help": "To set a reminder say `!reminder [time] [thing]`"
    },
    "<!timezone, !spoilimage, !crosspost, etc.>": {
        <same structure>
    }
}

I have a function that's meant to access the values stored in the JSON file, do any necessary formatting using kwargs, and return a string. My original approach was

def dialog(command, category, name, **fmt):
    json_data = <json stuff>
    return json_data[command][category][name].format(**fmt)

# Sample call:
pastcommand = <magic>
reply = dialog("!remind", "response", "reminder", time=pastcommand.time, thing=pastcommand.message)
# Although in practice I've made wrapper methods to avoid having to specify all of these args each time

But this will only work for "responses" and "errors", not "help," since in "help" the message to send is a level "shallower".

Two other things to note:

  • It's unlikely there will ever need to be anything in "help" other than the single value.
  • Currently there are no name conflicts between keys in different subcategories, and it's very easy to keep it that way. However, "responses"/"errors"/"help" is consistent across all categories, and some key names are repeated across categories (although I could change that if necessary).

So, in terms of fixing this, I could always just restructure the JSON file, something like

"help": {"main": "To set a reminder say `!reminder [time] [thing]`"}

but I don't like the idea of turning a string into a dict containing just a single string, just to satisfy the constraints of a function that pulls it.

Beyond that, I've run through a number of options, namely: explicitly checking the category and making it a special case (if category == "help"); trying both options with a try/except block, and using pandas.json_normalize (which I'm pretty sure would work? I haven't actually worked with it. Either way, any time a seemingly simple problem brings me to a third-party library, it makes me suspect I'm doing something wrong.).

What I've settled on, so far, is this:

def dialog(*json_keys, **fmt):
    json_data = <json stuff>
    current_level = json_data
    for key in json_keys:
        # Let's pretend I did error-handling here.
        current_level = current_level[key]
    return current_level.format(**fmt)

It's a lot more elegant and more flexible than any of the other things I considered, but I'm self-taught and pretty inexperienced, and I'm wondering if I'm overlooking some better approach.

0 Answers
Related