Repository: nipy/heudiconv

Issue:
enhance explicitness about what json files heudiconv fails to read
<!-- DO NOT DELETE THIS! 
This template is used to facilitate issue resolution.
All text in <!-> tags will not be displayed.
-->

### Summary
I was helping a user troubleshoot a problem with heudiconv, as it was tripping up on an invalid json file, but the user only thought heudiconv was touching files in the subject directory inside the bids directory, and not looking in other places inside the bids directory (which it does).

A clue that could have gotten this user on track faster would be to print which file was the invalid json file.

To help, the `load_json` function could be changed to something like:

```
try:
    with open(filename, 'r') as fp:
            data = json.load(fp)
except json.JSONDecodeError:
    print("{fname} is not a valid json file".format(fname=filename)
    raise
```
Thoughts on this?
If positive I can open a pull request.
<!-- If you are having conversion troubles, please share as much
relevant information as possible. This includes, but is not limited to:
- log of conversion
- heuristic
-->



### Platform details:

Choose one:
- [ ] Local environment
<!-- If selected, please provide OS and python version -->
- [x] Container
<!-- If selected, please provide container name and tag"-->

- Heudiconv version: 0.5.4
<!-- To check: run heudiconv with just the --version flag -->



Discussion:

Patch:
diff --git a/heudiconv/utils.py b/heudiconv/utils.py
index abea820..2453e9d 100644
--- a/heudiconv/utils.py
+++ b/heudiconv/utils.py
@@ -19,6 +19,11 @@ from nipype.utils.filemanip import which
 import logging
 lgr = logging.getLogger(__name__)
 
+if sys.version_info[0] > 2:
+    from json.decoder import JSONDecodeError
+else:
+    JSONDecodeError = ValueError
+
 
 seqinfo_fields = [
     'total_files_till_now',  # 0
@@ -172,10 +177,15 @@ def load_json(filename):
     -------
     data : dict
     """
-    with open(filename, 'r') as fp:
-        data = json.load(fp)
-    return data
+    try:
+        with open(filename, 'r') as fp:
+            data = json.load(fp)
+    except JSONDecodeError:
+        lgr.error("{fname} is not a valid json file".format(fname=filename))
+        raise
 
+    return data
+    
 
 def assure_no_file_exists(path):
     """Check if file or symlink (git-annex?) exists, and if so -- remove"""


Test patch:
diff --git a/heudiconv/tests/test_utils.py b/heudiconv/tests/test_utils.py
index 47faec4..ad84673 100644
--- a/heudiconv/tests/test_utils.py
+++ b/heudiconv/tests/test_utils.py
@@ -1,10 +1,16 @@
+import json
 import os
 import os.path as op
+
 from heudiconv.utils import (
     get_known_heuristics_with_descriptions,
     get_heuristic_description,
     load_heuristic,
-    json_dumps_pretty)
+    json_dumps_pretty,
+    load_json,
+    create_tree,
+    save_json,
+    JSONDecodeError)
 
 import pytest
 from .utils import HEURISTICS_PATH
@@ -58,4 +64,24 @@ def test_json_dumps_pretty():
           'Mar  3 2017 10:46:13 by eja'
     # just the date which reveals the issue
     # tstr = 'Mar  3 2017 10:46:13 by eja'
-    assert pretty({'WipMemBlock': tstr}) == '{\n  "WipMemBlock": "%s"\n}' % tstr
\ No newline at end of file
+    assert pretty({'WipMemBlock': tstr}) == '{\n  "WipMemBlock": "%s"\n}' % tstr
+
+
+def test_load_json(tmp_path, caplog):
+    # test invalid json
+    ifname = 'invalid.json'
+    invalid_json_file = str(tmp_path / ifname)
+    create_tree(str(tmp_path), {ifname: u"I'm Jason Bourne"})
+
+    with pytest.raises(JSONDecodeError):
+        load_json(str(invalid_json_file))
+
+    assert ifname in caplog.text
+
+    # test valid json
+    vcontent = {"secret": "spy"}
+    vfname = "valid.json"
+    valid_json_file = str(tmp_path / vfname)
+    save_json(valid_json_file, vcontent)
+    
+    assert load_json(valid_json_file) == vcontent