51

我对此还是有点陌生​​,所以我可能不知道所有事物的常规术语:

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

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

4

5 回答 5

33

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

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)]]
于 2013-03-30T17:58:24.290 回答
24

不,这是不可能的。JSON 格式中没有元组的概念(请参阅此处了解 JSON 中存在哪些类型的简明细分)。Python 的json模块将 Python 元组转换为 JSON 列表,因为这是 JSON 中最接近元组的东西。

您在这里没有详细介绍您的用例,但是如果您需要存储包含元组的数据结构的字符串表示形式,那么您会立即想到一些可能性,根据您的情况,这可能合适也可能不合适:

  1. 创建自己的编码和解码函数
  2. 使用pickle(小心;pickle.loads在用户提供的输入上使用不安全)。
  3. 使用reprandast.literal_eval代替json.dumpsand json.loadsrepr将为您提供外观上与 相当相似的输出json.dumps,但repr不会将元组转换为列表。ast.literal_eval是一个功能更弱、更安全的版本,eval它只会解码字符串、数字、元组、列表、字典、布尔值和None.

选项 3 可能是您最简单和最简单的解决方案。

于 2013-03-30T17:32:04.257 回答
5

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

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 列表而其他的是 python 元组,则需要将数据项包装在注释类型信息的 dict 或元组中。这本身是一种影响算法控制流的更好方法,而不是基于某事物是列表还是元组(或其他可迭代类型)的分支。

于 2014-08-13T19:41:52.980 回答
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)
于 2015-04-28T02:09:21.217 回答
0

Pavel Anossov 很好地回答了这个问题。要对元组等对象进行编码,代码可以工作。将元组作为 Python dict 键也很有用,并且上面的代码没有将元组作为 dict 键处理。要将元组作为键进行管理,可以使用表示元组是否为 dict 键的布尔标志,并且元组被包装在 json.dumps(...) 输出层中;在解码期间,json 通过递归处理。

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

  1. hint_tuples 可以有一个命名参数 dict_key - 元组的标志是一个字典键。Python dict 类型不能作为 dict 的键,最好使用 json.dumps(...) 将其转换为字符串,在解码期间应将其恢复为 dict,并且递归应注意将其转换为元组。
  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,)
        )
    
        #"""
于 2020-09-12T12:02:41.470 回答