From 4147343d754205902ce46164ced49e5d14b34d80 Mon Sep 17 00:00:00 2001
From: Michiel Cottaar <MichielCottaar@protonmail.com>
Date: Fri, 27 Mar 2020 13:26:15 +0000
Subject: [PATCH] ENH: I/O to of filetree to JSON files

Added test for I/O to both JSON and pickle files
---
 fsl/utils/filetree/filetree.py   | 44 ++++++++++++++++++++++++++++++--
 tests/test_filetree/test_read.py | 24 +++++++++++++++++
 2 files changed, 66 insertions(+), 2 deletions(-)

diff --git a/fsl/utils/filetree/filetree.py b/fsl/utils/filetree/filetree.py
index 3159fefa8..b1dad5f1b 100644
--- a/fsl/utils/filetree/filetree.py
+++ b/fsl/utils/filetree/filetree.py
@@ -3,9 +3,9 @@ from typing import Tuple, Optional, Dict, Any, Set
 from copy import deepcopy
 from . import parse
 import pickle
+import json
 import os.path as op
 from . import utils
-from fsl.utils.deprecated import deprecated
 
 
 class MissingVariable(KeyError):
@@ -255,12 +255,28 @@ class FileTree(object):
         with open(filename, 'wb') as f:
             pickle.dump(self, f)
 
+    def save_json(self, filename):
+        """
+        Saves the Filetree to a JSON file
+
+        :param filename: filename to store the file tree in
+        """
+        def default(obj):
+            if isinstance(obj, FileTree):
+                res = dict(obj.__dict__)
+                del res['_parent']
+                return res
+            return obj
+
+        with open(filename, 'w') as f:
+            json.dump(self, f, default=default)
+
     @classmethod
     def load_pickle(cls, filename):
         """
         Loads the Filetree from a pickle file
 
-        :param filename: filename produced from Filetree.save
+        :param filename: filename produced from Filetree.save_pickle
         :return: stored Filetree
         """
         with open(filename, 'rb') as f:
@@ -269,6 +285,30 @@ class FileTree(object):
             raise IOError("Pickle file did not contain %s object" % cls)
         return res
 
+    @classmethod
+    def load_json(cls, filename):
+        """
+        Loads the FileTree from a JSON file
+
+        :param filename: filename produced by FileTree.save_json
+        :return: stored FileTree
+        """
+        def from_dict(input_dict):
+            res_tree = FileTree(
+                templates=input_dict['templates'],
+                variables=input_dict['variables'],
+                sub_trees={name: from_dict(value) for name, value in input_dict['sub_trees'].items()},
+                name=input_dict['_name'],
+            )
+            for sub_tree in res_tree.sub_trees.values():
+                sub_tree._parent = res_tree
+            return res_tree
+
+        with open(filename, 'r') as f:
+            as_dict = json.load(f)
+        return from_dict(as_dict)
+
+
     def defines(self, short_names, error=False):
         """
         Checks whether templates are defined for all the `short_names`
diff --git a/tests/test_filetree/test_read.py b/tests/test_filetree/test_read.py
index 6627969e3..8382cc6d2 100644
--- a/tests/test_filetree/test_read.py
+++ b/tests/test_filetree/test_read.py
@@ -1,5 +1,6 @@
 # Sample Test passing with nose and pytest
 from fsl.utils import filetree
+from fsl.utils.tempdir import tempdir
 from pathlib import PurePath
 import os.path as op
 import pytest
@@ -180,3 +181,26 @@ def test_read_local_sub_children():
         # ensure current directory is not the test directory, which would cause the test to be too easy
         os.chdir('..')
     filetree.FileTree.read(op.join(directory, 'local_parent.tree'))
+
+
+def same_tree(t1, t2):
+    assert t1.all_variables == t2.all_variables
+    assert t1.templates == t2.templates
+    assert len(t1.sub_trees) == len(t2.sub_trees)
+    for name in t1.sub_trees:
+        same_tree(t1.sub_trees[name], t2.sub_trees[name])
+        assert t1.sub_trees[name].parent is t1
+        assert t2.sub_trees[name].parent is t2
+
+
+def test_io():
+    directory = op.split(__file__)[0]
+    tree = filetree.FileTree.read(op.join(directory, 'parent.tree'), partial_fill=True)
+    with tempdir():
+        tree.save_pickle('test.pck')
+        new_tree = filetree.FileTree.load_pickle('test.pck')
+        same_tree(tree, new_tree)
+
+        tree.save_json('test.json')
+        new_tree = filetree.FileTree.load_json('test.json')
+        same_tree(tree, new_tree)
-- 
GitLab