diff --git a/box/box_list.py b/box/box_list.py index 72609ff..819c205 100644 --- a/box/box_list.py +++ b/box/box_list.py @@ -106,9 +106,15 @@ def __setitem__(self, key, value): return super().__setitem__(pos, value) children = key[len(list_pos.group()) :].lstrip(".") if self.box_options.get("default_box"): + # Only replace the element at ``pos`` with a fresh container when it is + # not already the right one; otherwise a second dotted write into an + # existing element (e.g. ``a[0].y`` after ``a[0].x``) would discard the + # data already stored there. Mirrors the guard in Box.__setitem__. + current = super().__getitem__(pos) if children[0] == "[": - super().__setitem__(pos, box.BoxList(**self.box_options)) - else: + if not isinstance(current, box.BoxList): + super().__setitem__(pos, box.BoxList(**self.box_options)) + elif not isinstance(current, box.Box): super().__setitem__(pos, self.box_options.get("box_class")(**self.box_options)) return super().__getitem__(pos).__setitem__(children, value) super().__setitem__(key, value) diff --git a/test/test_box_list.py b/test/test_box_list.py index 9cd90e0..0fd7de8 100644 --- a/test/test_box_list.py +++ b/test/test_box_list.py @@ -241,6 +241,24 @@ def test_box_list_default_dots(self): box_3["a.b[0]"] = 42 assert box_3.a.b[0] == 42 + # A second dotted write into an existing list element must not discard + # the keys already written there (regression). + box_4 = Box(default_box=True, box_dots=True) + box_4["a[0].x"] = 1 + box_4["a[0].y"] = 2 + assert box_4.a[0].to_dict() == {"x": 1, "y": 2} + + box_5 = Box(default_box=True, box_dots=True) + box_5["a[0][0].x"] = 1 + box_5["a[0][1].y"] = 2 + assert box_5.a[0].to_list() == [{"x": 1}, {"y": 2}] + + # A scalar already at the position is still overwritten. + box_6 = Box(default_box=True, box_dots=True) + box_6["a[0]"] = 5 + box_6["a[0].x"] = 1 + assert box_6.a[0].to_dict() == {"x": 1} + def test_box_config_propagate(self): structure = Box(a=[Box(default_box=False)], default_box=True, box_inherent_settings=True) assert structure._box_config["default_box"] is True