Python的JSON解析器允许重复的键。

13

我需要解析一个JSON文件,很不幸的是,它不遵循原型。我有两个问题需要解决,但我已经找到了解决方法,所以我会在最后提一下,也许有人可以帮忙。

因此,我需要解析这样的条目:

    "Test":{
        "entry":{
            "Type":"Something"
                },
        "entry":{
            "Type":"Something_Else"
                }
           }, ...

json默认解析器会更新字典,因此只使用最后一个条目。我必须以某种方式存储其他条目,但我不知道如何做到这一点。我还必须按文件中出现的顺序在几个字典中存储键,这就是我使用OrderedDict来实现的原因。它运行良好,因此如果有任何扩展重复条目的方法,我将不胜感激。

我的第二个问题是同一个json文件包含以下条目:

         "Test":{
                   {
                       "Type":"Something"
                   }
                }

当Json.load()函数在json文件中遇到该行时,会引发异常。我解决这个问题的唯一方法是手动删除内部括号。

提前感谢您


为什么不根据你的数据模型改变结构呢?好的,你有一个带有这种结构的文件或数据,对吗? - ManuParra
是的,它是一个完整的JSON文件。你有什么想法我可以做吗? - Greg K.
实际上,这个文件并不是JSON文件。因此,如果你想使用JSON python解析器来解析它,可能会失败。好的,我需要一点这段代码来尝试。尝试使用SIMPLEJSON python:http://simplejson.readthedocs.org/en/latest/ ,它比原始的Python JSON解析器更灵活。 - ManuParra
你想将这些数据解析成什么Python数据结构?字典不能有重复的键,这只是字典的属性,与JSON无关。那么你会如何使用这个数据结构呢?如果键不唯一,你期望通过键查找返回什么? - Lukas Graf
我想要能够修改重复的键名,并将其以不同的名称添加回字典中。我对保留字典中相同的名称不感兴趣,只是不想丢失数据。 - Greg K.
在这里回答:http://stackoverflow.com/questions/20829646/how-do-i-parse-json-with-multiple-keys-the-same - frnhr
3个回答

24
您可以使用 JSONDecoder.object_pairs_hook 来自定义JSONDecoder解码对象的方式。此钩子函数将接收到一个由(key, value)对组成的列表,您通常会对这些对进行一些处理,然后转换为dict
然而,由于 Python 字典不允许有重复的键(您无法更改这一点),因此您可以在钩子中保持这些对不变,并在解码 JSON 时获得一个嵌套的(key, value)对列表:
from json import JSONDecoder

def parse_object_pairs(pairs):
    return pairs


data = """
{"foo": {"baz": 42}, "foo": 7}
"""

decoder = JSONDecoder(object_pairs_hook=parse_object_pairs)
obj = decoder.decode(data)
print obj

输出:

[(u'foo', [(u'baz', 42)]), (u'foo', 7)]

如何使用这个数据结构完全取决于您。如上所述,Python字典不允许重复的键,并且没有任何方法可以绕过这一点。你怎么查找一个键呢?dct[key]会产生歧义。

因此,您可以实现自己的逻辑来处理期望工作方式的查找,或者实现某种冲突避免以使键唯一(如果它们不是),然后从嵌套列表创建一个字典。


编辑:由于您想要修改重复的键以使其唯一,这是如何做到的:

from collections import OrderedDict
from json import JSONDecoder


def make_unique(key, dct):
    counter = 0
    unique_key = key

    while unique_key in dct:
        counter += 1
        unique_key = '{}_{}'.format(key, counter)
    return unique_key


def parse_object_pairs(pairs):
    dct = OrderedDict()
    for key, value in pairs:
        if key in dct:
            key = make_unique(key, dct)
        dct[key] = value

    return dct


data = """
{"foo": {"baz": 42, "baz": 77}, "foo": 7, "foo": 23}
"""

decoder = JSONDecoder(object_pairs_hook=parse_object_pairs)
obj = decoder.decode(data)
print obj

输出:

OrderedDict([(u'foo', OrderedDict([(u'baz', 42), ('baz_1', 77)])), ('foo_1', 7), ('foo_2', 23)])

make_unique 函数负责返回一个无冲突的键。在这个例子中,它只是用 _n 后缀键名,其中 n 是递增计数器 - 可根据需要进行调整。

因为 object_pairs_hook 恰好按照 JSON 文档中出现的顺序接收到这些键值对,所以也可以通过使用 OrderedDict 来保留该顺序,我也将其包含在内。


@GregKassapidis 更新了我的答案。 新代码应该大多是自我解释的,但如果你需要任何澄清,请让我知道。 - Lukas Graf
顺便问一下,你有关于第二个问题的任何想法吗?我该如何过滤掉那些双括号? - Greg K.
“double brackets”指的是什么?OrderedDict([(只是一种OrderedDict的表示方式,如果打印出来,否则它的工作方式与普通字典相同。 - Lukas Graf
啊,抱歉,我错过了那部分内容。那只是无效的JSON,几乎任何解码器都会失败。我看不到除了事先清理JSON(手动或程序化)之外的其他处理方式。 - Lukas Graf
不,那个流浪的 {"Type":"Something"} 应该如何解释?是缺少一个键,它应该是 "somekey": {"Type":"Something"} 吗?还是有一个多余的 { } 层级,它实际上应该属于 "Test":{} 对象?或者应该直接删除它?没有解析器,甚至没有宽容的解析器,可以为您决定。只有 JSON 文件的创建者(可能还有您)知道,因此您必须以非常特定的方式修复它。 - Lukas Graf
显示剩余4条评论

2
非常感谢@Lukas Graf,我通过实现自己版本的挂钩函数也使其正常工作了。
def dict_raise_on_duplicates(ordered_pairs):
  count=0
  d=collections.OrderedDict()
  for k,v in ordered_pairs:
      if k in d:
          d[k+'_dupl_'+str(count)]=v
          count+=1
      else:
          d[k]=v
  return d

现在唯一需要做的就是自动去掉双括号,然后我就完成了 :D 再次感谢。


1
如果您希望将那些重复的键转换为数组,而不是拥有独立的副本,可以使用以下代码:
def dict_raise_on_duplicates(ordered_pairs):
    """Convert duplicate keys to JSON array."""
    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

然后你只需要使用:


dict = json.loads(yourString, object_pairs_hook=dict_raise_on_duplicates) 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接