plugin_json.py

#
#

Copyright 2014-2019 Álvaro Justen https://github.com/turicas/rows/

#

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

#

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

#

You should have received a copy of the GNU Lesser General Public License along with this program. If not, see http://www.gnu.org/licenses/.

from __future__ import unicode_literals

import json
from io import BytesIO

import six

from rows import fields
from rows.plugins.utils import (
    create_table,
    prepare_to_export,
)
from rows.utils import Source
#

Import a JSON file or file-like object into a rows.Table.

def import_from_json(filename_or_fobj, encoding="utf-8", *args, **kwargs):
#

If a file-like object is provided it MUST be open in text (non-binary) mode on Python 3 and could be open in both binary or text mode on Python 2.

    source = Source.from_file(filename_or_fobj, mode="rb", plugin_name="json", encoding=encoding)

    json_obj = json.load(source.fobj, encoding=source.encoding)
    field_names = []
    for row in json_obj:
        for key in row.keys():
            if key not in field_names:
                field_names.append(key)
    table_rows = [[item.get(key) for key in field_names] for item in json_obj]

    meta = {"imported_from": "json", "source": source}
    return create_table([field_names] + table_rows, meta=meta, *args, **kwargs)
#
def _convert(value, field_type, *args, **kwargs):
    if value is None or field_type in (
        fields.BinaryField,
        fields.BoolField,
        fields.FloatField,
        fields.IntegerField,
        fields.JSONField,
        fields.TextField,
    ):
#

If the field_type is one of those, the value can be passed directly to the JSON encoder

        return value
    else:
#

The field type is not represented natively in JSON, then it needs to be serialized (converted to a string)

        return field_type.serialize(value, *args, **kwargs)
#

Export a rows.Table to a JSON file or file-like object.

def export_to_json(
    table, filename_or_fobj=None, encoding="utf-8", indent=None, *args, **kwargs
):
#

If a file-like object is provided it MUST be open in binary mode (like in open('myfile.json', mode='wb')).

    return_data, should_close = False, None
    if filename_or_fobj is None:
        filename_or_fobj = BytesIO()
        return_data = should_close = True

    source = Source.from_file(
        filename_or_fobj,
        plugin_name="json",
        mode="wb",
        encoding=encoding,
        should_close=should_close,
    )
#

TODO: will work only if table.fields is OrderedDict

    fields = table.fields
    prepared_table = prepare_to_export(table, *args, **kwargs)
    field_names = next(prepared_table)
    data = [
        {
            field_name: _convert(value, fields[field_name], *args, **kwargs)
            for field_name, value in zip(field_names, row)
        }
        for row in prepared_table
    ]

    json_data = json.dumps(data, indent=indent)
    if type(json_data) is six.text_type:  # Python 3
        json_data = json_data.encode(encoding)

    if indent is not None:
#

clean up empty spaces at the end of lines

        json_data = b"\n".join(line.rstrip() for line in json_data.splitlines())

    if return_data:
        result = json_data
    else:
        result = source.fobj
        source.fobj.write(json_data)
        source.fobj.flush()

    if source.should_close:
        source.fobj.close()

    return result