Skip to content

Commit 139dfed

Browse files
pablosperychoo2
authored andcommitted
Fix JSON serialization of DeletionMark objects in Change.to_dict()
The Change.to_dict() method was returning raw DeletionMark objects instead of calling their to_dict() method, causing 'TypeError: Object of type DeletionMark is not JSON serializable' when serializing changes. This fix ensures DeletionMark objects are properly converted to dictionaries before serialization, consistent with how other nested objects like Region and StylesInfo are handled. Changes: - Fix Change.to_dict() to call self.__deletionMark.to_dict() when present - Handle None and missing deletionMark cases properly - Add comprehensive test suite covering all serialization scenarios Fixes: JSON serialization errors when changes contain deletion marks
1 parent 3250e3e commit 139dfed

File tree

2 files changed

+172
-1
lines changed

2 files changed

+172
-1
lines changed

draftable/endpoints/comparisons/changes.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,9 @@ def to_dict(self):
325325
"stylesInfo": (
326326
self.__stylesInfo.to_dict() if self.__stylesInfo else None
327327
),
328-
"deletionMark": self.__deletionMark,
328+
"deletionMark": (
329+
self.__deletionMark.to_dict() if self.__deletionMark else None
330+
),
329331
}
330332

331333
def __repr__(self):
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import json
2+
from .changes import Change, DeletionMark, ChangeDetails
3+
4+
5+
class TestDeletionMarkSerialization:
6+
"""Test that DeletionMark objects are properly serialized in Change.to_dict()"""
7+
8+
def test_deletion_mark_serialization(self):
9+
"""Test that DeletionMark objects are properly serialized in Change.to_dict()"""
10+
11+
# Create test data with a DeletionMark
12+
change_data = {
13+
"kind": "deletion",
14+
"leftText": "Some deleted text",
15+
"rightText": None,
16+
"leftRegion": None,
17+
"rightRegion": None,
18+
"stylesInfo": None,
19+
"deletionMark": {"pageIndex": 1, "point": [100, 200]},
20+
}
21+
22+
# Create Change object
23+
change = Change(change_data)
24+
25+
# Convert to dict (this should not raise a TypeError)
26+
result_dict = change.to_dict()
27+
28+
# Verify the result can be JSON serialized
29+
json_str = json.dumps(result_dict)
30+
31+
# Verify the structure is correct
32+
assert result_dict["deletionMark"]["pageIndex"] == 1
33+
assert result_dict["deletionMark"]["point"] == [100, 200]
34+
35+
# Verify we can round-trip through JSON
36+
parsed_back = json.loads(json_str)
37+
assert parsed_back["deletionMark"]["pageIndex"] == 1
38+
assert parsed_back["deletionMark"]["point"] == [100, 200]
39+
40+
def test_deletion_mark_none(self):
41+
"""Test that None deletionMark is handled correctly"""
42+
43+
change_data = {
44+
"kind": "insertion",
45+
"leftText": None,
46+
"rightText": "Some new text",
47+
"leftRegion": None,
48+
"rightRegion": None,
49+
"stylesInfo": None,
50+
"deletionMark": None,
51+
}
52+
53+
change = Change(change_data)
54+
result_dict = change.to_dict()
55+
56+
# Should be serializable and deletionMark should be None
57+
json.dumps(result_dict)
58+
assert result_dict["deletionMark"] is None
59+
60+
def test_deletion_mark_missing(self):
61+
"""Test that missing deletionMark is handled correctly"""
62+
63+
change_data = {
64+
"kind": "modification",
65+
"leftText": "Old text",
66+
"rightText": "New text",
67+
"leftRegion": None,
68+
"rightRegion": None,
69+
"stylesInfo": None,
70+
# deletionMark is missing entirely
71+
}
72+
73+
change = Change(change_data)
74+
result_dict = change.to_dict()
75+
76+
# Should be serializable and deletionMark should be None
77+
json.dumps(result_dict)
78+
assert result_dict["deletionMark"] is None
79+
80+
def test_change_details_with_deletion_marks(self):
81+
"""Test that Changes with DeletionMarks can be serialized"""
82+
83+
change_details_data = {
84+
"changes": [
85+
{
86+
"kind": "deletion",
87+
"leftText": "Deleted text",
88+
"rightText": None,
89+
"deletionMark": {"pageIndex": 0, "point": [50, 100]},
90+
},
91+
{
92+
"kind": "insertion",
93+
"leftText": None,
94+
"rightText": "Added text",
95+
"deletionMark": None,
96+
},
97+
],
98+
"summary": {"anyChanges": True},
99+
}
100+
101+
# Create ChangeDetails object
102+
change_details = ChangeDetails(change_details_data)
103+
104+
# Convert to dict (this should not raise a TypeError)
105+
result_dict = change_details.to_dict()
106+
107+
# Verify the result can be JSON serialized
108+
json.dumps(result_dict)
109+
110+
# Verify structure is correct
111+
assert len(result_dict["changes"]) == 2
112+
assert result_dict["changes"][0]["deletionMark"]["pageIndex"] == 0
113+
assert result_dict["changes"][0]["deletionMark"]["point"] == [50, 100]
114+
assert result_dict["changes"][1]["deletionMark"] is None
115+
116+
def test_deletion_mark_direct_serialization(self):
117+
"""Test that DeletionMark objects can be directly serialized"""
118+
119+
deletion_mark_data = {"pageIndex": 2, "point": [150, 250]}
120+
121+
deletion_mark = DeletionMark(deletion_mark_data)
122+
123+
# Test to_dict method
124+
result_dict = deletion_mark.to_dict()
125+
assert result_dict["pageIndex"] == 2
126+
assert result_dict["point"] == [150, 250]
127+
128+
# Test JSON serialization
129+
json_str = json.dumps(result_dict)
130+
parsed_back = json.loads(json_str)
131+
assert parsed_back["pageIndex"] == 2
132+
assert parsed_back["point"] == [150, 250]
133+
134+
def test_change_with_complex_nested_data(self):
135+
"""Test serialization of Change with multiple nested objects including DeletionMark"""
136+
137+
change_data = {
138+
"kind": "replacement",
139+
"leftText": "Old text",
140+
"rightText": "New text",
141+
"leftRegion": {
142+
"pageIndex": 0,
143+
"rectangles": [{"left": 10, "top": 20, "right": 100, "bottom": 50}],
144+
},
145+
"rightRegion": {
146+
"pageIndex": 0,
147+
"rectangles": [{"left": 10, "top": 20, "right": 120, "bottom": 50}],
148+
},
149+
"stylesInfo": {
150+
"leftStyles": [{"color": "red", "font": "Arial", "size": 12}],
151+
"rightStyles": [{"color": "blue", "font": "Arial", "size": 12}],
152+
"leftStyleMap": "style1",
153+
"rightStyleMap": "style2",
154+
},
155+
"deletionMark": {"pageIndex": 0, "point": [75, 35]},
156+
}
157+
158+
change = Change(change_data)
159+
result_dict = change.to_dict()
160+
161+
# Should be fully serializable
162+
json.dumps(result_dict)
163+
164+
# Verify all nested structures are properly serialized
165+
assert result_dict["deletionMark"]["pageIndex"] == 0
166+
assert result_dict["deletionMark"]["point"] == [75, 35]
167+
assert result_dict["leftRegion"]["pageIndex"] == 0
168+
assert len(result_dict["leftRegion"]["rectangles"]) == 1
169+
assert result_dict["stylesInfo"]["leftStyleMap"] == "style1"

0 commit comments

Comments
 (0)