如何使用json.dumps()获取字典内的排序列表

5 投票
3 回答
3794 浏览
提问于 2025-04-18 08:50

我遇到了一个问题:我有一个像下面这样的Python字典:

{"qqq": [{"bbb": "111"}, {"aaa": "333"}], "zzz": {"bbb": [5, 2, 1, 9]}}

我想得到一个有序的JSON对象,像这样:

'{"qqq": [{"aaa": "333"}, {"bbb": "111"}], "zzz": {"bbb": [1, 2, 5, 9]}}'

目前我使用的是以下代码:

class ListEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, list):
            return sorted(o)
        return json.JSONEncoder.default(self, o)

print json.dumps(c, sort_keys=True, cls=ListEncoder)

但是我对象里面的两个列表并没有排序,所以我得到的是:

'{"qqq": [{"bbb": "111"}, {"aaa": "333"}], "zzz": {"bbb": [5, 2, 1, 9]}}'

这可能是因为自定义的JSON编码器跳过了一种它已经会处理的类型(列表)。

更新

下面Martijn的解决方案在上面的例子中效果很好,但不幸的是,我需要处理更复杂的字典,层级更深:比如下面这两个:

a = {
    'aaa': 'aaa',
    'op': 'ccc',
    'oppa': {
        'ggg': [{'fff': 'ev'}],
        'flt': {
            'nnn': [
                {
                'mmm': [{'a_b_d': [6]},{'a_b_c': [6,7]}]
                },
                {
                    'iii': [3, 2, 4, 5]
                }
            ]
        }
    },
    'rrr': {},
    'ttt': ['aaa-bbb-ccc']
}
b = {
    'aaa': 'aaa',
    'op': 'ccc',
    'oppa': {
        'ggg': [{'fff': 'ev'}],
        'flt': {
            'nnn': [
                {
                    'iii': [2, 3, 4, 5]
                },
                {
                'mmm': [{'a_b_c': [6,7]},{'a_b_d': [6]}]
                }
            ]
        }
    },
    'rrr': {},
    'ttt': ['aaa-bbb-ccc']
}

如果里面的列表能够排序,它们就会是一样的。但用上面的类处理时,它们并没有排序,所以我得到了两个不同的JSON字符串:

{"aaa": "aaa", "op": "ccc", "oppa": {"flt": {"nnn": [{"iii": [3, 2, 4, 1]}, {"mmm": [{"a_b_d": [6]}, {"a_b_c": [6, 7]}]}]}, "ggg": [{"fff": "ev"}]}, "rrr": {}, "ttt": ["aaa-bbb-ccc"]}
{"aaa": "aaa", "op": "ccc", "oppa": {"flt": {"nnn": [{"iii": [2, 3, 4, 5]}, {"mmm": [{"a_b_c": [6, 7]}, {"a_b_d": [6]}]}]}, "ggg": [{"fff": "ev"}]}, "rrr": {}, "ttt": ["aaa-bbb-ccc"]}

有没有什么办法可以解决这个问题?

3 个回答

-1

我把这个留在这里,因为我也遇到了同样的问题。

你可以使用这个函数来排序你的嵌套数据结构:

def sort_data(data):
    if isinstance(data, dict):
        output = OrderedDict()
        for key, value in data.items():
            output[key] = sort_data(value)
        return output
    elif isinstance(data, list):
        calculated = [sort_data(x) for x in data]
        return sorted(calculated, key=str)
    elif isinstance(data, (int, bool, str, float, type(None))):
        return data
    else:
        raise Exception("Unkown type: {} for {}".format(type(data), data))

举个例子:

data = {"b":[ "zzz", "yyy", "xxx"],
        "d": [42, 54, 675, "aaa"],
        "c": {"a": ["bbb", "ccc", "aaa"]},
        }

sorted_data = sort_data(data)
print(json.dumps(sorted_data, indent=2, sort_keys=True))

# prints:
#{
#    "b": [
#        "xxx",
#        "yyy",
#        "zzz"
#    ],
#    "c": {
#        "a": [
#            "aaa",
#            "bbb",
#            "ccc"
#        ]
#    },
#    "d": [
#        42,
#        54,
#        675,
#        "aaa"
#    ]
#}

0

更新后的问题其实应该是一个新问题,但我更新的解决方案是扩展之前被接受的答案,让列表排序时可以使用更复杂的键:

class SortedListEncoder(json.JSONEncoder):
    def encode(self, obj):
        def get_key(item):
            if isinstance(item, dict):
                return get_key(sorted(item.keys()))
            else:
                return str(item)
        def sort_lists(item):
            if isinstance(item, list):
                return sorted((sort_lists(i) for i in item), key=lambda nm: get_key(nm))
            elif isinstance(item, dict):
                return {k: sort_lists(v) for k, v in item.items()}
            else:
                return item
        return super(SortedListEncoder, self).encode(sort_lists(obj))

这样就可以根据键的排序列表来比较字典。

这并不是对对象的完全排序,但对于你们的两个测试案例(还有我的案例),返回的顺序是一样的:

{"aaa": "aaa", "op": "ccc", "oppa": {"flt": {"nnn": [{"iii": [2, 3, 4, 5]}, {"mmm": [{"a_b_c": [6, 7]}, {"a_b_d": [6]}]}]}, "ggg": [{"fff": "ev"}]}, "rrr": {}, "ttt": ["aaa-bbb-ccc"]}

不过,它不能处理包含字典的列表,这些字典的“第一个”键相同但值不同的情况,也就是说:

a=[{"bb": ["aa", "dd"]}, {"bb": ["cc", "dd"]}]
b=[{"bb": ["dd", "cc"]}, {"bb": ["dd", "aa"]}]

它会生成排序后的子列表,但字典的顺序不会改变:

[{"bb": ["aa", "dd"]}, {"bb": ["cc", "dd"]}]
[{"bb": ["cc", "dd"]}, {"bb": ["aa", "dd"]}]
8

default 方法并不适用于列表;这个方法只是用来处理编码器不知道如何处理的类型。你应该重写 encode 方法:

class SortedListEncoder(json.JSONEncoder):
    def encode(self, obj):
        def sort_lists(item):
            if isinstance(item, list):
                return sorted(sort_lists(i) for i in item)
            elif isinstance(item, dict):
                return {k: sort_lists(v) for k, v in item.items()}
            else:
                return item
        return super(SortedListEncoder, self).encode(sort_lists(obj))

这实际上就是在编码之前对所有列表进行排序(递归地排序);这本来可以在传递给 json.dumps() 之前就完成,但这样做是编码器的一部分责任,就像排序键一样。

演示:

>>> json.dumps(c, sort_keys=True, cls=SortedListEncoder)
'{"qqq": [{"aaa": "333"}, {"bbb": "111"}], "zzz": {"bbb": [1, 2, 5, 9]}}'
>>> json.dumps(a, sort_keys=True, cls=SortedListEncoder)
'{"aaa": "aaa", "op": "ccc", "oppa": {"flt": {"nnn": [{"iii": [2, 3, 4, 5]}, {"mmm": [{"a_b_c": [6, 7]}, {"a_b_d": [6]}]}]}, "ggg": [{"fff": "ev"}]}, "rrr": {}, "ttt": ["aaa-bbb-ccc"]}'
>>> json.dumps(b, sort_keys=True, cls=SortedListEncoder)
'{"aaa": "aaa", "op": "ccc", "oppa": {"flt": {"nnn": [{"iii": [2, 3, 4, 5]}, {"mmm": [{"a_b_c": [6, 7]}, {"a_b_d": [6]}]}]}, "ggg": [{"fff": "ev"}]}, "rrr": {}, "ttt": ["aaa-bbb-ccc"]}'

撰写回答