Source code for algebraixlib.io.mojson

"""Import a `MathObject` from and export it to a custom JSON format that allows a round-trip."""

# $Id: mojson.py 22700 2015-07-28 19:01:00Z jaustell $
# Copyright Algebraix Data Corporation 2015 - $Date: 2015-07-28 14:01:00 -0500 (Tue, 28 Jul 2015) $
#
# This file is part of algebraixlib <http://github.com/AlgebraixData/algebraixlib>.
#
# algebraixlib is free software: you can redistribute it and/or modify it under the terms of version
# 3 of the GNU Lesser General Public License as published by the Free Software Foundation.
#
# algebraixlib 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 algebraixlib.
# If not, see <http://www.gnu.org/licenses/>.
# --------------------------------------------------------------------------------------------------
import json as _json
import json.decoder as _jsondecoder

import algebraixlib.mathobjects as _mo


[docs]class ExportJson(_json.JSONEncoder): """Export a `MathObject` as a custom JSON format that allows a round-trip. .. note:: :class:`~.Multiset` is currently not supported. """ @staticmethod def _get_type_name(o): """Return a string containing the name of the object passed in.""" # split is used to remove any module and package prefixes from the object name. return type(o).__name__.split('.')[-1] @staticmethod def _encode_object(o): """Return a JSON string of a `MathObject` or a supported built-in data type.""" assert isinstance( o, (bool, int, float, str, complex, bytes, range, frozenset, tuple, _mo.Atom, _mo.Couplet, _mo.Set)) or o is None if isinstance(o, (bool, int, float, str)) or o is None: return o elif isinstance(o, complex): return {'complex': str(o)} elif isinstance(o, bytes): return {'bytes': o.decode('utf-8')} elif isinstance(o, range): return {'range': [o.start, o.stop, o.step]} elif isinstance(o, (frozenset, tuple, _mo.Set)): type_name = ExportJson._get_type_name(o) return {type_name: [ExportJson._encode_object(value) for value in o]} elif isinstance(o, _mo.Atom): return {'Atom': ExportJson._encode_object(o.value)} elif isinstance(o, _mo.Couplet): return {'Couplet': {'right': ExportJson._encode_object(o.right), 'left': ExportJson._encode_object(o.left)}}
[docs] def default(self, o): """Convert ``o`` into JSON and return the JSON string.""" if isinstance(o, (_mo.Atom, _mo.Couplet, _mo.Set)): return ExportJson._encode_object(o) raise TypeError('Invalid MathObject {} provided to encoder. Expected Atom, Couplet or Set.' .format(str(type(o))))
[docs]class ImportJson(_json.JSONDecoder): """Import the JSON format created by `ExportJson` and create a `MathObject` from it.""" @staticmethod def _is_math_object_text(s): """Return ``True`` if ``s`` is a string that is the type of a supported `MathObject`.""" return 'Atom' or 'Couplet' or 'Set' in s @staticmethod def _decode_object(o): """Return a `MathObject` or a hashable type decoded from the JSON string in ``o``. :raise: `TypeError` for unknown type. """ # Only JSON object, array and value types should make it here which are all handled so # there's no need for additional type checking. if isinstance(o, (bool, int, float, str)) or o is None: return o elif isinstance(o, list): return [ImportJson._decode_object(value) for value in o] elif isinstance(o, dict): # There should be only one element since dict is used by json for objects; dict is not # valid for math objects because it is not hashable. if len(o) != 1: raise TypeError("Invalid object provided to decoder. Expected a single object but " "received " + str(len(o))) for key, value in o.items(): if key == 'complex': return complex(ImportJson._decode_object(value)) elif key == 'bytes': return bytes(ImportJson._decode_object(value), 'utf-8') elif key == 'range': elements = ImportJson._decode_object(value) return range(elements[0], elements[1], elements[2]) elif key == 'frozenset': return frozenset(ImportJson._decode_object(value)) elif key == 'tuple': return tuple(ImportJson._decode_object(value)) elif key == 'Atom': return _mo.Atom(ImportJson._decode_object(value)) elif key == 'Couplet': left = ImportJson._decode_object(value['left']) right = ImportJson._decode_object(value['right']) return _mo.Couplet(left, right) elif key == 'Set': decoded = ImportJson._decode_object(value) return _mo.Set(decoded) else: raise TypeError("Invalid key, " + key + ", provided to decoder.")
[docs] def decode(self, s, _w=_jsondecoder.WHITESPACE.match): # Use JSONDecoder to decode the JSON document into Python objects that we can then decode # into MathObject instances. decoded = super().decode(s, _w) if isinstance(decoded, dict): # Only one math object is decoded at a time. This supports the json library's management # of collections and deferring to the decoder only when the object is unfamiliar. if len(decoded) != 1: raise TypeError("Invalid object provided to decoder. Expected a single Atom, " "Couplet or Set but received " + str(len(decoded))) if ImportJson._is_math_object_text(decoded): return ImportJson._decode_object(decoded) raise TypeError('Invalid object {} provided to decoder. Expected Atom, Couplet or Set.' .format(str(type(decoded))))