diff --git a/README.rst b/README.rst index bc16470..5667dc5 100644 --- a/README.rst +++ b/README.rst @@ -165,7 +165,15 @@ Documentation :param dates_to_str: Boolean, convert all date or datetime values to string. :returns: dictionary object - + JSONObject.update([Dict|List|Tuple]) accepts either a dictionary object or an iterable of key/value + pairs (as tuples or other iterables of length two). If keyword arguments are specified, the dictionary + is then updated with those key/value pairs: obj.update(sky=1, cloud=2). + + Plus Operator: Two JSONObjects may be merged using the plus (+) operator: obj = obj + other_obj. + + Number of Properties: The number of managed properties may be determined by using the Python 'len()' + function: len(obj) == 5. + Project Links ============= diff --git a/setup.py b/setup.py index 2aae694..2175a13 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import setup, find_packages -__VERSION__ = "1.1.9" +__VERSION__ = "1.1.10" base_dir = os.path.abspath(os.path.dirname(__file__)) diff --git a/src/python_easy_json/json_object.py b/src/python_easy_json/json_object.py index 6cedb87..dc10b54 100644 --- a/src/python_easy_json/json_object.py +++ b/src/python_easy_json/json_object.py @@ -267,3 +267,34 @@ def to_dict(self, recursive: bool = True, dates_to_str: bool = False): def __repr__(self): return self.to_json() + + def __len__(self): + return len(self.__data_dict__.keys()) + + def __add__(self, other): + if not isinstance(other, JSONObject): + raise TypeError(f"Invalid operand type for +: 'JSONObject' and '{str(other)}'") + return self.update(**other.to_dict()) + + def update(self, *args, **kwargs) -> "JSONObject": + """ + Update or add additional properties to this object by passing a dictionary or list of key value pairs. + """ + if args: + for arg in args: + if isinstance(arg, dict): + for k, v in arg.items(): + setattr(self, k, v) + elif isinstance(arg, (list, tuple)): + for item in arg: + if len(item) != 2: + raise ValueError('Invalid tuple size') + setattr(self, item[0], item[1]) + else: + raise TypeError(f"TypeError: '{type(arg)}' object is not iterable") + + elif kwargs: + for k, v in kwargs.items(): + setattr(self, k, v) + + return self diff --git a/tests/test_simple_json.py b/tests/test_simple_json.py index 0be77c2..6fdb6bb 100644 --- a/tests/test_simple_json.py +++ b/tests/test_simple_json.py @@ -109,3 +109,104 @@ def test_setattr_after_init(self): data = obj.to_dict() self.assertEqual(data['new_prop'], 'abc') self.assertEqual(data['test_prop'], 123) + + def test_update_with_dict(self): + """ Test we can update the JSONObject by passing a dict object """ + obj = JSONObject({'test_prop': 123}) + self.assertIsInstance(obj, JSONObject) + + obj = obj.update({'test_prop': 456, 'new_prop': 987}) + + self.assertEqual(obj.new_prop, 987) + self.assertEqual(obj.test_prop, 456) + + data = obj.to_dict() + + self.assertEqual(data['new_prop'], 987) + self.assertEqual(data['test_prop'], 456) + + def test_update_with_keyword_args(self): + """ Test we can update the JSONObject by passing key word arguments """ + obj = JSONObject({'test_prop': 123}) + self.assertIsInstance(obj, JSONObject) + + obj = obj.update(test_prop=456, new_prop=987) + + self.assertEqual(obj.new_prop, 987) + self.assertEqual(obj.test_prop, 456) + + data = obj.to_dict() + + self.assertEqual(data['new_prop'], 987) + self.assertEqual(data['test_prop'], 456) + + def test_update_with_iterable_pairs(self): + """ Test we can update the JSONObject by passing iterable pair arguments """ + obj = JSONObject({'test_prop': 123}) + self.assertIsInstance(obj, JSONObject) + + obj = obj.update([('test_prop', 456), ('new_prop', 987)]) + + self.assertEqual(obj.new_prop, 987) + self.assertEqual(obj.test_prop, 456) + + data = obj.to_dict() + + self.assertEqual(data['new_prop'], 987) + self.assertEqual(data['test_prop'], 456) + + def test_update_exceptions(self): + """ Test update using bad data to cause exceptions """ + + obj = JSONObject({'test_prop': 123}) + self.assertIsInstance(obj, JSONObject) + + # Test no args, should not raise an error. + self.assertIsInstance(obj.update({}), JSONObject) + + # Test non-iterable value raises TypeError + self.assertRaises(TypeError, obj.update, None) + self.assertRaises(TypeError, obj.update, 123) + + # Test bad iterable pair raise ValueError + self.assertRaises(ValueError, obj.update, [('test_prop', 456, 333)]) + + def test_number_of_properties(self): + """ Test the number of properties by calling len() function """ + + obj = JSONObject({'test_prop': 123, 'another_prop': 'abc'}) + self.assertIsInstance(obj, JSONObject) + + self.assertEqual(len(obj), 2) + + def test_add_object(self): + """ Test add object """ + obj = JSONObject({'test_prop': 123}) + self.assertIsInstance(obj, JSONObject) + + other_obj = JSONObject({'another_prop': 'abc'}) + self.assertIsInstance(other_obj, JSONObject) + + island_other_obj = JSONObject({'island_prop': 'sandy'}) + self.assertIsInstance(island_other_obj, JSONObject) + + obj += other_obj + + self.assertEqual(obj.test_prop, 123) + self.assertEqual(obj.another_prop, 'abc') + + obj = obj + island_other_obj + + self.assertEqual(obj.island_prop, 'sandy') + + def test_add_invalid_operand(self): + """ Test adding an invalid operand """ + obj = JSONObject({'test_prop': 123}) + self.assertIsInstance(obj, JSONObject) + + try: + obj += {'other_prop': 'abc'} + except TypeError: + pass + + self.assertFalse(hasattr(obj, 'other_prop'))