-1

I want to append multiple list items to a JSON file, but it creates a list within a list, and therefore I cannot acces the list from python. Since the code is overwriting existing data in the JSON file, there should not be any list there. I also tried it by having just an text in the file without brackets. It just creates a list within a list so [["x", "y","z"]] instead of ["x", "y","z"]

import json

filename = 'vocabulary.json'

print("Reading %s" % filename)
try:
    with open(filename, "rt") as fp:
        data = json.load(fp)
    print("Data: %s" % data)#check
except IOError:
    print("Could not read file, starting from scratch")
    data = []

# Add some data
TEMPORARY_LIST = []
new_word = input("give new word: ")
TEMPORARY_LIST.append(new_word.split())
print(TEMPORARY_LIST)#check
data = TEMPORARY_LIST

print("Overwriting %s" % filename)
with open(filename, "wt") as fp:
    json.dump(data, fp)

example and output with appending list with split words:

Reading vocabulary.json Data: [['my', 'dads', 'house', 'is', 'nice']]

give new word: but my house is nicer

[['but', 'my', 'house', 'is', 'nicer']]

Overwriting vocabulary.json

Osman P.
  • 45
  • 7
  • 2
    `TEMPORARY_LIST.append(new_word.split())` is going to create a list within a list because when `split()` is used, it splits a string into a list. – gmdev Nov 05 '20 at 15:41
  • @gmdev ah ok.. any suggestions on dumping multiple items into JSON? I also can't seem to append, it creates a copy of the existing list + the text I want to append. – Osman P. Nov 05 '20 at 16:03
  • What kind of items are you looking to dump? Lists, dictionaries, strings, etc. Can you update your question with some sample input/output? – gmdev Nov 05 '20 at 17:06
  • `new_word.split()` gives you a list. You want to add all elements of this list into `TEMPORARY_LIST`. Does this answer your question? [How do I concatenate two lists in Python?](https://stackoverflow.com/questions/1720421/how-do-i-concatenate-two-lists-in-python) – Pranav Hosangadi Nov 05 '20 at 17:24
  • @gmdev I need a list of words for a small program displaying words, and want to ability to manage the list of words from the program. I'm looking for an option to add multiple words to the existing list and completely overwrite the existing list in JSON. The latter is possible, but I can only append one word and when appending the next word, it overwrites the existing one.. so I'm stuck.. – Osman P. Nov 05 '20 at 17:26
  • @pranav hosangadi thank you for your comment. I know about joining lists, it wasn't what I needed. My goal is to be able to manage lists in json from the program interface. Both append items and overwrite existing lists in the JSON file. Also, while doing this i want to be able to append multiple items in one go, hence the idea of using split. I'm a total noob, so if you have other solutions I'm all ears/eyes :) – Osman P. Nov 05 '20 at 18:10

1 Answers1

0

So, if I understand what you are trying to accomplish correctly, it looks like you are trying to overwrite a list in a JSON file with a new list created from user input. For easiest data manipulation, set up your JSON file in dictionary form:

{
    "words": [
        "my", 
        "dad's", 
        "house", 
        "is", 
        "nice"
    ]
}

You should then set up functions to separate your functionality to make it more manageable:

def load_json(filename):
    with open(filename, "r") as f:
        return json.load(f)

Now, we can use those functions to load the JSON, access the words list, and overwrite it with the new word.

data = load_json("vocabulary.json")
new_word = input("Give new word: ").split()

data["words"] = new_word
write_json("vocabulary.json", data)

If the user inputs "but my house is nicer", the JSON file will look like this:

{
    "words": [
        "but",
        "my",
        "house",
        "is",
        "nicer"
    ]
}

Edit

Okay, I have a few suggestions to make before I get into solving the issue. Firstly, it's great that you have delegated much of the functionality of the program over to respective functions. However, using global variables is generally discouraged because it makes things extremely difficult to debug as any of the functions that use that variable could have mutated it by accident. To fix this, use method parameters and pass around the data accordingly. With small programs like this, you can think of the main() method as the point in which all data comes to and from. This means that the main() function will pass data to other functions and receive new or edited data back. One final recommendation, you should only be using all capital letters for variable names if they are going to be constant. For example, PI = 3.14159 is a constant, so it is conventional to make "pi" all caps.

Without using global, main() will look much cleaner:

def main():
    choice = input("Do you want to start or manage the list? (start/manage)")
    if choice == "start":
        data = load_json()
        words = data["words"]
        dictee(words)
    elif choice == "manage":
        manage_list()

You can use the load_json() function from earlier (notice that I deleted write_json(), more on that later) if the user chooses to start the game. If the user chooses to manage the file, we can write something like this:

def manage_list():
    choice = input("Do you want to add or clear the list? (add/clear)")
    if choice == "add":
        words_to_add = get_new_words()
        add_words("vocabulary.json", words_to_add)
    elif choice == "clear":
        clear_words("vocabulary.json")

We get the user input first and then we can call two other functions, add_words() and clear_words():

def add_words(filename, words):
    with open(filename, "r+") as f:
        data = json.load(f)
        data["words"].extend(words)
        f.seek(0)
        json.dump(data, f, indent=4)

def clear_words(filename):
    with open(filename, "w+") as f:
        data = {"words":[]}
        json.dump(data, f, indent=4)

I did not utilize the load_json() function in the two functions above. My reasoning for this is because it would call for opening the file more times than needed, which would hurt performance. Furthermore, in these two functions, we already need to open the file, so it is okayt to load the JSON data here because it can be done with only one line: data = json.load(f). You may also notice that in add_words(), the file mode is "r+". This is the basic mode for reading and writing. "w+" is used in clear_words(), because "w+" not only opens the file for reading and writing, it overwrites the file if the file exists (that is also why we don't need to load the JSON data in clear_words()). Because we have these two functions for writing and/or overwriting data, we don't need the write_json() function that I had initially suggested.

We can then add to the list like so:

>>> Do you want to start or manage the list? (start/manage)manage
>>> Do you want to add or clear the list? (add/clear)add
>>> Please enter the words you want to add, separated by spaces: these are new words

And the JSON file becomes:

{
    "words": [
        "but",
        "my",
        "house",
        "is",
        "nicer",
        "these",
        "are",
        "new",
        "words"
    ]
}

We can then clear the list like so:

>>> Do you want to start or manage the list? (start/manage)manage
>>> Do you want to add or clear the list? (add/clear)clear

And the JSON file becomes:

{
    "words": []
}

Great! Now, we implemented the ability for the user to manage the list. Let's move on to creating the functionality for the game: dictee()

You mentioned that you want to randomly select an item from a list and remove it from that list so it doesn't get asked twice. There are a multitude of ways you can accomplish this. For example, you could use random.shuffle:

def dictee(words):
    correct = 0
    incorrect = 0

    random.shuffle(words)
    for word in words:
        # ask word
        # evaluate response
        # increment correct/incorrect
        # ask if you want to play again
        pass

random.shuffle randomly shuffles the list around. Then, you can iterate throught the list using for word in words: and start the game. You don't necessarily need to use random.choice here because when using random.shuffle and iterating through it, you are essentially selecting random values.

I hope this helped illustrate how powerful functions and function parameters are. They not only help you separate your code, but also make it easier to manage, understand, and write cleaner code.

gmdev
  • 2,725
  • 2
  • 13
  • 28
  • setting it up in dictionary form does solve that problem, but now I don't now how to acces to vocabulary from the program I wrote. I don't now what the trick is with a dictionary with one key having several values. I think I need to rewrite parts of it, do you have suggestions for: VOCABULARY = json.load(f) and WORD = random.choice(VOCABULARY) and VOCABULARY.remove("{}".format(WORD)). So basically for loading the words, picking one at random and removing the random word so it doesn't show up twice. Or of course a solution with a list in JSON :-) – Osman P. Nov 05 '20 at 21:05
  • @OsmanP. so what are you trying to do exactly? For example, if the words were ["my dad's", "house", "is", "nice"], do you want to remove a random word from that list? – gmdev Nov 05 '20 at 21:26
  • thank you for bearing with me :-) I made a small program for my kid to help her read and write better, actually the reason why I started learning python a few weeks ago. She gets a list of words from school. I want to be able to put that list of words in a json file, import that list in to the program, program shows a random word, removes that word from the list (in the program since it's loaded), so it does show up twice and ends the program when that list is empty. And all of this works fine. But instead of updating the json file manually,I want to be able to do this from the program. – Osman P. Nov 05 '20 at 21:35
  • Here is a link to what i wrote, so you have an idea. Mind you, I just started to learn from a book without any programming knowledge so it might look a bit messy :-) https://www.codepile.net/pile/MaN6xJaL – Osman P. Nov 05 '20 at 21:38
  • Gotcha, I understand now. I am currently in the process of writing the code and will let you know when I update my answer! Thanks for the explanation! – gmdev Nov 06 '20 at 01:23
  • no, thank you! I also like that you separated the functionality. I use this code to test new functionalities, I rewrote everything in tkinter to make it more 'attractive' to her. And that's working fine, but with this code I can ask questions more easily :) – Osman P. Nov 06 '20 at 06:03
  • @OsmanP. I just updated my answer. Let me know if you have any questions :) – gmdev Nov 06 '20 at 13:52
  • Thanks :-) I wanted to test the add_words and clear_words, I keep getting " add_words() missing 2 required positional arguments: 'filename' and 'words'", This is how I used it: def add_words(filename, words): with open("vocabulary.json", "r+") as f: [...] Also, the reason why used so many functions it's I used the tkinter module afterwards. Tkinter requires to create object to use 'command' correctly. It's also the reason for the many globals. Do you think I am approaching it wrong? Here's a working link: https://www.codepile.net/pile/m1ydJxzd sorry it's a bit messy, building it up – Osman P. Nov 06 '20 at 18:33
  • The reason you're getting that error is because you aren't passing variables through the function call. When you call `add_words` or any function with parameters (in this case, it's "filename" and "words," you have to pass variables to that function like so: `add_words("vocabulary.json", words_to_add)` – gmdev Nov 06 '20 at 18:53
  • This is a good resource to learn about methods: https://www.w3schools.com/python/python_functions.asp As far as `tkinter` goes, I personally do not have any experience with that library and I don't want to point you in the wrong direction. – gmdev Nov 06 '20 at 18:55
  • almost there :-) I understand what you mean now. I have an idea on what functions do, but the parameters are something I need to work on, thank you for the link. I still get an error though "TypeError: list indices must be integers or slices, not str" any idea how I can fix that? – Osman P. Nov 06 '20 at 20:41
  • That error means you're trying to index a `list` with a `string`. I have a feeling that you may be trying to access ["words"] on a `list` and not a `dict`. Check to see where you perform that operation and then verify that the variable you're trying to get "words" from is a `dict`. – gmdev Nov 06 '20 at 21:15
  • found it and fixed it. It's adding now, but how come it's splitting up the words and creating list items from individual letters? { "words": [ "n", "e", "w", " ", "w", "o", "r", "d" ] } – Osman P. Nov 06 '20 at 21:26
  • hmmm, it works on my end. Do you have `list(some_variable)` somewhere? Can you edit your question and show the code that you implemented that adds the new words? – gmdev Nov 06 '20 at 21:29
  • I created a new question so it can be easier to find for other. I marked your answer :-) https://stackoverflow.com/questions/64721981/json-data-dump-creating-individual-items-from-letters-when-updating-list-with-ne – Osman P. Nov 06 '20 at 21:43