Maketober Day 13: MicroPython JSON file save and load

Today finds me saving setting information for my nightlight. I want to save the time the “on” colour and the “off” colour along with the times when the light should change from one to the other. Like a good programmer I’ve made a class to hold the setting information:

blue = [0,0,255]
yellow = [255,255,0]

class Settings:

    file_name = 'settings.json'

    def __init__(self, on, off, on_colour, off_colour):
        self.on = on
        self.off = off
        self.on_colour = on_colour
        self.off_colour = off_colour

    @staticmethod
    def getDefault():
        return Settings([06,30],[18,30], yellow, blue)

This is the first part of my Settings class. The __init__ method initialises a setting instance. The getDefault method creates a light that comes on at 6:30 and goes off at 18:30. The light is blue when it is “off” and yellow when “on”. So far, so good. Now I want to store the value of a setting in a JSON file.

def save(self):
    settings_json = ujson.dumps(self.__dict__)
    print('Saving:', settings_json)
    try:
        f = open(Settings.file_name,'w')
        f.write(settings_json)
        f.close()
        return True
    except:
        return False

We can use the save method to ask a Setting instance to save itself. It converts the data dictionary in the Setting object into a string of JSON and then writes this string to a file. If all is well it returns true. If you’re wondering why I’m using ujson rather than json its because this is a special JSON library for MicroPython. So we can now save the setting values in a file. But can we get them back?

@staticmethod
def load():
    try:
        f = open(Settings.file_name,'r')
        settings_string=f.read()
        f.close()
        print('Got settings:', settings_string)
        settings_dict = ujson.loads(settings_string)
        result = Settings.getDefault()
        for setting in settings_dict:
            print("Setting:", setting)
            setattr(result,setting, settings_dict[setting])
        return result
    except:
        print('Settings file load failed')
        return Settings.getDefault()

This is the load method. It is a bit more fiddly then save. The problem is that when we load the JSON back from the stored file we get a dictionary, i.e. a set of name/value pairs. We don’t want a dictionary. We want a Setting object. So we have to build one which contains the loaded data. The first thing we do is create a “default” setting as a target. Then we work through the dictionary that we loaded from the file and use the magical setattr function to set each of the matching attributes in the result to the loaded value. If the loaded data is “sparse” (i.e. some of the attribute values are missing) the result that is created will have the default value for that missing attribute.

Note that I’ve made the load method static which means that it can be called from the Settings class. This means that I can load settings without needing to make a Settings instance to do it for me.

We could use these load and save methods to store a Settings object with any number of attributes. The only thing we can’t do is have Settings attributes which are themselves objects. The other wrinkle is that if you save a tuple attribute it will be loaded back as an array. But it does work and I rather like it.