"""
Read a file that was created by :class:`~msl.io.writers.json_.JSONWriter`.
"""
import codecs
import json
from io import BufferedIOBase
import numpy as np
from ..base import Reader
from ..constants import IS_PYTHON2
from ..utils import register
[docs]@register
class JSONReader(Reader):
"""Read a file that was created by :class:`~msl.io.writers.json_.JSONWriter`."""
[docs] @staticmethod
def can_read(file, **kwargs):
"""Checks if the text ``MSL JSONWriter`` is in the first line of the file."""
if isinstance(file, BufferedIOBase):
text = Reader.get_bytes(file, 21, 34).decode()
else:
text = Reader.get_lines(file, 1, **kwargs)[0][20:34]
return text == 'MSL JSONWriter'
[docs] def read(self, **kwargs):
"""Read the file that was created by :class:`~msl.io.writers.json_.JSONWriter`
If a :class:`~msl.io.metadata.Metadata` `key` has a `value` that is a
:class:`list` then the list is converted to an :class:`~numpy.ndarray`
with :class:`~numpy.dtype` = :class:`object`
Parameters
----------
**kwargs
Accepts `encoding` and `errors` keyword arguments which are passed to
:func:`open`. The default `encoding` value is ``'utf-8'`` and the default
`errors` value is ``'strict'``. All additional keyword arguments are passed to
`json.loads <https://docs.python.org/3/library/json.html#json.loads>`_.
"""
open_kwargs = {
'encoding': kwargs.get('encoding', 'utf-8'),
'errors': kwargs.pop('errors', 'strict'),
}
if IS_PYTHON2:
opener = codecs.open # this allows the encoding and errors kwargs to be used
else:
opener = open
if hasattr(self.file, 'read'):
self.file.readline() # skip the first line
data = self.file.read()
if isinstance(data, bytes):
data = data.decode(**open_kwargs)
dict_ = json.loads(data, **kwargs)
else:
with opener(self.file, mode='r', **open_kwargs) as fp:
fp.readline() # skip the first line
dict_ = json.loads(fp.read(), **kwargs)
def list_to_ndarray(list_):
# convert a Metadata value to ndarray because one can easily make ndarray read only
# use dtype=object because it guarantees that the data types are preserved
# for example,
# >>> a = np.asarray([True, -5, 0.002345, 'something', 49.1871524])
# >>> a
# array(['True', '-5', '0.002345', 'something', '49.1871524'], dtype='<U32')
# would cast every element to a string
# also a regular Python list stores items as objects
return np.asarray(list_, dtype=object)
def create_group(parent, name, vertex):
group = self if parent is None else parent.create_group(name)
for key, value in vertex.items():
if not isinstance(value, dict): # Metadata
if isinstance(value, list):
value = list_to_ndarray(value)
group.metadata[key] = value
elif 'dtype' in value and 'data' in value: # Dataset
kws = dict()
for dkey, dval in value.items():
if dkey == 'data':
pass # handled in the 'dtype' check
elif dkey == 'dtype':
if isinstance(dval, list):
kws['data'] = np.asarray(
[tuple(row) for row in value['data']],
dtype=[tuple(item) for item in dval])
else:
kws['data'] = np.asarray(value['data'], dtype=dval)
else: # Metadata
if isinstance(dval, list):
dval = list_to_ndarray(dval)
kws[dkey] = dval
group.create_dataset(key, **kws)
else: # use recursion to create a sub-Group
create_group(group, key, value)
# create the root group
create_group(None, '', dict_)