将嵌套的JSON(Dict,List)展平到List中以准备写入DB

2024-06-16 11:09:12 发布

您现在位置:Python中文网/ 问答频道 /正文

我仍然在处理一个问题,使一个嵌套的JSON文件变平。嵌套项可以是List或Dict:

下面是我要展开的文件(与我之前的文章不同,我保持了很长的长度,但它只包含input[0],没有任何后续项,因为它会很长):

input = [{'states': ['USED'], 'niceName': '1-series', 'id': 'BMW_1_Series',
            'years': [{'styles':
                       [{'trim': '128i', 'states': ['USED'], 'submodel': {'body': 'Convertible', 'niceName': 'convertible', 'modelName': '1 Series Convertible'},
                         'name': '128i 2dr Convertible (3.0L 6cyl 6M)', 'id': 100994560},
                        {'trim': '128i', 'states': ['USED'], 'submodel': {'body': 'Coupe', 'niceName': 'coupe', 'modelName': '1 Series Coupe'},
                          'name': '128i 2dr Coupe (3.0L 6cyl 6M)', 'id': 100974974},
                        {'trim': '135i', 'states': ['USED'], 'submodel': {'body': 'Coupe', 'niceName': 'coupe', 'modelName': '1 Series Coupe'}, 
                         'name': '135i 2dr Coupe (3.0L 6cyl Turbo 6M)', 'id': 100974975},
                        {'trim': '135i', 'states': ['USED'], 'submodel': {'body': 'Convertible', 'niceName': 'convertible', 'modelName': '1 Series Convertible'}, 
                         'name': '135i 2dr Convertible (3.0L 6cyl Turbo 6M)', 'id': 100994561}
                        ],
                       'states': ['USED'], 'id': 100524709, 'year': 2008},
                      {'styles':
                       [{'trim': '135i', 'states': ['USED'], 'submodel': {'body': 'Coupe', 'niceName': 'coupe', 'modelName': '1 Series Coupe'}, 
                         'name': '135i 2dr Coupe (3.0L 6cyl Turbo 6M)', 'id': 101082656}, 
                        {'trim': '128i', 'states': ['USED'], 'submodel': {'body': 'Coupe', 'niceName': 'coupe', 'modelName': '1 Series Coupe'}, 
                         'name': '128i 2dr Coupe (3.0L 6cyl 6M)', 'id': 101082655},
                        {'trim': '135i', 'states': ['USED'], 'submodel': {'body': 'Convertible', 'niceName': 'convertible', 'modelName': '1 Series Convertible'}, 
                         'name': '135i 2dr Convertible (3.0L 6cyl Turbo 6M)', 'id': 101082663},
                        {'trim': '128i', 'states': ['USED'], 'submodel': {'body': 'Convertible', 'niceName': 'convertible', 'modelName': '1 Series Convertible'}, 
                         'name': '128i 2dr Convertible (3.0L 6cyl 6M)', 'id': 101082662}
                        ], 
                       'states': ['USED'], 'id': 100503222, 'year': 2009},
                      {'styles': 
                       [{'trim': '128i', 'states': ['USED'], 'submodel': {'body': 'Coupe', 'niceName': 'coupe', 'modelName': '1 Series Coupe'}, 
                         'name': '128i 2dr Coupe (3.0L 6cyl 6M)', 'id': 101200599},
                        {'trim': '135i', 'states': ['USED'], 'submodel': {'body': 'Coupe', 'niceName': 'coupe', 'modelName': '1 Series Coupe'}, 
                         'name': '135i 2dr Coupe (3.0L 6cyl Turbo 6M)', 'id': 101200600}, 
                        {'trim': '135i', 'states': ['USED'], 'submodel': {'body': 'Convertible', 'niceName': 'convertible', 'modelName': '1 Series Convertible'}, 
                         'name': '135i 2dr Convertible (3.0L 6cyl Turbo 6M)', 'id': 101200607}, 
                        {'trim': '128i', 'states': ['USED'], 'submodel': {'body': 'Convertible', 'niceName': 'convertible', 'modelName': '1 Series Convertible'}, 
                         'name': '128i 2dr Convertible (3.0L 6cyl 6M)', 'id': 101200601}
                        ], 
                       'states': ['USED'], 'id': 100529091, 'year': 2010}, 
                      {'styles':
                       [{'trim': '128i', 'states': ['USED'], 'submodel': {'body': 'Coupe', 'niceName': 'coupe', 'modelName': '1 Series Coupe'}, 
                         'name': '128i 2dr Coupe (3.0L 6cyl 6M)', 'id': 101288165}, 
                        {'trim': '135i', 'states': ['USED'], 'submodel': {'body': 'Coupe', 'niceName': 'coupe', 'modelName': '1 Series Coupe'}, 
                         'name': '135i 2dr Coupe (3.0L 6cyl Turbo 6M)', 'id': 101288166}, 
                        {'trim': '135i', 'states': ['USED'], 'submodel': {'body': 'Convertible', 'niceName': 'convertible', 'modelName': '1 Series Convertible'}, 
                         'name': '135i 2dr Convertible (3.0L 6cyl Turbo 6M)', 'id': 101288298}, 
                        {'trim': '128i', 'states': ['USED'], 'submodel': {'body': 'Convertible', 'niceName': 'convertible', 'modelName': '1 Series Convertible'}, 
                         'name': '128i 2dr Convertible (3.0L 6cyl 6M)', 'id': 101288297}
                        ], 
                       'states': ['USED'], 'id': 100531309, 'year': 2011}, 
                      {'styles': 
                       [{'trim': '128i', 'states': ['USED'], 'submodel': {'body': 'Convertible', 'niceName': 'convertible', 'modelName': '1 Series Convertible'}, 
                         'name': '128i 2dr Convertible (3.0L 6cyl 6M)', 'id': 101381667}, 
                        {'trim': '135i', 'states': ['USED'], 'submodel': {'body': 'Convertible', 'niceName': 'convertible', 'modelName': '1 Series Convertible'}, 
                         'name': '135i 2dr Convertible (3.0L 6cyl Turbo 6M)', 'id': 101381668}, 
                        {'trim': '128i', 'states': ['USED'], 'submodel': {'body': 'Coupe', 'niceName': 'coupe', 'modelName': '1 Series Coupe'}, 
                         'name': '128i 2dr Coupe (3.0L 6cyl 6M)', 'id': 101381665}, 
                        {'trim': '135i', 'states': ['USED'], 'submodel': {'body': 'Coupe', 'niceName': 'coupe', 'modelName': '1 Series Coupe'}, 
                         'name': '135i 2dr Coupe (3.0L 6cyl Turbo 6M)', 'id': 101381666}
                        ], 
                       'states': ['USED'], 'id': 100534729, 'year': 2012}, 
                      {'styles': 
                       [{'trim': '128i', 'states': ['USED'], 'submodel': {'body': 'Coupe', 'niceName': 'coupe', 'modelName': '1 Series Coupe'}, 
                        'name': '128i 2dr Coupe (3.0L 6cyl 6M)', 'id': 200428722},
                        {'trim': '128i', 'states': ['USED'], 'submodel': {'body': 'Convertible', 'niceName': 'convertible', 'modelName': '1 Series Convertible'}, 
                         'name': '128i 2dr Convertible (3.0L 6cyl 6M)', 'id': 200428721}, 
                        {'trim': '135is', 'states': ['USED'], 'submodel': {'body': 'Coupe', 'niceName': 'coupe', 'modelName': '1 Series Coupe'}, 
                         'name': '135is 2dr Coupe (3.0L 6cyl Turbo 6M)', 'id': 200421701}, 
                        {'trim': '135i', 'states': ['USED'], 'submodel': {'body': 'Coupe', 'niceName': 'coupe', 'modelName': '1 Series Coupe'}, 
                         'name': '135i 2dr Coupe (3.0L 6cyl Turbo 6M)', 'id': 200428724}, 
                        {'trim': '135i', 'states': ['USED'], 'submodel': {'body': 'Convertible', 'niceName': 'convertible', 'modelName': '1 Series Convertible'}, 
                         'name': '135i 2dr Convertible (3.0L 6cyl Turbo 6M)', 'id': 200428723}, 
                        {'trim': '128i SULEV', 'states': ['USED'], 'submodel': {'body': 'Coupe', 'niceName': 'coupe', 'modelName': '1 Series Coupe'}, 
                         'name': '128i SULEV 2dr Coupe (3.0L 6cyl 6M)', 'id': 200428726}, 
                        {'trim': '128i SULEV', 'states': ['USED'], 'submodel': {'body': 'Convertible', 'niceName': 'convertible', 'modelName': '1 Series Convertible'}, 
                         'name': '128i SULEV 2dr Convertible (3.0L 6cyl 6M)', 'id': 200428725}, 
                        {'trim': '135is', 'states': ['USED'], 'submodel': {'body': 'Convertible', 'niceName': 'convertible', 'modelName': '1 Series Convertible'}, 
                         'name': '135is 2dr Convertible (3.0L 6cyl Turbo 6M)', 'id': 200428727}
                        ], 
                       'states': ['USED'], 'id': 200421700, 'year': 2013}
                      ], 
          'name': '1 Series', 'make': {'niceName': 'bmw', 'name': 'BMW', 'id': 200000081}
          }, #here is more to come, but I needed to crop it
          ]

到目前为止,我使用的代码是由@poke from:Flattening Generic JSON List of Dicts or Lists in Python编写的

^{pr2}$

我收到以下错误:

AttributeError: 'str' object has no attribute 'items'

这是由于'states': ['USED']

我不知道该怎么办。键“states”可以作为一个列表保存。在

我希望有人能帮我解决这个问题。在

附言:这是Python: Write Nested JSON as multiple elements in List的后续帖子


Tags: nameidbodyusedseriesturbostatestrim
3条回答

这是我对splitObj的解决方案

def splitObj (obj, prefix = None):
'''
Split the object, returning a 3-tuple with the flat object, optionally
followed by the key for the subobjects and a list of those subobjects.
obj needs to be a Dictonary
'''
# copy the object, optionally add the prefix before each key
new = obj.copy() if prefix is None or prefix=="NotFlat" else { '{}_{}'.format(prefix, k): v for k, v in obj.items() }

cL = 0
cD = 0
# try to find the key holding the subobject or a list of subobjects
for k, v in new.items():
    #Determine the number of lists in v
    if isinstance(v, list):
        cL += 1
    #Determine the number of dict in v
    elif isinstance(v, dict):
        cD += 1     
for k, v in new.items():
    # list of subobjects
    if isinstance(v, list):
        if (cD+cL) <=1:
            try:
                type(v[0])
            except IndexError:
                v = [""]
            if not isinstance(v[0], str):
                del new[k]
                return new, k, v
            elif isinstance(v[0], str):
                #handle list when only containing strings, return, the whole thing
                #solve other dicts which might be in the line
                #use "NotFlat" to run loop again but without adding a prefix

                new[k] = ", ".join(v)
                return new, None, None
            else:
                custLog.logger.info("")
        elif (cD+cL) >1:

            #print("Count List2 CD: "+str(cD))
            #print("Count LIST2 CL: "+str(cL))

            #if list is empty
            try:
                type(v[0])
            except IndexError:
                v = [""]

            if not isinstance(v[0], str):
                del new[k]
                for x in flatten([new]):
                    newOut = x
                    break
                return newOut, k, v
            elif isinstance(v[0], str):
                #handle list when only containing strings, return, the whole thing
                #solve other dicts which might be in the line
                #use "NotFlat" to run loop again but without adding a prefix
                new[k] = ", ".join(v)
                return None, "NotFlat", [new]
            else:
                custLog.logger.error("weder noch 2")

    # or just one subobject
    elif isinstance(v, dict):
        if (cD+cL) <=1:
            del new[k]
            return new, k, [v]
        elif (cD+cL) >1:
            del new[k]
            for x in flatten([new]):
                newOut = x
                break
            return newOut, k, [v]
return new, None, None

这里是平坦的

^{pr2}$

重新思考问题

对于一个更普遍的问题,找到解决办法往往更容易。所以,让我们先仔细看看这个问题。在

输入是描述一组对象的JSON文件。在

对象被反复定义为原子(字符串或数字)或具有对象值的dict。列表用于表示备选方案(即列表中的任何元素都可以代替列表)。 例如,{a:[1,2]}表示{}可以是1或{}。在

输出应该是不包含任何选项的对象列表。此外,对象应该是扁平的,也就是说,应该是dicts,其值是原子,其键描述原始对象中值的路径。在

我的解决方案分别处理备选方案和扁平化。在

标准化

下面的函数normalise接受json.dumps的输入并生成一系列dict。注意,normalise的输入和输出具有相同的语义,并且描述了相同的对象集。产出只是标准化了,因为它只包含顶层的替代品。数据库人员将其称为非规范化,因为这对于关系数据库是不可取的。在

normalise始终返回一个对象序列。normalise被实现为生成器,以保持较低的内存使用率。在

下列情况在normalise中有区别。在

  • 原子输入意味着只有一种可能性。这样,就产生了原子(这就像返回一个包含原子的列表)。在
  • 列表意味着备选方案。它将生成其标准化输入的所有元素(这类似于连接列表)。在
  • dict意味着我们必须考虑各个键的所有可选组合。所以,我们返回备选方案的笛卡尔积。在

代码如下:

import itertools

def normalise(x):
    if isinstance(x, dict):
        keys = x.keys()
        values = (normalise(i) for i in x.values())
        for i in itertools.product(*values):
            yield (dict(zip(keys, i)))
    elif isinstance(x, list):
        #if not x:           # uncomment for "LEFT JOIN" behaviour
        #    yield None
        for i in x:
            yield from normalise(i)
    else:
        yield x

如果对象包含任何空列表,则此代码不返回该对象。这是因为没有可能的值。这就像SQL“内部联接”。从Bert的回答看来,他想要“LEFT JOIN”行为(即一些默认值)。要实现这一点,只需取消对这两行的注释。在

伪展平

normalise生成的对象仍然具有原始的(嵌套的)dict结构。可以使用在其他讨论中找到的代码将它们展平。在

但是,OP希望在数据库中插入对象。因此,他很可能不需要一个扁平字典的键列表。他只需要一个返回给定路径值的函数。在

这可以通过为dict创建一个具有__getitem__方法的包装器对象来实现。此包装器还可用于返回不存在路径的默认值。在

^{pr2}$

sql插入可以看起来像下面这样(用psycopg2测试)

^{3}$

实施细节

  • 为了清晰起见,这种实现显然牺牲了一些运行时性能。

  • 抽象基类可以用来代替list和{}。但是,这可能会有问题,因为str是一个序列,但应该作为原子来处理。

  • 只有当DictWrapper不包含在任何dict键中时,DictWrapper才能正常工作。

  • normalise不过滤出重复项。这可以通过使用集合和命名元组来实现,而不是使用列表和dict。然而,这意味着整个结果必须在内存中。最好在数据库级别筛选出重复项。

  • 为了将内存使用量保持在最低限度,应该延迟读取JSON。

虽然不是一个泛化函数,但考虑遍历每个嵌套元素以获得用于数据库导入或flatfile(csv,txt)导出的平面输出。由于json文件由字典和列表的组合组成,因此在每个级别上相应地处理它们:

items = []
for outer in data:    
    inner = [''] * 15    
    for outerk, outerv in outer.items():        
        inner[0] = outer['states'][0]
        inner[1] = outer['niceName']
        inner[2] = outer['id']
        inner[3] = outer['make']['niceName']
        inner[4] = outer['make']['name']
        inner[5] = outer['make']['id']    
        if outerk == 'years':            
            for yri in outer[outerk]:                
                for yrk, yrv in yri.items():
                    inner[6] = yri['states'][0] 
                    inner[7] = yri['id'] 
                    inner[8] = yri['year'] 
                    if yrk == 'styles':
                        for stylei in yri[yrk]:
                            inner[9] = stylei['trim']
                            inner[10] = stylei['name']
                            inner[11] = stylei['id']
                            inner[12] = stylei['submodel']['body']
                            inner[13] = stylei['submodel']['niceName']
                            inner[14] = stylei['submodel']['modelName']

                            items.append(inner[0:14])

for i in items:        
    print(i)

输出(父项对每个子项重复)

^{pr2}$

相关问题 更多 >