Python >> Tutoriel Python >  >> Python Tag >> JSON

python json imbriqué dans csv/xlsx avec les en-têtes spécifiés

Le code suivant est capable d'analyser les données fournies selon le format attendu.

from typing import List

def parse_recursive(dat)->List[List]:
    ret=[]
    if type(dat) is list:
        for item in dat:
            if type(item)==dict:
                for k in item:
                    #print(k, item[k], sep=" # ")#debug print
                    if item[k]==[]: #empty list
                        ret.append([k])
                    else:
                        for l in parse_recursive(item[k]):
                            #print(k,l,sep=" : ") #debug print
                            ret.append([k]+l) #always returns List of List
            else: #Right now only possibility is string eg. "one", "two"
                return [[",".join(dat)]]
    else: #can be int or string eg. 97, "23"
        return [[dat]]

    return ret


def write_to_csv(file_name:str, fields:List, row_data:List[List]):
    import csv
    with open(file_name, 'w') as csvfile:  
        # creating a csv writer object  
        csvwriter = csv.writer(csvfile)  
        # writing the fields  
        csvwriter.writerow(fields)  
        # writing the data rows  
        csvwriter.writerows(row_data)


if __name__=="__main__":
    org_data = [{"a": [
        {"a1": [
            {"id0": [
                {
                    "aa": [
                        {"aaa": 97},
                        {"aab": "one"}],
                    "ab": [
                        {"aba": 97},
                        {"abb": ["one", "two"]}
                        ]
                }
            ]
            },
            {"id1": [
                {"aa": [
                    {"aaa": 23}]}]}
            ]
        },
        {"a2": []}
        ]},
        {"b": [{"b1": [{"Common": [{"bb": [{"value": 4}]}]}]}]}]
    print(parse_recursive(org_data)) #Debug

    file_name="data_file.csv"
    fields=['Section', 'Subsection', 'pId', 'Group', 'Parameter', 'Value']
    write_to_csv(file_name, fields, parse_recursive(org_data))

parse_recursive essaie d'analyser le dictionnaire de profondeur arbitraire selon la règle que j'ai essayé de déduire de vos formats d'entrée et de sortie.

Voici la sortie de parse_recursive pour votre contribution fournie -

[email protected]:~/Desktop$ python3 so.py 
[['a', 'a1', 'id0', 'aa', 'aaa', 97], ['a', 'a1', 'id0', 'aa', 'aab', 'one'], ['a', 'a1', 'id0', 'ab', 'aba', 97], ['a', 'a1', 'id0', 'ab', 'abb', 'one,two'], ['a', 'a1', 'id1', 'aa', 'aaa', 23], ['a', 'a2'], ['b', 'b1', 'Common', 'bb', 'value', 4]]

write_to_csv est une fonction triviale qui écrit dans un fichier csv.


C'était une sorte de problème amusant... Il y a vraiment deux problèmes avec le formatage ici :

  1. Les données sont des listes de dicts, où ils voulaient vraiment juste des dictionnaires. par exemple. ils voulaient {"foo": 1, "bar": 2} mais à la place formaté en [{"foo": 1}, {"bar": 2}] .

    un. Je ne juge pas ici. Il peut y avoir des raisons pour lesquelles ils ont fait cela. Cela rend juste un peu ennuyeux pour nous d'analyser.

  2. Les données sont parfois tronquées; s'il y a généralement 5 niveaux de profondeur, parfois s'ils n'ont pas de données au-delà d'un point, ils l'omettent simplement. par exemple. 'a2' dans votre exemple.

Je vais donc montrer deux approches possibles pour résoudre ces problèmes.

La manière des pandas

Cette solution est un peu différente de l'autre mentionnée ici. Dites-moi ce que vous en pensez :

import pandas as pd
from copy import deepcopy

hdrs = ['Section', 'Subsection', 'pId', 'Group', 'Parameter', 'Value']

js = [{"a": [{"a1": [{"id0": [{"aa": [{"aaa": 97}, {"aab": "one"}],
                               "ab": [{"aba": 98}, {"abb": ["one", "two"]}]}]},
                     {"id1": [{"aa": [{"aaa": 23}]}]}
                    ]},
             {"a2": []}
            ]},
      {"b": [{"b1": [{"Common": [{"bb": [{"value": 4}]}]}]}]}]

def list_to_dict(lst):
    """convert a list of dicts as you have to a single dict

    The idea here is that you have a bunch of structures that look
    like [{x: ...}, {y: ...}] that should probably have been stored as
    {x:..., y:...}. So this function does that (but just one level in).
    
    Note:
    If there is a duplicate key in one of your dicts (meaning you have
    something like [{x:...},...,{x:...}]), then this function will overwrite
    it without warning!
    """
    d = {}
    for new_d in lst:
        d.update(new_d)
    return d

def recursive_parse(lst, levels):
    "Parse the nested json into a single pandas dataframe"
    name = levels.pop(0)  # I should have used a counter instead
    d = list_to_dict(lst)  # get a sensible dict instead of the list of dicts
    if len(levels) <= 1: # meaning there are no more levels to be parsed.
        if len(d) == 0:
            d = {'': ''} # to handle the uneven depths (e.g. think 'a2')
        return pd.Series(d, name=levels[-1])
    if len(d) == 0: # again to handle the uneven depths of json
        d = {'': []}
    # below is a list-comprehension to recursively parse the thing.
    d = {k: recursive_parse(v, deepcopy(levels)) for k, v in d.items()}
    return pd.concat(d)

def json_to_df(js, headers):
    "calls recursive_parse, and then adds the column names and whatnot"
    df = recursive_parse(js, deepcopy(headers))
    df.index.names = headers[:-1]
    df = df.reset_index()
    return df
df = json_to_df(js, hdrs)
display(df)

Et la sortie est exactement la trame de données souhaitée (mais avec une colonne d'index, vous ne voudrez peut-être pas). Si vous l'écrivez ensuite au format CSV, procédez comme suit :

df.to_csv('path/to/desired/file.csv', index=False)

Cela a-t-il un sens ?

La manière minimaliste

Meilleure version (n'utilisant pas de pandas)...

import csv

hdrs = ['Section', 'Subsection', 'pId', 'Group', 'Parameter', 'Value']

js = [{"a": [{"a1": [{"id0": [{"aa": [{"aaa": 97}, {"aab": "one"}],
                               "ab": [{"aba": 98}, {"abb": ["one", "two"]}]}]},
                     {"id1": [{"aa": [{"aaa": 23}]}]}
                    ]},
             {"a2": []}
            ]},
      {"b": [{"b1": [{"Common": [{"bb": [{"value": 4}]}]}]}]}]

def list_of_dicts_to_lists(lst, n_levels=len(hdrs)):
    if n_levels == 1:
        if isinstance(lst, list):
            if len(lst) == 0: # we fill the shorter ones with empty lists
                lst = None # replacing them back to None
            else: # [1, 2] => "1,2"
                lst = ','.join(str(x) for x in lst if x is not None)
        return [[lst]] # the later ones are going to be lists of lists so let's start out that way to keep everything consistent.
    if len(lst) == 0:
        lst = [{None: []}] # filling with an empty list
    output = []
    for d in lst:
        for k, v in d.items():
            tmp = list_of_dicts_to_lists(v, n_levels - 1)
            for x in tmp:
                output.append([k] + x)
    return output

def to_csv(values, header, outfile):
    with open(outfile, 'w', newline='') as csv_file:
        # pretty much straight from the docs @
        # https://docs.python.org/3.7/library/csv.html
        csv_writer = csv.writer(csv_file, quoting=csv.QUOTE_MINIMAL)
        csv_writer.writerow(header)
        for line in values:
            csv_writer.writerow(line)
    return True

rows = list_of_dicts_to_lists(js)
to_csv(rows, hdrs, 'tmp.csv')

Je vois maintenant que cette solution est super similaire à l'autre réponse ici... Mon mauvais.