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 22614 2015-07-15 18:14:53Z gfiedler $
# Copyright Algebraix Data Corporation 2015 - $Date: 2015-07-15 13:14:53 -0500 (Wed, 15 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

import algebraixlib.mathobjects as _mo


[docs]class ExportJson(json.JSONEncoder): """Custom json encoder. Manages encoding math objects and supported built in data types.""" @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 math object or 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): if isinstance(o, (_mo.Atom, _mo.Couplet, _mo.Set)): return ExportJson._encode_object(o) raise TypeError("Invalid math object, " + str(type(o)) + ", provided to encoder. Expected Atom, Couplet or Set")
[docs]class ImportJson(json.JSONDecoder): """Custom json decoder. Manages decoding json into math objects and hashable data types.""" @staticmethod def _is_math_object_text(s): return 'Atom' or 'Couplet' or 'Set' in s @staticmethod def _decode_object(o): """Return a math object or hashable data type decoded from a json string or 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.") from json.decoder import WHITESPACE
[docs] def decode(self, s, _w=WHITESPACE.match): # Use JSONDecoder to decode the json document into Python objects that we can then decode # into math objects. 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, " + str(type(decoded)) + ", provided to decoder. Expected Atom, Couplet or Set")