Преобразование 4-уровневого вложенного файла JSON в 1-уровневый вложенный файл с Python

У меня есть 4-уровневый вложенный файл JSON ниже, который я хотел бы нормализовать до одноуровневого вложения:

Входной файл выглядит так:

{
    "@index": "40",
    "row": [
      {
        "column": [
          {
            "text": {
              "@fontName": "Times New Roman",
              "@fontSize": "12.0",
              "@x": "85.10",
              "@y": "663.12",
              "@width": "250.01",
              "@height": "12.00",
              "#text": "text 1"
            }
          }
        ]
      },
      {
        "column": [
          {
            "text": {
              "@fontName": "Times New Roman",
              "@fontSize": "8.0",
              "@x": "121.10",
              "@y": "675.36",
              "@width": "348.98",
              "@height": "8.04",
              "#text": "text 2"
            }
          },
          {
            "text": {
              "@fontName": "Times New Roman",
              "@fontSize": "12.0",
              "@x": "473.30",
              "@y": "676.92",
              "@width": "42.47",
              "@height": "12.00",
              "#text": "text 3"
            }
          }
        ]
      },
      {
        "column": [
          {
            "text": {
              "@fontName": "Times New Roman",
              "@fontSize": "12.0",
              "@x": "85.10",
              "@y": "690.72",
              "@width": "433.61",
              "@height": "12.00",
              "#text": "text 4"
            }
          }
        ]
      }
    ]
  }

Желаемый результат выглядит следующим образом:

{
    "@index": "40",
    "row": [
          {
              "@fontName": "Times New Roman",
              "@fontSize": "12.0",
              "@x": "85.10",
              "@y": "663.12",
              "@width": "250.01",
              "@height": "12.00",
              "#text": "Text 1"
          },
          {
              "@fontName": "Times New Roman",
              "@fontSize": "8.0",
              "@x": "121.10",
              "@y": "675.36",
              "@width": "348.98",
              "@height": "8.04",
              "#text": "Text 2"
          },
          {
              "@fontName": "Times New Roman",
              "@fontSize": "12.0",
              "@x": "473.30",
              "@y": "676.92",
              "@width": "42.47",
              "@height": "12.00",
              "#text": "Text 3"
          },
          {
              "@fontName": "Times New Roman",
              "@fontSize": "12.0",
              "@x": "85.10",
              "@y": "690.72",
              "@width": "433.61",
              "@height": "12.00",
              "#text": "Text 4"
          }
    ]
  }

Код, который у меня есть до сих пор, использует pandas ниже, но я не знаю, как продолжить нормализацию до одного уровня.

import json 
import pandas as pd 
from pandas.io.json import json_normalize #package for flattening json in pandas df

#load json object
with open('D:\Files\JSON\4Level.json') as f:
    d = json.load(f)

nycphil = json_normalize(d['row'])
print (nycphil.head(4))

Это текущий вывод в таблице, где показано, что column является вложенным элементом:

                                            column
0  [{'text': {'@fontName': 'Times New Roman', '@f...
1  [{'text': {'@fontName': 'Times New Roman', '@f...
2  [{'text': {'@fontName': 'Times New Roman', '@f...

Печать с одноуровневым вложением будет:

text.#text   text.@fontName text.@fontSize   ...   text.@width text.@x text.@y
0     Text 1  Times New Roman           12.0   ...        250.01   85.10  663.12
1     Text 2  Times New Roman            8.0   ...        348.98  121.10  675.36
2     Text 3  Times New Roman           12.0   ...         42.47  473.30  676.92
3     Text 4  Times New Roman           12.0   ...        433.61   85.10  690.72

Сравнение ввода/вывода выглядит следующим образом:

введите здесь описание изображения

Может быть, кто-то мог бы помочь мне с этим. Спасибо за любую помощь.

ОБНОВЛЕНИЕ

Чтобы сделать небольшой пример из первого примера ввода, который я показал, я удалил некоторые элементы, которые кажутся необходимыми для работы ваших скриптов. Итак, теперь я показываю точно такую ​​же структуру, как и реальный файл, но с этим вводом ваши скрипты не работают. Я думаю, что их нужно немного подправить, но я пытался и не знаю, как их изменить, чтобы получить тот же результат с этим новым вводом. Может быть, вы можете мне помочь и извините за то, что не показывали правильный ввод с самого начала.

{
   "document":{
      "page":[
         {
            "@index":"0",
            "image":{
               "@data":"ABC",
               "@format":"png",
               "@height":"620.00",
               "@type":"base64encoded",
               "@width":"450.00",
               "@x":"85.00",
               "@y":"85.00"
            }
         },
         {
            "@index":"1",
            "row":[
               {
                  "column":[
                     {
                        "text":""
                     },
                     {
                        "text":{
                           "#text":"Text1",
                           "@fontName":"Arial",
                           "@fontSize":"12.0",
                           "@height":"12.00",
                           "@width":"71.04",
                           "@x":"121.10",
                           "@y":"83.42"
                        }
                     }
                  ]
               },
               {
                  "column":[
                     {
                        "text":""
                     },
                     {
                        "text":{
                           "#text":"Text2",
                           "@fontName":"Arial",
                           "@fontSize":"12.0",
                           "@height":"12.00",
                           "@width":"101.07",
                           "@x":"121.10",
                           "@y":"124.82"
                        }
                     }
                  ]
               }
            ]
         },
         {
            "@index":"2",
            "row":[
               {
                  "column":{
                     "text":{
                        "#text":"Text3",
                        "@fontName":"Arial",
                        "@fontSize":"12.0",
                        "@height":"12.00",
                        "@width":"363.44",
                        "@x":"85.10",
                        "@y":"69.62"
                     }
                  }
               },
               {
                  "column":{
                     "text":{
                        "#text":"Text4",
                        "@fontName":"Arial",
                        "@fontSize":"12.0",
                        "@height":"12.00",
                        "@width":"382.36",
                        "@x":"85.10",
                        "@y":"83.42"
                     }
                  }
               },
               {
                  "column":{
                     "text":{
                        "#text":"Text5",
                        "@fontName":"Arial",
                        "@fontSize":"12.0",
                        "@height":"12.00",
                        "@width":"435.05",
                        "@x":"85.10",
                        "@y":"97.22"
                     }
                  }
               }
            ]
         },
         {
            "@index":"3"
         }
      ]
   }
}

person Ger Cas    schedule 28.05.2019    source источник
comment
Проверьте flatten_json() из здесь. Я уже проверил. Это работает.   -  person shaik moeed    schedule 28.05.2019
comment
Возможный дубликат Python сглаживает многоуровневый JSON   -  person shaik moeed    schedule 28.05.2019


Ответы (5)


Вы можете использовать понимание списка:

d = {'@index': '40', 'row': [{'column': [{'text': {'@fontName': 'Times New Roman', '@fontSize': '12.0', '@x': '85.10', '@y': '663.12', '@width': '250.01', '@height': '12.00', '#text': 'text 1'}}]}, {'column': [{'text': {'@fontName': 'Times New Roman', '@fontSize': '8.0', '@x': '121.10', '@y': '675.36', '@width': '348.98', '@height': '8.04', '#text': 'text 2'}}, {'text': {'@fontName': 'Times New Roman', '@fontSize': '12.0', '@x': '473.30', '@y': '676.92', '@width': '42.47', '@height': '12.00', '#text': 'text 3'}}]}, {'column': [{'text': {'@fontName': 'Times New Roman', '@fontSize': '12.0', '@x': '85.10', '@y': '690.72', '@width': '433.61', '@height': '12.00', '#text': 'text 4'}}]}]}
new_d = {**d, 'row':[c['text'] for b in d['row'] for c in b['column']]}

import json
print(json.dumps(new_d, indent=4))

Выход:

{
 "@index": "40",
 "row": [
     {
        "@fontName": "Times New Roman",
        "@fontSize": "12.0",
        "@x": "85.10",
        "@y": "663.12",
        "@width": "250.01",
        "@height": "12.00",
        "#text": "text 1"
     },
     {
        "@fontName": "Times New Roman",
        "@fontSize": "8.0",
        "@x": "121.10",
        "@y": "675.36",
        "@width": "348.98",
        "@height": "8.04",
        "#text": "text 2"
     },
     {
        "@fontName": "Times New Roman",
        "@fontSize": "12.0",
        "@x": "473.30",
        "@y": "676.92",
        "@width": "42.47",
        "@height": "12.00",
        "#text": "text 3"
     },
     {
        "@fontName": "Times New Roman",
        "@fontSize": "12.0",
        "@x": "85.10",
        "@y": "690.72",
        "@width": "433.61",
        "@height": "12.00",
        "#text": "text 4"
    }
  ]
}

Изменить: чтобы сгладить вложенную структуру, вы можете использовать рекурсию с генератором:

def flatten(d, t = ["image", "text"]):
   for a, b in d.items():
      if a in t:
         yield b
      elif isinstance(b, dict):
         yield from flatten(b)
      elif isinstance(b, list):
         for i in b:
            yield from flatten(i)


d = {'document': {'page': [{'@index': '0', 'image': {'@data': 'ABC', '@format': 'png', '@height': '620.00', '@type': 'base64encoded', '@width': '450.00', '@x': '85.00', '@y': '85.00'}}, {'@index': '1', 'row': [{'column': [{'text': ''}, {'text': {'#text': 'Text1', '@fontName': 'Arial', '@fontSize': '12.0', '@height': '12.00', '@width': '71.04', '@x': '121.10', '@y': '83.42'}}]}, {'column': [{'text': ''}, {'text': {'#text': 'Text2', '@fontName': 'Arial', '@fontSize': '12.0', '@height': '12.00', '@width': '101.07', '@x': '121.10', '@y': '124.82'}}]}]}, {'@index': '2', 'row': [{'column': {'text': {'#text': 'Text3', '@fontName': 'Arial', '@fontSize': '12.0', '@height': '12.00', '@width': '363.44', '@x': '85.10', '@y': '69.62'}}}, {'column': {'text': {'#text': 'Text4', '@fontName': 'Arial', '@fontSize': '12.0', '@height': '12.00', '@width': '382.36', '@x': '85.10', '@y': '83.42'}}}, {'column': {'text': {'#text': 'Text5', '@fontName': 'Arial', '@fontSize': '12.0', '@height': '12.00', '@width': '435.05', '@x': '85.10', '@y': '97.22'}}}]}, {'@index': '3'}]}}
print(json.dumps(list(filter(None, flatten(d))), indent=4))

Выход:

[
  {
    "@data": "ABC",
    "@format": "png",
    "@height": "620.00",
    "@type": "base64encoded",
    "@width": "450.00",
    "@x": "85.00",
    "@y": "85.00"
  },
  {
    "#text": "Text1",
    "@fontName": "Arial",
    "@fontSize": "12.0",
    "@height": "12.00",
    "@width": "71.04",
    "@x": "121.10",
    "@y": "83.42"
  },
  {
    "#text": "Text2",
    "@fontName": "Arial",
    "@fontSize": "12.0",
    "@height": "12.00",
    "@width": "101.07",
    "@x": "121.10",
    "@y": "124.82"
  },
  {
    "#text": "Text3",
    "@fontName": "Arial",
    "@fontSize": "12.0",
    "@height": "12.00",
    "@width": "363.44",
    "@x": "85.10",
    "@y": "69.62"
  },
  {
    "#text": "Text4",
    "@fontName": "Arial",
    "@fontSize": "12.0",
    "@height": "12.00",
    "@width": "382.36",
    "@x": "85.10",
    "@y": "83.42"
  },
  {
    "#text": "Text5",
    "@fontName": "Arial",
    "@fontSize": "12.0",
    "@height": "12.00",
    "@width": "435.05",
    "@x": "85.10",
    "@y": "97.22"
  }
]
person Ajax1234    schedule 28.05.2019
comment
Спасибо за помощь. Это работает для меня в Python3. - person Ger Cas; 28.05.2019
comment
@GerCas Рад помочь! - person Ajax1234; 28.05.2019
comment
Привет, Ajax1234, не могли бы вы увидеть новый ввод под моим ОБНОВЛЕНИЕМ. Я не смог изменить ваш скрипт, чтобы заставить его работать с этим вводом, который имеет ту же структуру, что и настоящий файл. Спасибо - person Ger Cas; 29.05.2019
comment
@GerCas Нет проблем. Каков ваш желаемый результат от вашего нового образца? Это все еще данные, связанные с row? - person Ajax1234; 29.05.2019
comment
Привет, Аякс, спасибо. Мне нужен только один уровень с одинаковыми блоками, между "text":{....} image":{...}, если это возможно. заранее спасибо - person Ger Cas; 29.05.2019
comment
@GerCas Спасибо за разъяснение. Пожалуйста, посмотрите мое недавнее редактирование. - person Ajax1234; 29.05.2019
comment
Аякс, спасибо большое. Работает просто идеально. Теперь нужно только распечатать в табличном виде. Я думаю, для этого я могу использовать панду с выводом вашего решения? - person Ger Cas; 29.05.2019
comment
@GerCas Да, определенно, вы можете использовать pd.DataFrame(result). - person Ajax1234; 29.05.2019

В качестве альтернативы json_normalize() вы также можете использовать понимание.:

my_dict["row"] = [{k: v for k, v in col_entry["text"].items()} for entry in my_dict["row"] for col_entry in entry["column"]]

Изменить: исправлен код для покрытия нескольких записей в каждом списке столбцов. Это, по общему признанию, приближается к болевому порогу с точки зрения вложенности понятий...

person JohnO    schedule 28.05.2019
comment
Привет, Джон. Я новичок в Python. В этом случае мне нужно загрузить файл Json в словарь? Если да, то как загрузить файл в диктофон? Спасибо - person Ger Cas; 28.05.2019
comment
json.loads(). Дополнительную информацию можно найти здесь docs.python.org/3/library/json.html< /а> - person JohnO; 28.05.2019
comment
Это извлечет только первый элемент списка столбцов. Вы получите 3 раздела шрифта вместо 4 на выходе. - person addmoss; 28.05.2019
comment
Ах, да, извините, я пропустил, что их было два на одном уровне. Код исправлю, когда вернусь домой. - person JohnO; 28.05.2019
comment
Привет, JohnO, не могли бы вы увидеть, пожалуйста, новый вход под моим ОБНОВЛЕНИЕМ. Я не смог изменить ваш скрипт, чтобы заставить его работать с этим вводом, который имеет ту же структуру, что и настоящий файл. Спасибо - person Ger Cas; 29.05.2019
comment
Честно говоря, я думаю, что это достигло такой сложности, что попытка решить ее с помощью понимания списка была бы грубым нарушением подсчета удобочитаемости. Итак, на данный момент я бы выбрал одно из других опубликованных решений. - person JohnO; 29.05.2019

Ниже приведен рабочий код:

(56336255.json - это образец данных, который вы опубликовали)

import json
import pprint

flat_data = dict()
with open('56336255.json') as f:
    data = json.load(f)
    for k, v in data.items():
        if k == '@index':
            flat_data[k] = data[k]
        else:
            flat_data[k] = []
            for row in v:
                for cell in row['column']:
                    flat_data[k].append(cell['text'])

    pprint.pprint(flat_data)

выход

{'@index': '40',
 'row': [{'#text': 'text 1',
          '@fontName': 'Times New Roman',
          '@fontSize': '12.0',
          '@height': '12.00',
          '@width': '250.01',
          '@x': '85.10',
          '@y': '663.12'},
         {'#text': 'text 2',
          '@fontName': 'Times New Roman',
          '@fontSize': '8.0',
          '@height': '8.04',
          '@width': '348.98',
          '@x': '121.10',
          '@y': '675.36'},
         {'#text': 'text 3',
          '@fontName': 'Times New Roman',
          '@fontSize': '12.0',
          '@height': '12.00',
          '@width': '42.47',
          '@x': '473.30',
          '@y': '676.92'},
         {'#text': 'text 4',
          '@fontName': 'Times New Roman',
          '@fontSize': '12.0',
          '@height': '12.00',
          '@width': '433.61',
          '@x': '85.10',
          '@y': '690.72'}]}
person balderman    schedule 28.05.2019

Это делает работу:

data = json.load(json_file)
flat = [ column['text'] for entry in data['row'] for column in entry['column'] ]

Полный рабочий пример:

import json
import sys
import os.path

def main(argv):

    #Load JSON
    current_folder = os.path.dirname(os.path.realpath(__file__))
    with open(current_folder + '\\input.json') as json_file:  
        data = json.load(json_file)

    #Flatten (using for loops)
    flat=[]
    for entry in data['row']:
        for column in entry['column']:
            flat.append(column['text'])

    # OR, Flatten the pythonic way (using list comprehension)
    # looks strange at first but notice
    #   1. we start with the item we want to keep in the list
    #   2. the loops order is the same, we just write them inline 
    flat2 = [ column['text'] for entry in data['row'] for column in entry['column'] ]


    #Format data for saving to JSON
    output = {}
    output['@index']=data['@index']
    output['row'] = flat #or flat2 

    #Save to JSON
    with open('flat.txt', 'w') as outfile:
        json.dump(output, outfile, indent=4)

if __name__ == "__main__":
   main(sys.argv[1:])

введите здесь описание изображения

person addmoss    schedule 28.05.2019

Попробуй это,

#!/usr/bin/python
# -*- coding: utf-8 -*-


def flatten_json(y):
    out = {}

    def flatten(x, name=''):
        if type(x) is dict:
            for a in x:
                flatten(x[a], name + a + '_')
        elif type(x) is list:
            i = 0
            for a in x:
                flatten(a, name + str(i) + '_')
                i += 1
        else:
            out[name[:-1]] = x

    flatten(y)
    return out


expected_output = flatten_json(input_data)  # This will convert
person shaik moeed    schedule 28.05.2019
comment
@GerCas Пожалуйста, проверьте сейчас и подтвердите. - person shaik moeed; 28.05.2019
comment
Та же проблема. Я пробовал с консолью Python напрямую и как скрипт python script.py, в этом случае я получаю только вывод {'': 'input.json'} - person Ger Cas; 29.05.2019
comment
@GerCas Я снова проверил на своей машине ваши input_data, все работает. - person shaik moeed; 29.05.2019
comment
Я не уверен, что происходит, я заменяю input_data на «input.json» и получаю этот отпечаток. Кроме того, возможно, вы можете увидеть мое ОБНОВЛЕНИЕ. - person Ger Cas; 29.05.2019