Skip to content

Commit 30d94f9

Browse files
authored
Merge pull request #734 from jmmshn/jmmshn/strict
allow `strict="skip"` for `jsanitize`
2 parents 8c13b0f + 8916142 commit 30d94f9

File tree

2 files changed

+59
-9
lines changed

2 files changed

+59
-9
lines changed

src/monty/json.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,8 @@ def jsanitize(
905905
jsanitize will try to get the as_dict() attribute of the object. If
906906
no such attribute is found, an attribute error will be thrown. If
907907
strict is False, jsanitize will simply call str(object) to convert
908-
the object to a string representation.
908+
the object to a string representation. If "skip" is provided,
909+
jsanitize will skip and return the original object without modification.
909910
allow_bson (bool): This parameter sets the behavior when jsanitize
910911
encounters a bson supported type such as objectid and datetime. If
911912
True, such bson types will be ignored, allowing for proper
@@ -1009,7 +1010,7 @@ def jsanitize(
10091010
except AttributeError:
10101011
pass
10111012

1012-
if not strict:
1013+
if strict is False:
10131014
return str(obj)
10141015

10151016
if isinstance(obj, str):
@@ -1024,13 +1025,18 @@ def jsanitize(
10241025
recursive_msonable=recursive_msonable,
10251026
)
10261027

1027-
return jsanitize(
1028-
obj.as_dict(),
1029-
strict=strict,
1030-
allow_bson=allow_bson,
1031-
enum_values=enum_values,
1032-
recursive_msonable=recursive_msonable,
1033-
)
1028+
try:
1029+
return jsanitize(
1030+
obj.as_dict(),
1031+
strict=strict,
1032+
allow_bson=allow_bson,
1033+
enum_values=enum_values,
1034+
recursive_msonable=recursive_msonable,
1035+
)
1036+
except Exception as exc_:
1037+
if strict == "skip":
1038+
return obj
1039+
raise exc_
10341040

10351041

10361042
def _serialize_callable(o):

tests/test_json.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,50 @@ def test_jsanitize(self):
875875
clean = jsanitize(d, strict=True)
876876
assert "@class" in clean["c"]
877877

878+
def test_unserializable_composite(self):
879+
class Unserializable:
880+
def __init__(self, a):
881+
self._a = a
882+
883+
def __str__(self):
884+
return "Unserializable"
885+
886+
class Composite(MSONable):
887+
def __init__(self, name, unserializable, msonable):
888+
self.name = name
889+
self.unserializable = unserializable
890+
self.msonable = msonable
891+
892+
composite_dictionary = {
893+
"name": "test",
894+
"unserializable": Unserializable(1),
895+
"msonable": GoodMSONClass(1, 2, 3),
896+
}
897+
898+
with pytest.raises(AttributeError):
899+
jsanitize(composite_dictionary, strict=True)
900+
901+
composite_obj = Composite.from_dict(composite_dictionary)
902+
903+
with pytest.raises(AttributeError):
904+
jsanitize(composite_obj, strict=True)
905+
906+
# Test that skip mode preserves unserializable objects
907+
skipped_dict = jsanitize(composite_obj, strict="skip", recursive_msonable=True)
908+
assert skipped_dict["name"] == "test", "String values should remain unchanged"
909+
assert (
910+
skipped_dict["unserializable"]._a == 1
911+
), "Unserializable object should be preserved in skip mode"
912+
assert (
913+
skipped_dict["msonable"]["a"] == 1
914+
), "MSONable object should be properly serialized"
915+
916+
# Test non-strict mode converts unserializable to string
917+
dict_with_str = jsanitize(composite_obj, strict=False, recursive_msonable=True)
918+
assert isinstance(
919+
dict_with_str["unserializable"], str
920+
), "Unserializable object should be converted to string in non-strict mode"
921+
878922
@pytest.mark.skipif(pd is None, reason="pandas not present")
879923
def test_jsanitize_pandas(self):
880924
s = pd.Series({"a": [1, 2, 3], "b": [4, 5, 6]})

0 commit comments

Comments
 (0)