Repository: bids-standard/pybids

Issue:
All False boolean metadata fields are changed to True
I've noticed that False metadata fields are being changed to True when I use `get_metadata()`. I've noticed this in `0.16.3` and the current master (`0.16.3.post0.dev27`) on Python 3.9.17, but I haven't checked previous versions.

To create the test dataset:

```python
import json
import os

os.makedirs("dset/sub-01/func", exist_ok=True)
with open("dset/sub-01/func/sub-01_task-rest_bold.json", "w") as fo:
    json.dump({"VascularCrushing": False}, fo)  # note that the value is False

with open("dset/sub-01/func/sub-01_task-rest_bold.nii.gz", "w") as fo:
    pass

with open("dset/dataset_description.json", "w") as fo:
    json.dump({"BIDSVersion": "1.8.0", "Name": "Error Dataset"}, fo)
```

To check the value:

```python
from bids import BIDSLayout

layout = BIDSLayout("dset")
files = layout.get(suffix="bold", extension=".nii.gz")
files[0].get_metadata()
```
> {'VascularCrushing': True}


Discussion:
Just to be extra safe, I created a fresh conda environment to test this:

```
conda create -n pybids python=3.9 pip
conda activate pybids
pip install pybids
```

I still got the same error.
I also tried installing pybids versions back to 0.14.2 in the same original environment and got the same issue. I have to assume it's a problem with a dependency, but I don't know anything about sqlalchemy, so I'm having trouble tracing the issue back to its source.

Resolving patch:
diff --git a/bids/layout/models.py b/bids/layout/models.py
--- a/bids/layout/models.py
+++ b/bids/layout/models.py
@@ -698,6 +698,9 @@ def _init_on_load(self):
         if self._dtype == 'json':
             self.value = json.loads(self._value)
             self.dtype = 'json'
+        elif self._dtype == 'bool':
+            self.value = self._value == 'True'
+            self.dtype = bool
         else:
             self.dtype = type_map[self._dtype]
             self.value = self.dtype(self._value)


Test patch:
diff --git a/bids/layout/tests/test_layout.py b/bids/layout/tests/test_layout.py
--- a/bids/layout/tests/test_layout.py
+++ b/bids/layout/tests/test_layout.py
@@ -210,6 +210,7 @@ def test_derivative_datasets_load_with_no_validation(self, dataset_path):
         assert len(layout.get()) == 4
         assert len(layout.get(desc="preproc")) == 3
 
+
 def test_get_metadata(layout_7t_trt):
     target = 'sub-03/ses-2/func/sub-03_ses-2_task-' \
              'rest_acq-fullbrain_run-2_bold.nii.gz'
@@ -244,6 +245,21 @@ def test_get_metadata4(layout_ds005):
     assert result == {}
 
 
+def test_get_metadata_error2(layout_7t_trt):
+    """Ensure that False boolean fields are retained as False."""
+    target = 'sub-03/ses-2/func/sub-03_ses-2_task-' \
+             'rest_acq-fullbrain_run-2_bold.nii.gz'
+    target = target.split('/')
+    # Check get_metadata from the BIDSLayout
+    result = layout_7t_trt.get_metadata(join(layout_7t_trt.root, *target))
+    assert result['MTState'] is False
+
+    # Check get_metadata from the BIDSFile
+    files = layout_7t_trt.get(task="rest", acquisition="fullbrain", suffix="bold", extension=".nii.gz")
+    result = files[0].get_metadata()
+    assert result['MTState'] is False
+
+
 def test_get_metadata_meg(layout_ds117):
     funcs = ['get_subjects', 'get_sessions', 'get_tasks', 'get_runs',
              'get_acquisitions', 'get_procs']
@@ -278,6 +294,32 @@ def test_get_metadata_via_bidsfile(layout_7t_trt):
     assert 'subject' not in result
 
 
+def test_metadata_equivalence(layout_7t_trt):
+    """Ensure that JSON metadata is not corrupted by the layout."""
+    bold = layout_7t_trt.get(
+        subject='02', acquisition='fullbrain', suffix='bold', extension='.nii.gz'
+    )[0]
+    root_metadata_file = layout_7t_trt.get(
+        subject=None, acquisition='fullbrain', suffix='bold', extension='.json'
+    )[0]
+
+    assert bold.get_metadata() == root_metadata_file.get_dict()
+
+    physio = layout_7t_trt.get(
+        subject='02', acquisition='fullbrain', suffix='physio', extension='.tsv.gz'
+    )[0]
+    root_metadata_file = layout_7t_trt.get(
+        subject=None, acquisition='fullbrain', suffix='physio', extension='.json'
+    )[0]
+
+    assert physio.get_metadata() == root_metadata_file.get_dict()
+
+    # Verify that we cover all common metadata types
+    types = {type(val) for val in bold.get_metadata().values()}
+    assert types == {str, float, bool, list}
+    assert isinstance(physio.get_metadata()['StartTime'], int)
+
+
 def test_get_metadata_error(layout_7t_trt):
     ''' Same as test_get_metadata5, but called through BIDSFile. '''
     target = 'sub-01/ses-1/func/sub-01_ses-1_task-rest_acq-fullbrain_run-1_bold.nii.gz'
diff --git a/bids/tests/data/7t_trt/task-rest_acq-fullbrain_bold.json b/bids/tests/data/7t_trt/task-rest_acq-fullbrain_bold.json
--- a/bids/tests/data/7t_trt/task-rest_acq-fullbrain_bold.json
+++ b/bids/tests/data/7t_trt/task-rest_acq-fullbrain_bold.json
@@ -2,6 +2,7 @@
     "CogAtlasID": "trm_4c8a834779883",
     "EchoTime": 0.017,
     "EffectiveEchoSpacing": 0.0003333262223739227,
+    "MTState": false,
     "PhaseEncodingDirection": "j-",
     "RepetitionTime": 3.0,
     "SliceEncodingDirection": "k",