使用JSON保留Python元组

时间:2013-03-30 17:28:56

标签: python json tuples

我对此仍然有点新意,所以我可能不知道所有传统术语:

使用JSON编码时是否可以保留Python元组?现在json.loads(json.dumps(tuple))给我一个清单。我不想将我的元组转换为列表,但我想使用JSON。那么,有选择吗?

原因: 我正在创建一个使用多维数组的应用程序,并不总是相同的形状。我有一些类方法使用递归来探测数组并将端点转换为字符串或int。我最近意识到(基于我的递归如何工作)我可以使用元组来防止更深入的数组递归搜索(Python rawks)。在我知道我肯定不需要深入研究我的数据结构的情况下,这可能会派上用场。

5 个答案:

答案 0 :(得分:24)

您可以编写高度专业化的编码器和解码器钩子:

import json

class MultiDimensionalArrayEncoder(json.JSONEncoder):
    def encode(self, obj):
        def hint_tuples(item):
            if isinstance(item, tuple):
                return {'__tuple__': True, 'items': item}
            if isinstance(item, list):
                return [hint_tuples(e) for e in item]
            if isinstance(item, dict):
                return {key: hint_tuples(value) for key, value in item.items()}
            else:
                return item

        return super(MultiDimensionalArrayEncoder, self).encode(hint_tuples(obj))

def hinted_tuple_hook(obj):
    if '__tuple__' in obj:
        return tuple(obj['items'])
    else:
        return obj


enc = MultiDimensionalArrayEncoder()
jsonstring =  enc.encode([1, 2, (3, 4), [5, 6, (7, 8)]])

print jsonstring

# [1, 2, {"items": [3, 4], "__tuple__": true}, [5, 6, {"items": [7, 8], "__tuple__": true}]]

print json.loads(jsonstring, object_hook=hinted_tuple_hook)

# [1, 2, (3, 4), [5, 6, (7, 8)]]

答案 1 :(得分:19)

不,这是不可能的。 JSON格式中没有元组的概念(有关JSON中存在的类型的简要分析,请参阅here)。 Python的json模块将Python元组转换为JSON列表,因为它是JSON中最接近元组的东西。

您还没有在此处详细说明您的用例,但如果您需要存储包含元组的数据结构的字符串表示,则会立即想到一些可能性,这可能是也可能不合适,具体取决于你的情况:

  1. 创建自己的编码和解码功能
  2. 使用pickle(小心; pickle.loads对用户提供的输入无法安全使用。)
  3. 使用reprast.literal_eval代替json.dumpsjson.loadsrepr会在外观上为json.dumps提供合理相似的输出,但repr不会将元组转换为列表。 ast.literal_eval是一个功能较弱,更安全的eval版本,它只会解码字符串,数字,元组,列表,字母,布尔值和None
  4. 选项3可能是最简单,最简单的解决方案。

答案 2 :(得分:4)

用simplejson

import simplejson

def _to_json(python_object) :
    if isinstance(python_object, tuple) :
        python_object = {'__class__': 'tuple',
                         '__value__': list(python_object)}
    else :
        raise TypeError(repr(python_object) + ' is not JSON serializable') 

    return python_object

def _from_json(json_object):                                   
    if json_object['__class__'] == 'tuple':
        return tuple(json_object['__value__'])
    return json_object


jsn = simplejson.dumps((1,2,3), 
                       default=_to_json, 
                       tuple_as_array=False)

tpl = simplejson.loads(jsn, object_hook=_from_json)

答案 3 :(得分:3)

python列表和元组之间的主要区别在于可变性,这与JSON表示无关,只要您不考虑在文本形式中修改JSON列表的内部成员。你可以把你回来的列表变成元组。如果您不使用任何自定义对象解码器,您必须考虑的唯一结构化数据类型是JSON对象和数组,它们以python dicts和列表形式出现。

def tuplify(listything):
    if isinstance(listything, list): return tuple(map(tuplify, listything))
    if isinstance(listything, dict): return {k:tuplify(v) for k,v in listything.items()}
    return listything

如果您专注于解码,或者希望某些JSON数组是python列表而其他JSON数组是python元组,那么您需要将数据项包装在注释类型信息的字典或元组中。这本身就是影响算法控制流的一种更好的方法,而不是基于某些东西是列表或元组(或其他可迭代类型)的分支。

答案 4 :(得分:0)

帕维尔·阿诺索夫(Pavel Anossov)很好地回答了这个问题。为了对诸如元组之类的对象进行编码,代码可以工作。将元组用作Python dict键也很有用,并且上面的代码无法将元组作为dict键处理。为了将元组作为键来管理,可以使用一个布尔标志来表示元组是否为字典键,并且元组将被包装在json.dumps(...)输出层中;在解码过程中,json由递归处理。

解决方案可以允许传递元组vs值的数据结构,从而使哈希更容易。 Python def __hash__(self):经常返回对象中项的元组的哈希值,有时使未包装在类中的简单数据结构很有用。

  1. hint_tuples可以有一个命名参数dict_key-元组作为dict键的标志。 Python字典类型不能是字典的键,最好使用json.dumps(...)将其转换为字符串,在解码过程中应将其恢复为字典,递归应注意将其转换为元组。
  2. 可以选择对__tuple__进行模糊处理,以便在有人将字符串__tuple__作为dict键的一部分进行编码的情况下,它可以通过编码器/解码器。

下面的代码是我想出的用Python dict键编码元组的方法。 __main__中包含一些基本测试,以演示该解决方案。放弃了编码输出的可读性,以增加通过解决方案的案例数量。

    # Pavel Anossov's solution hinted this:
    
    import json
    tuple_signifier = '__tuple__s_i_g_n_i_f_i_e_r__'
    
    class StreamTuple(dict):
         def __hash__(self):
             return hash(str(self))
    
    class MultiDimensionalArrayEncoder(json.JSONEncoder):
        def encode(self, obj):
            def hint_tuples(item, dict_key=False):
                global tuple_signifier
                ret_val = None
                if isinstance(item, tuple):
                    if dict_key:
                        ret_val = json.dumps(dict(
                            [(
                                tuple_signifier,
                                json.dumps(hint_tuples(list(item))),
                            ),],
                        ))
                    else:
                        ret_val = dict(
                            [(
                                tuple_signifier,
                                json.dumps(hint_tuples(list(item))),
                            ),],
                        )
    
                elif isinstance(item, list):
                    ret_val = [hint_tuples(e) for e in item]
                elif isinstance(item, dict):
                    ret_val = dict([
                        (hint_tuples(key, dict_key=True), hint_tuples(value))
                        for key, value in item.items()
                    ])
                else:
                    ret_val = item
                return ret_val
            return super(MultiDimensionalArrayEncoder, self).\
                         encode(hint_tuples(obj))
    
    
    def hinted_tuple_hook(obj):
        global tuple_signifier
    
        ret_val = {}
        if tuple_signifier in obj:
            ret_val = tuple(json.loads(obj[tuple_signifier], object_hook=hinted_tuple_hook,))
        else:
            for k, v in obj.items():
                inner_k = k
                inner_v = v
                if isinstance(k, str) and tuple_signifier in k:
                    inner_k = json.loads(k, object_hook=hinted_tuple_hook,)
                if isinstance(v, str) and tuple_signifier in v:
                    inner_v = json.loads(v, object_hook=hinted_tuple_hook,)
                ret_val[inner_k] = inner_v
        return ret_val
    
    #
    # Some tests that show how to use the above hinted tuple hook to encode 
    # / decode Python tuples.
    #
    if __name__ == '__main__':
        enc = MultiDimensionalArrayEncoder()
        test_input_1 = (2,)
        test_input_2 = {(2,): 'a'}
        test_input_3 = {'a': {(2,): {1:'a'}}}
        print('test_input_1 encoded:', enc.encode(test_input_1), test_input_1)
        print('test_input_1 decoded:',
            json.loads(enc.encode(test_input_1),
                object_hook=hinted_tuple_hook,)
        )
    #"""
        print('test_input_2 encoded:', enc.encode(test_input_2))
        print('test_input_2 decoded:',
            json.loads(enc.encode(test_input_2),
                object_hook=hinted_tuple_hook,)
        )
    
        print('\n' * 3)
        print('test_input_3 encoded:', enc.encode(test_input_3))
        print('test_input_3 decoded:',
            json.loads(enc.encode(test_input_3),
                object_hook=hinted_tuple_hook,)
        )
    
        print('\n' * 3)
        test_input_4 = {'a': 'b'}
        print('test_input_4  encoded:', enc.encode(test_input_4))
        print('test_input_4 decoded:',
            json.loads(enc.encode(test_input_4),
                object_hook=hinted_tuple_hook,)
        )
    
        #"""