Skip to content

Commit

Permalink
[FIX] Add function to prep data for JSON serialization (#859)
Browse files Browse the repository at this point in the history
* Adds attempt at fix

* Addres @handwerkerd review

* Trailing whitespace fix
  • Loading branch information
Joshua Teves authored May 4, 2022
1 parent 50f21ab commit d4406e4
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 1 deletion.
46 changes: 45 additions & 1 deletion tedana/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import logging
import os
import os.path as op
from copy import deepcopy
from string import Formatter

import nibabel as nib
Expand Down Expand Up @@ -198,7 +199,8 @@ def save_file(self, data, description, **kwargs):
if description.endswith("img"):
self.save_img(data, name)
elif description.endswith("json"):
self.save_json(data, name)
prepped = prep_data_for_json(data)
self.save_json(prepped, name)
elif description.endswith("tsv"):
self.save_tsv(data, name)

Expand Down Expand Up @@ -680,3 +682,45 @@ def split_ts(data, mmix, mask, comptable):
resid = data - hikts

return hikts, resid


def prep_data_for_json(d) -> dict:
"""Attempts to create a JSON serializable dictionary from a data dictionary
Parameters
----------
d: dict
A dictionary that will be converted into something JSON serializable
Raises
------
ValueError if it cannot force the dictionary to be serializable
TypeError if you do not supply a dict
Returns
-------
An attempt at JSON serializable data
Notes
-----
Use this to make something serializable when writing JSON to disk.
To speed things up since there are a small number of conversions, this
function does not check for serializability, but does use conversion
rules for cases encountered where things were not serializable.
Add more conversion rules to this function in cases where a
tedana-generated object does not serialize to JSON.
"""
if not isinstance(d, dict):
raise TypeError(f"Dictionary required to force serialization; got type {type(d)} instead.")
# The input dictionary may want to retain types, so we copy
d = deepcopy(d)
for k, v in d.items():
if isinstance(v, dict):
# One of the values in the dict is the problem, need to recurse
v = prep_data_for_json(v)
elif isinstance(v, np.ndarray):
v = v.tolist()
# NOTE: add more special cases for type conversions above this
# comment line as an elif block
d[k] = v
return d
29 changes: 29 additions & 0 deletions tedana/tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,32 @@ def test_smoke_load_data():


# TODO: "BREAK" AND UNIT TESTS


def test_prep_data_for_json():
"""
Tests for prep_data_for_json
"""
# Should reject non-dict entities since that is required for saver
with pytest.raises(TypeError):
me.prep_data_for_json(1)

# Should not modify something with no special types
d = {"mustang": "vroom"}
new_d = me.prep_data_for_json(d)
assert new_d == d

# Should coerce an ndarray into a list
d = {"number": np.ndarray(1)}
new_d = me.prep_data_for_json(d)
assert isinstance(new_d["number"], list)

# Should work for nested dict
d = {
"dictionary": {
"serializable": "cat",
"array": np.ndarray([1, 2, 3]),
}
}
new_d = me.prep_data_for_json(d)
assert isinstance(new_d["dictionary"]["array"], list)

0 comments on commit d4406e4

Please sign in to comment.