Repository: materialsvirtuallab/monty

Issue:
[Bug]: The `jsanitize` function does not behave as expected with `recursive_msonable=True`
### Email (Optional)

_No response_

### Version

2024.10.21

### Which OS(es) are you using?

- [ ] MacOS
- [X] Windows
- [X] Linux

### What happened?

Currently, `jsanitize(recursive_msonable=True)` does not (as the name might suggest) recursively crawl an MSONable object and make it fully sanitized. If an `MSONable` object is within an attribute of the parent `MSONable` object, the former will not be sanitized.

For instance, try the following:

```python
from monty.json import jsanitize
from pymatgen.core import Structure
from copy import deepcopy
structure = Structure(
   lattice=[[0, 2.13, 2.13], [2.13, 0, 2.13], [2.13, 2.13, 0]],
   species=["Mg", "O"],
   coords=[[0, 0, 0], [0.5, 0.5, 0.5]],
   )
structure.properties = {"test": deepcopy(structure)}
sanitized = jsanitize(structure, recursive_msonable=True)
```
You will see the output is as follows:

```python
{'@module': 'pymatgen.core.structure',
 '@class': 'Structure',
 'charge': 0,
 'lattice': {'matrix': [[0.0, 2.13, 2.13],
   [2.13, 0.0, 2.13],
   [2.13, 2.13, 0.0]],
  'pbc': (True, True, True),
  'a': 3.012274887854692,
  'b': 3.012274887854692,
  'c': 3.012274887854692,
  'alpha': 60.00000000000001,
  'beta': 60.00000000000001,
  'gamma': 60.00000000000001,
  'volume': 19.327193999999995},
 'properties': {'test': Structure Summary
  Lattice
      abc : 3.012274887854692 3.012274887854692 3.012274887854692
   angles : 60.00000000000001 60.00000000000001 60.00000000000001
   volume : 19.327193999999995
        A : 0.0 2.13 2.13
        B : 2.13 0.0 2.13
        C : 2.13 2.13 0.0
      pbc : True True True
  PeriodicSite: Mg (0.0, 0.0, 0.0) [0.0, 0.0, 0.0]
  PeriodicSite: O (2.13, 2.13, 2.13) [0.5, 0.5, 0.5]},
 'sites': [{'species': [{'element': 'Mg', 'occu': 1}],
   'abc': [0.0, 0.0, 0.0],
   'properties': {},
   'label': 'Mg',
   'xyz': [0.0, 0.0, 0.0]},
  {'species': [{'element': 'O', 'occu': 1}],
   'abc': [0.5, 0.5, 0.5],
   'properties': {},
   'label': 'O',
   'xyz': [2.13, 2.13, 2.13]}]}
```

Note how the `properties` key has an actual `Structure` object in it.



### Code snippet

_No response_

### Log output

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct

Discussion:

Patch:
diff --git a/src/monty/json.py b/src/monty/json.py
index d0626d3..7674ccf 100644
--- a/src/monty/json.py
+++ b/src/monty/json.py
@@ -992,7 +992,13 @@ def jsanitize(
 
     if recursive_msonable:
         try:
-            return obj.as_dict()
+            return jsanitize(
+                obj.as_dict(),
+                strict=strict,
+                allow_bson=allow_bson,
+                enum_values=enum_values,
+                recursive_msonable=recursive_msonable,
+            )
         except AttributeError:
             pass
 


Test patch:
diff --git a/tests/test_json.py b/tests/test_json.py
index e59e89d..e91e6ef 100644
--- a/tests/test_json.py
+++ b/tests/test_json.py
@@ -794,6 +794,18 @@ class TestJson:
         assert clean_recursive_msonable["hello"][1] == "test"
         assert clean_recursive_msonable["test"] == "hi"
 
+        DoubleGoodMSONClass = GoodMSONClass(1, 2, 3)
+        DoubleGoodMSONClass.values = [GoodMSONClass(1, 2, 3)]
+        clean_recursive_msonable = jsanitize(
+            DoubleGoodMSONClass, recursive_msonable=True
+        )
+        assert clean_recursive_msonable["a"] == 1
+        assert clean_recursive_msonable["b"] == 2
+        assert clean_recursive_msonable["c"] == 3
+        assert clean_recursive_msonable["values"][0]["a"] == 1
+        assert clean_recursive_msonable["values"][0]["b"] == 2
+        assert clean_recursive_msonable["values"][0]["c"] == 3
+
         d = {"dt": datetime.datetime.now()}
         clean = jsanitize(d)
         assert isinstance(clean["dt"], str)