docs: 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