0

I have a Python 3.5 program that creates an inventory of objects. I created a class of Trampolines (color, size, spring, etc.). I constantly will create new instances of the class and I then save a dictionary of them. The dictionary looks like this:

my_dict = {name: instance} and the types are like so {"string": "object"}

My issue is that I want to know how to save this inventory list so that I can start where I left off the last time I closed the program.

I don't want to use pickle because I'm trying to learn secure ways to do this for more important versions in the future.

I thought about using sqlite3, so any tips on how to do this easily would be appreciated.

My preferred solution would state how to do it with the json module. I tried it, but the error I got was:

__main__.Trampoline object at 0x00032432... is not JSON serializable

Edit:

Below is the code I used when I got the error:

out_file = open(input("What do you want to save it as?  "), "w")
json.dump(my_dict, out_file, indent=4)
out_file.close()

End of Edit

I've done a good amount of research, and saw that there's also an issue with many of these save options that you can only do one object per 'save file', but that the work around to this is that you use a dictionary of objects, such as the one I made. Any info clarifying this would be great, too!

martineau
  • 119,623
  • 25
  • 170
  • 301
AmericanMade
  • 453
  • 1
  • 9
  • 22
  • To use json you will have to write your own json serializer (which is not hard) or make non-json objects into strings first. For instance how exactly do you want datetime objects saved. – cmd Jul 11 '16 at 19:49
  • Ok, I had seen something about that when researching but didn't quite understand. If you make them into strings first, will they still function the same when you restart the program or do you somehow convert back to an object? Also, I don't even plan to use datetime just yet. Probably in the near future. – AmericanMade Jul 11 '16 at 19:58
  • `https://docs.python.org/2.7/library/json.html#encoders-and-decoders` – cmd Jul 11 '16 at 20:51
  • Thanks @cmd I was actually just reading the python 3.5 version of that. I have one more question..should I implement this serializer INSIDE of the Trampoline class and call it before I append the object to the dictionary or do I just do it once I have the dictionary of objects already made? Thanks again! – AmericanMade Jul 11 '16 at 21:04
  • You might might find [my answer](https://stackoverflow.com/a/18561055/355230) to another, related question useful. – martineau Jan 30 '18 at 01:45

3 Answers3

2

What you might be able to do is saving the instance's attributes to a CSV-file and then just create it when starting up. This might be a bit too much code and is possible not the best way. One obvious problem is that it doesn't work if you don't have the same amount of attributes as parameters, which should be possible to fix if necessary I believe. I just thought I might try and post and see if it helps :)

import json


class Trampoline:
    def __init__(self, color, size, height, spring):
        self.color = color
        self.size = size
        self.height = height
        self.spring = spring

    def __repr__(self):
        return "Attributes: {}, {}, {}, {}".format(self.color, self.size, self.height, self.spring)


my_dict = {
    "name1": Trampoline('red', 100, 2.3, True),
    "name2": Trampoline('blue', 50, 2.1, False),
    "name3": Trampoline('green', 25, 1.8, True),
    "name5": Trampoline('white', 10, 2.6, False),
    "name6": Trampoline('black', 0, 1.4, True),
    "name7": Trampoline('purple', -33, 3.0, True),
    "name8": Trampoline('orange', -999, 2.5, False),
}


def save(my_dict):
    with open('save_file.txt', 'w') as file:
        temp = {}
        for name, instance in my_dict.items():
            attributes = {}
            for attribute_name, attribute_value in instance.__dict__.items():
                attributes[attribute_name] = attribute_value
            temp[name] = attributes
        json.dump(temp, file)


def load():
    with open('save_file.txt', 'r') as file:
        my_dict = {}
        x = json.load(file)
        for name, attributes in x.items():
            my_dict[name] = Trampoline(**attributes)
    return my_dict


# CHECK IF IT WORKS!
save(my_dict)
my_dict = load()
print("\n".join(["{}  |  {}".format(name, instance) for name, instance in sorted(my_dict.items())]))
Ted Klein Bergman
  • 9,146
  • 4
  • 29
  • 50
  • That seems like a good possible solution. I'm working something a little different right now, but I might try this out after. Thanks! – AmericanMade Jul 11 '16 at 21:12
1

Here is an example of a class that handles datetime objects.

class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            if obj.tzinfo:
                obj = obj.astimezone(isodate.tzinfo.UTC).replace(tzinfo=None)
            return obj.isoformat()[:23] + 'Z'
        return json.JSONEncoder.default(self, obj)

when you encode to json the default function of the cls is called with object you passed. If you want to handle a type that is not part of the standard json.JSONEncoder.default you need to intercept it and return how you want it handled as a valid json type. In this example I turned the datetime into a str and returned that. If its not one of the types I want to special case, I just pass it along to the standard json.JSONEncoder.default handler.

To use this class you need to pass it in the cls param of json.dump or json.dumps:

json.dumps(obj, cls=CustomEncoder)

Decoding is done the same way but with json.JSONDecoder, json.load, and json.loads. However you can not match on type, so you will need to either add an 'hint' in encoding for decoding or know what type it needs to decode.

cmd
  • 5,754
  • 16
  • 30
0

For a simple class, you can make an easy serializer as below. This will take all of the properties of your Trampoline object and put them into a dictionary and then into JSON.

class Trampoline(object):
    ...
    def serialize(self):
        return json.dumps(vars(self))

If your class is a bit more complicated, then write a more complicated serializer :)

Brian
  • 1,659
  • 12
  • 17