h5todict string issue
```
Traceback (most recent call last):
  File "inspect_h5master.py", line 6, in <module>
    adict = nxtodict(master, errors="ignore", asarray=False)
  File "/users/denolf/virtualenvs/ewoks/ubuntu_20_04/py38/lib/python3.8/site-packages/silx/io/dictdump.py", line 746, in nxtodict
    nxtreedict = h5todict(h5file, include_attributes=include_attributes, **kw)
  File "/users/denolf/virtualenvs/ewoks/ubuntu_20_04/py38/lib/python3.8/site-packages/silx/io/dictdump.py", line 621, in h5todict
    for aname, avalue in attrs.items():
  File "/users/denolf/virtualenvs/ewoks/ubuntu_20_04/py38/lib/python3.8/site-packages/silx/io/utils.py", line 1170, in items
    yield k, self[k]
  File "/users/denolf/virtualenvs/ewoks/ubuntu_20_04/py38/lib/python3.8/site-packages/silx/io/utils.py", line 1159, in __getitem__
    return h5py_encode_value(value, encoding="utf-8")
  File "/users/denolf/virtualenvs/ewoks/ubuntu_20_04/py38/lib/python3.8/site-packages/silx/io/utils.py", line 1053, in h5py_encode_value
    return value.encode(encoding, errors=errors)
AttributeError: 'numpy.bytes_' object has no attribute 'encode'
```
diff --git a/doc/source/Tutorials/writing_NXdata.rst b/doc/source/Tutorials/writing_NXdata.rst
index 4d87e3d9b..e59eb360a 100644
--- a/doc/source/Tutorials/writing_NXdata.rst
+++ b/doc/source/Tutorials/writing_NXdata.rst
@@ -174,10 +174,7 @@ the *h5py* library.
        import sys
 
        # this is needed for writing arrays of utf-8 strings with h5py
-       if sys.version_info < (3,):
-           text_dtype = h5py.special_dtype(vlen=unicode)
-       else:
-           text_dtype = h5py.special_dtype(vlen=str)
+      text_dtype = h5py.special_dtype(vlen=str)
 
        filename = "./myfile.h5"
        h5f = h5py.File(filename, "w")
diff --git a/src/silx/io/test/test_utils.py b/src/silx/io/test/test_utils.py
index b9fc3ab36..dacfdabf8 100644
--- a/src/silx/io/test/test_utils.py
+++ b/src/silx/io/test/test_utils.py
@@ -725,12 +725,6 @@ class TestH5Strings(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         cls.tempdir = tempfile.mkdtemp()
-        cls.vlenstr = h5py.special_dtype(vlen=str)
-        cls.vlenbytes = h5py.special_dtype(vlen=bytes)
-        try:
-            cls.unicode = unicode
-        except NameError:
-            cls.unicode = str
 
     @classmethod
     def tearDownClass(cls):
@@ -743,68 +737,113 @@ class TestH5Strings(unittest.TestCase):
         self.file.close()
 
     @classmethod
-    def _make_array(cls, value, n):
+    def _make_array(cls, value, n, vlen=True):
         if isinstance(value, bytes):
-            dtype = cls.vlenbytes
-        elif isinstance(value, cls.unicode):
-            dtype = cls.vlenstr
+            if vlen:
+                dtype = h5py.special_dtype(vlen=bytes)
+            else:
+                if hasattr(h5py, "string_dtype"):
+                    dtype = h5py.string_dtype("ascii", len(value))
+                else:
+                    dtype = f"|S{len(value)}"
+        elif isinstance(value, str):
+            if vlen:
+                dtype = h5py.special_dtype(vlen=str)
+            else:
+                value = value.encode("utf-8")
+                if hasattr(h5py, "string_dtype"):
+                    dtype = h5py.string_dtype("utf-8", len(value))
+                else:
+                    dtype = f"|S{len(value)}"
         else:
-            return numpy.array([value] * n)
+            dtype = None
         return numpy.array([value] * n, dtype=dtype)
 
     @classmethod
     def _get_charset(cls, value):
         if isinstance(value, bytes):
             return h5py.h5t.CSET_ASCII
-        elif isinstance(value, cls.unicode):
+        elif isinstance(value, str):
             return h5py.h5t.CSET_UTF8
         else:
             return None
 
     def _check_dataset(self, value, result=None):
-        # Write+read scalar
-        if result:
+        if result is not None:
             decode_ascii = True
         else:
             decode_ascii = False
             result = value
+
+        # Write+read scalar
         charset = self._get_charset(value)
         self.file["data"] = value
         data = utils.h5py_read_dataset(self.file["data"], decode_ascii=decode_ascii)
-        assert type(data) == type(result), data
+        assert isinstance(data, type(result)), data
         assert data == result, data
-        if charset:
+        if charset is not None:
             assert self.file["data"].id.get_type().get_cset() == charset
 
         # Write+read variable length
+        no_unicode_support = isinstance(value, str) and not hasattr(h5py, "string_dtype")
+        if no_unicode_support:
+            decode_ascii = True
         self.file["vlen_data"] = self._make_array(value, 2)
         data = utils.h5py_read_dataset(self.file["vlen_data"], decode_ascii=decode_ascii, index=0)
-        assert type(data) == type(result), data
+        assert isinstance(data, type(result)), data
         assert data == result, data
         data = utils.h5py_read_dataset(self.file["vlen_data"], decode_ascii=decode_ascii)
         numpy.testing.assert_array_equal(data, [result] * 2)
-        if charset:
+        if charset is not None:
             assert self.file["vlen_data"].id.get_type().get_cset() == charset
 
+        # Write+read fixed length
+        self.file["flen_data"] = self._make_array(value, 2, vlen=False)
+        data = utils.h5py_read_dataset(self.file["flen_data"], decode_ascii=decode_ascii, index=0)
+        assert isinstance(data, type(result)), data
+        assert data == result, data
+        data = utils.h5py_read_dataset(self.file["flen_data"], decode_ascii=decode_ascii)
+        numpy.testing.assert_array_equal(data, [result] * 2)
+        if charset is not None and not no_unicode_support:
+            assert self.file["flen_data"].id.get_type().get_cset() == charset
+
     def _check_attribute(self, value, result=None):
-        if result:
+        if result is not None:
             decode_ascii = True
         else:
             decode_ascii = False
             result = value
+
+        # Write+read scalar
         self.file.attrs["data"] = value
         data = utils.h5py_read_attribute(self.file.attrs, "data", decode_ascii=decode_ascii)
-        assert type(data) == type(result), data
+        assert isinstance(data, type(result)), data
         assert data == result, data
 
+        # Write+read variable length
+        no_unicode_support = isinstance(value, str) and not hasattr(h5py, "string_dtype")
+        if no_unicode_support:
+            decode_ascii = True
         self.file.attrs["vlen_data"] = self._make_array(value, 2)
         data = utils.h5py_read_attribute(self.file.attrs, "vlen_data", decode_ascii=decode_ascii)
-        assert type(data[0]) == type(result), data[0]
+        assert isinstance(data[0], type(result)), data[0]
         assert data[0] == result, data[0]
         numpy.testing.assert_array_equal(data, [result] * 2)
 
         data = utils.h5py_read_attributes(self.file.attrs, decode_ascii=decode_ascii)["vlen_data"]
-        assert type(data[0]) == type(result), data[0]
+        assert isinstance(data[0], type(result)), data[0]
+        assert data[0] == result, data[0]
+        numpy.testing.assert_array_equal(data, [result] * 2)
+
+        # Write+read fixed length
+        self.file.attrs["flen_data"] = self._make_array(value, 2, vlen=False)
+        data = utils.h5py_read_attribute(self.file.attrs, "flen_data", decode_ascii=decode_ascii)
+        assert isinstance(data[0], type(result)), data[0]
+        assert data[0] == result, data[0]
+        numpy.testing.assert_array_equal(data, [result] * 2)
+
+        data = utils.h5py_read_attributes(self.file.attrs, decode_ascii=decode_ascii)["flen_data"]
+        assert isinstance(data[0], type(result)), data[0]
         assert data[0] == result, data[0]
         numpy.testing.assert_array_equal(data, [result] * 2)
 
diff --git a/src/silx/io/utils.py b/src/silx/io/utils.py
index 058813825..110284cb6 100644
--- a/src/silx/io/utils.py
+++ b/src/silx/io/utils.py
@@ -1025,6 +1025,25 @@ def vol_to_h5_external_dataset(vol_file, output_url, info_file=None,
                                           overwrite=overwrite)
 
 
+def hdf5_to_python_type(value, decode_ascii, encoding):
+    """Convert HDF5 type to proper python type.
+
+    :param value:
+    :param bool decode_ascii:
+    :param encoding str:
+    """
+    if encoding == "ascii":
+        is_bytes = h5py_value_isinstance(value, bytes)
+        if is_bytes and decode_ascii:
+            return h5py_decode_value(value, encoding="utf-8")
+        if not is_bytes and not decode_ascii:
+            return h5py_encode_value(value, encoding="utf-8")
+    elif encoding == "utf-8":
+        if h5py_value_isinstance(value, bytes):
+            return h5py_decode_value(value, encoding="utf-8")
+    return value
+
+
 def h5py_decode_value(value, encoding="utf-8", errors="surrogateescape"):
     """Keep bytes when value cannot be decoded
 
@@ -1034,8 +1053,8 @@ def h5py_decode_value(value, encoding="utf-8", errors="surrogateescape"):
     """
     try:
         if numpy.isscalar(value):
-            return value.decode(encoding, errors=errors)
-        str_item = [b.decode(encoding, errors=errors) for b in value.flat]
+            return _decode_string(value, encoding, errors)
+        str_item = [_decode_string(b, encoding, errors) for b in value.flat]
         return numpy.array(str_item, dtype=object).reshape(value.shape)
     except UnicodeDecodeError:
         return value
@@ -1050,13 +1069,52 @@ def h5py_encode_value(value, encoding="utf-8", errors="surrogateescape"):
     """
     try:
         if numpy.isscalar(value):
-            return value.encode(encoding, errors=errors)
-        bytes_item = [s.encode(encoding, errors=errors) for s in value.flat]
+            return _encode_string(value, encoding, errors)
+        bytes_item = [_encode_string(s, encoding, errors=errors) for s in value.flat]
         return numpy.array(bytes_item, dtype=object).reshape(value.shape)
     except UnicodeEncodeError:
         return value
 
 
+def h5py_value_isinstance(value, vtype):
+    """Keep string when value cannot be encoding
+
+    :param value: string or array of strings
+    :param vtype:
+    :return bool:
+    """
+    if numpy.isscalar(value):
+        try:
+            value = value.item()
+        except AttributeError:
+            pass
+    else:
+        value = value[0]
+    return isinstance(value, vtype)
+
+
+def _decode_string(string, encoding, errors):
+    """
+    :param value: string
+    :param encoding str:
+    :param errors str:
+    """
+    if isinstance(string, str):
+        return string
+    return string.decode(encoding, errors=errors)
+
+
+def _encode_string(string, encoding, errors):
+    """
+    :param value: string
+    :param encoding str:
+    :param errors str:
+    """
+    if isinstance(string, bytes):
+        return string
+    return string.encode(encoding, errors=errors)
+
+
 class H5pyDatasetReadWrapper:
     """Wrapper to handle H5T_STRING decoding on-the-fly when reading
     a dataset. Uniform behaviour for h5py 2.x and h5py 3.x
@@ -1066,13 +1124,12 @@ class H5pyDatasetReadWrapper:
     Therefore an H5T_STRING with ASCII encoding is not decoded by default.
     """
 
-    H5PY_AUTODECODE_NONASCII = int(h5py.version.version.split(".")[0]) < 3
-
     def __init__(self, dset, decode_ascii=False):
         """
         :param h5py.Dataset dset:
         :param bool decode_ascii:
         """
+        # Get the string encoding (if a string)
         try:
             string_info = h5py.h5t.check_string_dtype(dset.dtype)
         except AttributeError:
@@ -1091,23 +1148,14 @@ class H5pyDatasetReadWrapper:
             except AttributeError:
                 # Not an H5T_STRING
                 encoding = None
-        if encoding == "ascii" and not decode_ascii:
-            encoding = None
-        if encoding != "ascii" and self.H5PY_AUTODECODE_NONASCII:
-            # Decoding is already done by the h5py library
-            encoding = None
-        if encoding == "ascii":
-            # ASCII can be decoded as UTF-8
-            encoding = "utf-8"
+
         self._encoding = encoding
+        self._decode_ascii = decode_ascii
         self._dset = dset
 
     def __getitem__(self, args):
         value = self._dset[args]
-        if self._encoding:
-            return h5py_decode_value(value, encoding=self._encoding)
-        else:
-            return value
+        return hdf5_to_python_type(value, self._decode_ascii, self._encoding)
 
 
 class H5pyAttributesReadWrapper:
@@ -1119,8 +1167,6 @@ class H5pyAttributesReadWrapper:
     Therefore an H5T_STRING with ASCII encoding is not decoded by default.
     """
 
-    H5PY_AUTODECODE = int(h5py.version.version.split(".")[0]) >= 3
-
     def __init__(self, attrs, decode_ascii=False):
         """
         :param h5py.Dataset dset:
@@ -1153,17 +1199,7 @@ class H5pyAttributesReadWrapper:
                 # Not an H5T_STRING
                 return value
 
-        if self.H5PY_AUTODECODE:
-            if encoding == "ascii" and not self._decode_ascii:
-                # Undo decoding by the h5py library
-                return h5py_encode_value(value, encoding="utf-8")
-        else:
-            if encoding == "ascii" and self._decode_ascii:
-                # Decode ASCII as UTF-8 for consistency
-                return h5py_decode_value(value, encoding="utf-8")
-
-        # Decoding is already done by the h5py library
-        return value
+        return hdf5_to_python_type(value, self._decode_ascii, encoding)
 
     def items(self):
         for k in self._attrs.keys():
['src/silx/io/utils.py', 'src/silx/io/test/test_utils.py', 'doc/source/Tutorials/writing_NXdata.rst']