首页 > 解决方案 > JSON将重复条目转换为数组,但恢复原始顺序

问题描述

我有一个带有重复条目的 JSON 字符串。我已经学会了如何在调用 json.loads(string) 时将它们转换为数组以保留所有这些,参见例如https://stackoverflow.com/a/61416136/7471760

现在的问题是,在某些情况下,我有非常奇怪的输入 JSON 字符串(我无法更改),我确实需要在“不同”重复项集之间保留顺序。例如:

jstring = '\
{\
   "anna": { "age": 23, "color": "green"},\
   "john": { "age": 35, "color": "blue"},\
   "laura":{ "age": 32, "color": "red"},\
   "john": { "age": 31, "color": "black"},\
   "anna": { "age": 41, "color": "pink"}\
}'

现在我使用这个钩子将此字符串转换为 JSON 对象,但不会丢失重复项(不同的学生)。

def array_on_duplicates(ordered_pairs):
    d = {}
    for k, v in ordered_pairs:
        if k in d:
            if type(d[k]) is list:
                d[k].append(v)
            else:
                d[k] = [d[k],v]
        else:
           d[k] = v
    return d

但是,我仍然需要恢复这些学生的原始列表顺序(第一个输入 - 第一个输出)。

使用 json.loads() 时,我有所有条目,但我失去了原来的顺序:

json.loads(jstring, object_pairs_hook=array_on_duplicates)
{'anna': [{'age': 23, 'color': 'green'}, {'age': 41, 'color': 'pink'}],
 'john': [{'age': 35, 'color': 'blue'}, {'age': 31, 'color': 'black'}],
 'laura': {'age': 32, 'color': 'red'}}

解决这个问题的最有效方法是什么?(除了更改繁琐的输入字符串,不幸的是我不能)。

标签: pythonjson

解决方案


我想到的一种解决方法是按照此处的建议为每个条目 anna_1、anna_2 等生成重复的密钥:https ://stackoverflow.com/a/29323197/7471760 ,这样一个人就可以拥有唯一的条目,然后挂钩这对到 OrderedDict。

其他选择是直接在钩子中返回键值元组并稍后处理它https://stackoverflow.com/a/29322077/7471760

但是,保留数组结构对我来说非常有用,最适合我的是使用这种解决方法,将顺序显式保留在一个额外的键中:

def array_on_duplicates_keep_order(ordered_pairs):
    """Convert duplicate keys to arrays and store order on an extra key."""
#    https://www.semicolonworld.com/question/56998/python-json-parser-allow-duplicate-keys
#    https://stackoverflow.com/questions/14902299/json-loads-allows-duplicate-keys-in-a-dictionary-overwriting-the-first-value

    d = {}
    order = 0
    for k, v in ordered_pairs:
        if type(v) is dict:
            v['o'] = order
        if k in d:
            if type(d[k]) is list:
                d[k].append(v)
            else:
                d[k] = [d[k],v]
        else:
           d[k] = v
        order += 1
    return d

产生:

jobj = json.loads(jstring, object_pairs_hook=array_on_duplicates_keep_order)
{'anna': [{'age': 23, 'color': 'green', 'o': 0},
  {'age': 41, 'color': 'pink', 'o': 4}],
 'john': [{'age': 35, 'color': 'blue', 'o': 1},
  {'age': 31, 'color': 'black', 'o': 3}],
 'laura': {'age': 32, 'color': 'red', 'o': 2}}

最后,我可以通过使用命名元组并按顺序键排序来恢复学生的原始顺序:

class Student(NamedTuple):
    name: str
    age: int
    color: str
    o: int

studentList = []
for k, v in jobj.items(): 
    if not type(v) is list:
       studentList.append(Student(k, v['age'], v['color'], v['o']))
    else:
       for s in v:
           studentList.append(Student(k, s['age'], s['color'], s['o']))

orderedList = sorted(studentList, key=lambda s: s.o) 

这给了我想要的东西,而无需更改输入并仍然使用 JSON 作为中间存储变量:

studentList
[Student(name='anna', age=23, color='green', o=0),
 Student(name='anna', age=41, color='pink', o=4),
 Student(name='john', age=35, color='blue', o=1),
 Student(name='john', age=31, color='black', o=3),
 Student(name='laura', age=32, color='red', o=2)]

orderedList
[Student(name='anna', age=23, color='green', o=0),
 Student(name='john', age=35, color='blue', o=1),
 Student(name='laura', age=32, color='red', o=2),
 Student(name='john', age=31, color='black', o=3),
 Student(name='anna', age=41, color='pink', o=4)]

推荐阅读