#๐Ÿ”’ json read/write

135 messages ยท Page 1 of 1 (latest)

violet trellis
#
def main():
    with open("tasks.json", "r") as f:
      pass
      #code here to return back into objects
    while True:
        print("\nTASK TRACKER\n")
        option_choice = show_options(options)
        if option_choice is None:
            exit_program()
        options[option_choice - 1].func()

this is where I write to the json

def exit_program():
    print("Exiting program...")
    for task in tasks:
        jsonlist = [dataclasses.asdict(task) for task in tasks]
        with open("tasks.json", "w") as f:
            json.dump(jsonlist, f)
runic flameBOT
#

@violet trellis

Python help channel opened

Remember to:

  • Ask your Python question, not if you can ask or if there's an expert who can help.
  • Show a code sample as text (rather than a screenshot) and the error message, if you've got one.
  • Explain what you expect to happen and what actually happens.

:warning: Do not pip install anything that isn't related to your question, especially if asked to over DMs.

violet trellis
#

how would I use the data in jsonlist to convert it into a list with objects of dataclasses

#

tasks is initialised outside as a empty list so would I need to make it global

rapid pulsar
#

yeah but what is tasks? A list of dataclasses?

#

Is that the same as jsonlist?

#

Kinda confusing tbh

violet trellis
#

[Task(name='Eat', completed=False), Task(name='Sleep', completed=False)]

rapid pulsar
violet trellis
#

oh hold on

#

i messed up the exit function

rapid pulsar
#

Instead of taking all of the tasks list and saving them to json, why not as soon as there are changes to any task, save them to json file?

violet trellis
#

how would I read it and convert it back into list form tho

rapid pulsar
#

If you're talking about on start, you would read off the JSON file and then re-initialise the Task class object and append them to the list.

#

I assume you created the task objects like Task("Eat", False) etc

jaunty root
#

yes, rename the exit_program() function to save_to_json() for example and call it each time there has been a change to the data, that way you will not experience data loss if the program doesn't exit cleanly

#

you also don't want the line

for task in tasks:
```in that function as you are already looping through the data on this line
```py
jsonlist = [dataclasses.asdict(task) for task in tasks]
```so just remove the former and remove one level of indentation
violet trellis
#

i was trynna do something else then realised i could use list comprehension

#

just trynna make a save function now

violet trellis
#

asdict right?

#

kinda confused

def save(task):
   print(type(task))
   dataclasses.asdict(task)
   print(type(task))
   with open("tasks.json", "w") as f:
        json.dump(task, f)
jaunty root
#

how does your import lines look like in the program?

violet trellis
#

!paste

runic flameBOT
#
Pasting large amounts of code

If your code is too long to fit in a codeblock in Discord, you can paste your code here:
https://paste.pythondiscord.com/

After pasting your code, save it by clicking the Paste! button in the bottom left, or by pressing CTRL + S. After doing that, you will be navigated to the new paste's page. Copy the URL and post it here so others can see it.

violet trellis
#

ill just send code

violet trellis
#

is it because it isnt actually a dataclass

jaunty root
#

you don't really need the line

import dataclasses
```as you already have
```py
from dataclasses import dataclass, asdict
```you can just do
```py
jsonlist = [asdict(task) for task in tasks]
violet trellis
#
raise TypeError(f'Object of type {o.__class__.__name__} '
                    f'is not JSON serializable')
TypeError: Object of type Task is not JSON serializable
violet trellis
#

error above

#

line 20, in save
json.dump(task, f)

#

oh wit

#

should be list

jaunty root
#

oh, you can't just do

    with open("tasks.json", "w") as f:
        json.dump(task, f)
violet trellis
#

meant to be jsonlist

jaunty root
#

you had the right idea from the start

violet trellis
#

yep works now

#

in the json file:

[{"name": "Eat", "completed": false}]
#

now i gotta handle the remove logic and stuff

jaunty root
#
def save(tasks):
    jsonlist = [asdict(task) for task in tasks]
    with open("tasks.json", "w") as f:
        json.dump(jsonlist, f)
#

you need to pass in tasks (the whole list, not just one task)

violet trellis
#

yeah the adding works

#

now i gotta read off file and initialise into tasks on start

jaunty root
#

you would first have to empty the list and the start adding new elements to the list one by one after initialising a new Task object

#

if the filename to use to save the tasks aren't a parameter to the function you probably want to make it a global constant

violet trellis
jaunty root
#

something like this if you want to start using type hints in your code as well
most (more or less all but the load() function) of it is your original code from different places with just some small adaptions

import json
from dataclasses import dataclass, asdict

TASKS_JSON_FILENAME = "tasks.json"


@dataclass
class Task:
    name: str
    completed: bool = False


tasks: list[Task] = []


def load() -> None:
    with open(TASKS_JSON_FILENAME) as file:
        json_list = json.load(file)
    tasks.clear()
    for item in json_list:
        tasks.append(Task(**item))


def save(tasks: list[Task]) -> None:
    json_list = [asdict(task) for task in tasks]
    with open(TASKS_JSON_FILENAME, "w") as file:
        json.dump(json_list, file)
#

okay, i could have done type hinting for both places where we initialised json_list too
something like

json_list: list[dict[str, str]] = ...
```if you really want to be pedantic
violet trellis
#

i'm going to continue to attempt it the way im doing it

#

[{'name': 'Eat', 'completed': False}, {'name': 'Sleep', 'completed': False}]

how would I seperate this?

jaunty root
#

you would go through each element of the outer list with a for loop over the variable that holds the list

#

that is what line

for item in json_list:
```in the above code does
#

but before you do that you would need to empty the global tasks list variable
that is why i call tasks.clear() just before that loop

violet trellis
#

when I start the code tasks is set to []

#

anyway

jaunty root
#

just to make sure that if you call that load function at any other time in the program it won't corrupt your state/data, just to prevent possible bugs and make the program more reliable

violet trellis
#

tasks.append(Task(**item))

#

what does ** do

jaunty root
#

the one part that probably needs some explanation is probably the double asterisk

#

haha, just what i was going to explain ๐Ÿ™‚

violet trellis
#

thanks for helping out btw

jaunty root
#

anyways, the double asterisk will expand the dict to key value pairs
so in very simple terms you can think of it as removing the curly braces {} around the dictionary so that just it's content is passed into the function as separate arguments instead of just passing the whole dictionary as one single argument

#

this is required as the dataclass requires you to give it the values for each field in the correct order or pass them as key value pairs with the name of each field and it's corresponding value, in the later case the order doesn't matter, which is nice and also adds to the robustness of the code

#

just one asterisk will expand a list in the same way, but doing that with a dictionary will just give you a list of the names of the keys, which is not really what you want here as you would loss the actual values that you want to restore

violet trellis
jaunty root
#

so
for item being

{'name': 'Eat', 'completed': False}
```with just
```py
Task(item)
```the result would be like writing
```py
Task({'name': 'Eat', 'completed': False})
```which is **not** valid for your `Task` dataclass
but with
```py
Task(**item)
```it is like writing
```py
Task('name': 'Eat', 'completed': False)
```which is totally valid for your `Task` dataclass
violet trellis
#

i keep procrastinating ๐Ÿคฃ
i click on my ide, type a word then go back on discord and look in discussion

#

i'm gonna finish this now

jaunty root
jaunty root
violet trellis
#

next i need to handle removing tasks and also marking as completed

#

<html>Incompatible types.<br/>Required: object. Actual: array.

why is this being marked as a problem in the json file?

jaunty root
violet trellis
#
def my_input(msg, out_type):
    while True:
        user_input = input(msg)
        if out_type == int and user_input.lower() == "q":
            return "q"
        try:
            return out_type(user_input)
        except ValueError:
            print("Invalid input. Please enter a valid number or 'Q' to quit.")
        except KeyboardInterrupt:
            print("Input cancelled by user.")
            continue
jaunty root
jaunty root
violet trellis
#

what do you mean?

#

what is the difference?

jaunty root
#

to use is instead of ==

violet trellis
#

oh wait

violet trellis
#

returns "q"

#

they both do

#

actually

jaunty root
#
my_input("q", int)
```will print `q` as the prompt for the user
violet trellis
#

ah yeah

rapid pulsar
#

!d is

runic flameBOT
#
is

6.10.3. Identity comparisons

The operators is and is not test for an objectโ€™s identity: x is y is true if and only if x and y are the same object. An Objectโ€™s identity is determined using the id() function. x is not y yields the inverse truth value. [4]

violet trellis
#

i meant

#

if input is "q"

#

and expected is int

#

lol

jaunty root
rapid pulsar
#

Why not isinstance?

violet trellis
jaunty root
#

it's ran as out_type(user_input) where out_type in this instance is int

rapid pulsar
#

i see

violet trellis
#

so I should use == when comparing values
is when comparing types

jaunty root
#

kind of, but yes, use == when comparing values

#

is is for checking that it's the same instance of a class, and all types in python are classes

violet trellis
#

how would I check if the json file exists before I run the function to read json

#

as it's not gonna exist for the first execution of code

#

after doing some research i can use
os.path and pathlib

jaunty root
#

i would say that you shouldn't check that, it can cause race conditions in your code and isn't pythonic
instead handle the error gracefully with a try: and except FileNotFoundError as e: around the with open part

try:
    with open(TASKS_JSON_FILENAME) as file:
        ...
except FileNotFoundError as e:
    pass
```or better yet use `suppress()` from `contextlib` instead of the `except` with only a `pass` in it:
```py
from contextlib import suppress:

with suppress(FileNotFoundError):
    with open(TASKS_JSON_FILENAME) as file:
        ...
```this is the modern and pythonic way to do it
#

about the differences of "look before you leap" (LBYL) and "easier to ask forgiveness than permission" (EAFP), the latter is often the preferred way in python (and should be in most languages in this situation that aim for correctness and robustness)

violet trellis
#

which is what i done in this scenario then

#

i wrote the function, it gave me a FileNotFoundError

#

now i need to handle the exception

jaunty root
#

yeah, and the easiest way to handle it in this instance is to just ignore/suppress it and go on with your day

violet trellis
#

yeah as it means there is no data saved

#

so nothing to read

#

and can run the program as normal

jaunty root
#

exactly, no need to print any errors to the user or anything like that

runic flameBOT
#
Python help channel closed for inactivity

This help channel has been closed. Feel free to create a new post in #1035199133436354600. To maximize your chances of getting a response, check out this guide on asking good questions.