0

Lets say I have this class

class LineItem:
   def __init__(self):
        self.var1 = 1

When I encode this json with JSON pickle it will save all the attributes and class definition. And after encoding, lets say I change the class to following.

class LineItem:
   def __init__(self):
        self.var1 = 1
        self.var2 = 2

Now after changing the line item, if I decode the existing json with JSON pickle. The newly decoded class instance of LineItem will not have attribute var2. So if I try to access it, it will throw an error.

In my opinion, there should be an option to reinitialize the class instance.

Mamoon Ahmed
  • 113
  • 10
  • Just call `__init__` explicitly? `obj.__init_()`. – chepner May 10 '23 at 20:29
  • You could give the class a `__setstate__()` method that knows to fill in the missing attribute if it's not present in the unpickled state. – jasonharper May 10 '23 at 20:41
  • I appreciate the valuable comments. IMO the solution you guys suggested is an external solution and kind of a hack. I have already implemented a hack to get around this problem. I was just wondering if there is a solution that is builtin that I maybe missing. – Mamoon Ahmed May 11 '23 at 12:43
  • @MamoonAhmed What do you mean by "external solution"? jsonpickle supports `__setstate__()`: https://jsonpickle.readthedocs.io/en/latest/api.html#customizing-json-output – aaron May 16 '23 at 03:33
  • @aaron I have found one particular issue. existing pickled jsons wont work. By existing, I mean, JSONs that were produced before implementing a __setstate__ and __getstate__ – Mamoon Ahmed Jul 19 '23 at 15:23

1 Answers1

0

Implement the __setstate__() method:

class LineItem:
    def __init__(self):
        self.var1 = 1
        self.var2 = 2

    def __setstate__(self, state: dict):
        state.setdefault('var2', 2)
        self.__dict__.update(state)

existing pickled JSONs won't work. By existing, I mean, JSONs that were produced before implementing a __setstate__ and __getstate__

You can override Unpickler _restore_object_instance_variables to always call __setstate__:

import jsonpickle
from jsonpickle import tags


class Unpickler(jsonpickle.Unpickler):

    def _restore_object_instance_variables(self, obj, instance):
        if hasattr(instance, '__setstate__'):
            obj.setdefault(tags.STATE, {})
        return super()._restore_object_instance_variables(obj, instance)

Usage:

s = '{"py/object": "__main__.LineItem", "var1": 1}'
context = Unpickler()
print(jsonpickle.decode(s, context=context).var2)
aaron
  • 39,695
  • 6
  • 46
  • 102