References:
        http://stackoverflow.com/questions/8230315/python-sets-are
        http://stackoverflow.com/questions/11561932/why-does-json
        https://github.com/jsonpickle/jsonpickle
        http://stackoverflow.com/questions/24369666/typeerror-b1
        http://stackoverflow.com/questions/30469575/how-to-pickle

def make_utool_json_encoder(allow_pickle=False):
    """
    References:
        http://stackoverflow.com/questions/8230315/python-sets-are
        http://stackoverflow.com/questions/11561932/why-does-json
        https://github.com/jsonpickle/jsonpickle
        http://stackoverflow.com/questions/24369666/typeerror-b1
        http://stackoverflow.com/questions/30469575/how-to-pickle
    """
    import utool as ut
    PYOBJECT_TAG = '__PYTHON_OBJECT__'
    UUID_TAG = '__UUID__'
    SLICE_TAG = '__SLICE__'

    def decode_pickle(text):
        obj = pickle.loads(codecs.decode(text.encode(), 'base64'))
        return obj

    def encode_pickle(obj):
        try:
            # Use protocol 2 to support both python2.7 and python3
            COMPATIBLE_PROTOCOL = 2
            pickle_bytes = pickle.dumps(obj, protocol=COMPATIBLE_PROTOCOL)
        except Exception:
            raise
        text = codecs.encode(pickle_bytes, 'base64').decode()
        return text

    type_to_tag = collections.OrderedDict([
        (slice, SLICE_TAG),
        (uuid.UUID, UUID_TAG),
        (object, PYOBJECT_TAG),
    ])

    tag_to_type = {tag: type_ for type_, tag in type_to_tag.items()}

    def slice_part(c):
        return '' if c is None else str(c)

    def encode_slice(s):
        parts = [slice_part(s.start), slice_part(s.stop), slice_part(s.step)]
        return ':'.join(parts)

    def decode_slice(x):
        return ut.smart_cast(x, slice)

    encoders = {
        UUID_TAG: str,
        SLICE_TAG: encode_slice,
        PYOBJECT_TAG: encode_pickle,
    }

    decoders = {
        UUID_TAG: uuid.UUID,
        SLICE_TAG: decode_slice,
        PYOBJECT_TAG: decode_pickle,
    }

    if not allow_pickle:
        del encoders[PYOBJECT_TAG]
        del decoders[PYOBJECT_TAG]
        type_ = tag_to_type[PYOBJECT_TAG]
        del tag_to_type[PYOBJECT_TAG]
        del type_to_tag[type_]

    class UtoolJSONEncoder(json.JSONEncoder):
        def default(self, obj):
            if isinstance(obj, util_type.NUMPY_TYPE_TUPLE):
                return obj.tolist()
            elif six.PY3 and isinstance(obj, bytes):
                return obj.decode('utf-8')
            elif isinstance(obj, (set, frozenset)):
                return list(obj)
                # return json.JSONEncoder.default(self, list(obj))
                # return [json.JSONEncoder.default(o) for o in obj]
            elif isinstance(obj, util_type.PRIMATIVE_TYPES):
                return json.JSONEncoder.default(self, obj)
            elif hasattr(obj, '__getstate__'):
                return obj.__getstate__()
            else:
                for type_, tag in type_to_tag.items():
                    if isinstance(obj, type_):
                        #print('----')
                        #print('encoder obj = %r' % (obj,))
                        #print('encoder type_ = %r' % (type_,))
                        func = encoders[tag]
                        text = func(obj)
                        return {tag: text}
                raise TypeError('Invalid serialization type=%r' % (type(obj)))

        @classmethod
        def _json_object_hook(cls, value, verbose=False, **kwargs):
            if len(value) == 1:
                tag, text = list(value.items())[0]
                if tag in decoders:
                    #print('----')
                    #print('decoder tag = %r' % (tag,))
                    func = decoders[tag]
                    obj = func(text)
                    #print('decoder obj = %r' % (obj,))
                    return obj
            else:
                return value
            return value
    return UtoolJSONEncoder