1

I've been currently working on a warn system for my discord.py bot. I have been going pretty well until I realized I don't know how to add to the file instead of overwriting the file. I don't want to add to the end of the file because I want it to be categorized by discord guilds, then the guilds members. Therefore when someone wants to check their warnings, it will show only their warnings in the specific guild the command is called in. My question is what are all the different ways of storing the information to the .json file?

Ex: warn_list[str(ctx.guild.id)][member.id].add({stuff in here})

As shown in the example I don't want to know the different modes of storing aka 'a', 'w', ect. I want to know, if there are any, what can be added to the end. Like .append() or .add(). I've looked for a list on google but I can't find the info I am looking for. Any help would be much appreciated.

Sincerely, killrebeest

killrebeest
  • 348
  • 2
  • 14
  • 2
    A JSON file is just text, JSON is just a text-based serialization format. Generally, you manipulate some deserialized data structure in Python, serialize the whole thing again overwriting the whole file. You might even manipulate a string representing the JSON, but that is generally to be avoided. If that sort of thing won't scale, then you are looking for a database of some kind, not just a JSON file. – juanpa.arrivillaga Feb 08 '21 at 01:17
  • Tell me what the object `warn_list[str(ctx.guild.id)][member.id]` is and I'll tell you what functions it supports. I was not able to find anything online about a bespoke `warn_list`, so I'm guessing that you created this structure yourself. If you have somewhere to capture terminal output (say a log file) then you could always `print(dir(warn_list[str(ctx.guild.id)][member.id]))` and it will list the functions that it supports. – Razzle Shazl Feb 08 '21 at 01:22
  • @RazzleShazl `warn_list` currently is `json.load`, the line of code is `warn_list = json.load(f)`. What it does is put the info into a `.json` file when someone is warned, but I want it to not overwrite the pre-existing data and instead just add to the pre-existing data. – killrebeest Feb 08 '21 at 01:25
  • `warn_list = json.load(f)` will deserialize the contents of your saved file into a python object. This is the read step. I'm guessing somewhere in your code, there is the reverse transaction where your in-memory object gets serialized into the saved file. Could you find and share that line of code? – Razzle Shazl Feb 08 '21 at 01:31
  • @RazzleShazl Ah yes, `with open('warnings.json', 'w') as f: json.dump(warn_list, f)` – killrebeest Feb 08 '21 at 01:54
  • Thanks for confirming. So `warn_list` is indeed what is serialized. Anything you add to this data structure (a big dictionary) will be persisted. – Razzle Shazl Feb 08 '21 at 01:56
  • Do you have any questions about my answer? – Razzle Shazl Feb 12 '21 at 10:51

1 Answers1

1

How to Add Custom Data that is Persisted to JSON

You are adding to a dictionary, so you would use dict[key] = value to add data, and value = dict[key] to read data 1 . To add to the end of a list, use append().

You're pretty close with what you had. Let's refactor for mental clarity:

warns_for_a_user = warn_list[str(ctx.guild.id)][member.id]

We can directly use this dictionary for storing warning information about this member.id. We need a data structure and I think a list fits the bill (or a dict). You might want time information in these warnings, and time seems like a sensible sort order and/or primary key.

I want to know, if there are any, what can be added to the end

One usually does not modify a .json file directly. The only robust way to modify a .json file is to slurp the entire contents into memory, then save it back to .json after modification.

Think of a .json file as a photograph or snapshot in time. If the scene has changed, you simply throw away the old photograph and take another photograph; if state has updated, we simply junk the old warnings.json and create a new warnings.json.

Step 1: Add New Field and Append to New Field

Given that we have information on the latest warning, we can persist this to the user's history:

# working with givens:
warning = { "type": "spam", "count": "20", "timestamp": 1612756912.987963 }

if not history in warns_for_a_user:
    warns_for_a_user['history'] = []
warns_for_a_user['history'].append([warning['timestamp'], warning])

These new fields that you add should not conflict with anything because they are bespoke fields that are unknown to other parts of code.

If you find it convoluted to wrap each warning in a timestamp, that is because it is. However, it is common to choose a single field (or combination of fields) to be the representative key for the entire object. This helps humans reading the json file directly, and also can simplify the processing logic.

Step 2: Read New Field Data

Later, the user wants to view his warnings. The following prints each warning one at a time (datetime code from SO):

import datetime
for epoch_time, warn in warns_for_a_user['history']:
    _datetime = datetime.datetime.fromtimestamp(epoch_time)
    _human_datetime = _datetime.strftime('%Y-%m-%d %H:%M:%S')
    print(f"{_human_datetime}: [{warn['count']}x] {warn['type']}")

Output:

2021-02-07 20:01:52: [20x] spam

Supported Data Types

The JSON encoder docs has a mapping of how it translates serialized JSON data into deserialized python objects. Note that it converts tuple to list.

Inspecting warnings.json

If you followed my example, you should see the following added under the appropriate user in warnings.json:

{"history": [[1612756912.987963, {"count": 20, "type": "spam", "timestamp": 1612756912.987963}]]}

1 square bracket notation is syntactic sugar for __getitem__() and __setitem__()

Razzle Shazl
  • 1,287
  • 1
  • 8
  • 20
  • Alright, thanks for this amazing deep explanation. I have seen someone do it with this way with the number count. I am assuming you can only do it like that because it will overwrite otherwise and it is better to know which warning. Sorry for not responding sooner as I have not been on my PC, have a great day! – killrebeest Mar 10 '21 at 19:46
  • @killrebeest *I am assuming you can only do it like that because it will overwrite otherwise and it is better to know which warning* -- sorry I don't quite follow this, and is this a question for me? – Razzle Shazl Mar 10 '21 at 20:08