JSON 到 Markdown table 格式

JSON to Markdown table formatting

我正在尝试构建一个函数,将 JSON 数据转换为列表,然后用作构建降价 tables 的基础。

我有第一个原型:

#!/usr/bin/env python3
import json

data = {
  "statistics": {
    "map": [
      {
        "map_name": "Location1",
        "nan": "loc1",
        "dont": "ignore this",
        "packets": "878607764338"
      },
      {
        "map_name": "Location2",
        "nan": "loc2",
        "dont": "ignore this",
        "packets": "67989088698"
      },
    ],
    "map-reset-time": "Thu Jan  6 05:59:47 2022\n"
  }
}
headers = ['Name', 'NaN', 'Packages']

def jsonToList(data):
    """adds the desired json fields"""
    # Wil be re-written to be more acceptant to different data fields. 
    json_obj = data

    ips = []
    for piece in json_obj['statistics']['map']:
        this_ip = [piece['map_name'], piece['nan'], piece['packets']]
        ips.append(this_ip)

    return ips 

def markdownTable(data, headers):
  # Find maximal length of all elements in list
    n = max(len(x) for l in data for x in l)
    # Print the rows
    headerLength = len(headers)
  
    # expected "|        Name|         NaN|    Packages|"
    for i in range(len(headers)):
      # Takes the max number of characters and subtracts the length of the header word
      hn = n - len(headers[i])
      # Prints | [space based on row above][header word]
      print("|" + " " * hn + f"{headers[i]}", end='')
      # If last run is meet add ending pipe
      if i == headerLength-1:
        print("|") # End pipe for headers

        # expected |--------|--------|--------|
        print("|", end='') # Start pipe for sep row
        for i in   range(len(headers)):
          print ("-" *n + "|", end='')

        # seams to be adding an extra line however if its not there,
        # Location1 
        print("\n", end='') 
        
    dataLength = len(data)
    for row in data:
      for x in row:
        hn = n - len(x)
        print(f"|" + " " * hn + x, end='')
      print("|")
 

if __name__ == "__main__":
    da = jsonToList(data)
    markdownTable(da, headers)

此代码按预期输出 table 可用作 markdown。

|        Name|         NaN|    Packages|
|------------|------------|------------|
|   Location1|        loc1|878607764338|
|   Location2|        loc2| 67989088698|

我想知道是否有人对我目前正在使用 n = max(len(x) for l in data for x in l) 然后减去当前字符串的长度并将其放在末尾的单词(集中)的位置有什么好的想法输出,这对左对齐很有效,但如果想让它们居中,那就有问题了。

此外,如果有人在这是我的第一次尝试或直接从 JSON.

的方法之前构建了类似的功能,则非常感谢关于优化代码方法的一般反馈。

如果您可以自由使用 pandas,这非常简单。降价功能很容易获得。请参阅下面的示例。

import pandas
df = pandas.DataFrame.from_dict(data['statistics']['map']).rename(columns={'map_name':'Name', 'nan':'NaN', 'packets':'Packages'})
df.drop(['dont'], axis=1, inplace=True)
print(df.to_markdown(index=False,tablefmt='fancy_grid'))

这将提供如下输出:

╒═══════════╤═══════╤══════════════╕
│ Name      │ NaN   │     Packages │
╞═══════════╪═══════╪══════════════╡
│ Location1 │ loc1  │ 878607764338 │
├───────────┼───────┼──────────────┤
│ Location2 │ loc2  │  67989088698 │
╘═══════════╧═══════╧══════════════╛

您可以使用 tablefmt 参数来应用不同的样式,例如 psql, pipe

如果您希望在 Python 中对齐文本,可以使用一些 built-in 方法。

ljust、center 和 rjust

可以在 str 实例和 return 字符串上调用 ljust, center, and rjust 方法,该字符串使用给定的填充字符(space 填充到给定的长度默认)。

>>> s = 'foo'
>>> s.ljust(10)
'foo       '
>>> s.center(10)
'   foo    '
>>> s.rjust(10)
'       foo'
>>> # Use a different fill character
>>> s.center(11, '*')
'****foo****'

格式化字符串语法

或者,您可以使用 Format String Syntax(下面使用 f-strings 进行了演示)。如果您需要将填充的字符串与其他文本组合在一起,这些内容特别有用,因为可以包含在同一字符串中。

>>> f'{s:<10}'
'foo       '
>>> f'{s:^10}'
'   foo    '
>>> f'{s:>10}'
'       foo'
>>> # Pass in a length
>>> length = 11
>>> f'{s:^{length}}'
'    foo    '
>>> # Specify a fill character
>>> f'{s:*^11}'
'****foo****'

general feedback on ways to optimize the code is much appreciated

我可能会这样做:

data = {
  "statistics": {
    "map": [
      {
        "map_name": "Location1",
        "nan": "loc1",
        "dont": "ignore this",
        "packets": "878607764338"
      },
      {
        "map_name": "Location2",
        "nan": "loc2",
        "dont": "ignore this",
        "packets": "67989088698"
      },
    ],
    "map-reset-time": "Thu Jan  6 05:59:47 2022\n"
  }
}

header_map = {
    'Name': 'map_name',
    'NaN': 'nan',
    'Packages': 'packets'
}

def markdownTable(data):
    rows = []
    length = max(len(v) for d in data['statistics']['map'] for k, v in d.items() if k in header_map.values()) + 2
    # build header
    rows.append('|'.join(s.center(length) for s in header_map.keys()))
    rows.append('|'.join('-' * length for x in range(len(header_map))))
    # build body
    for item in data['statistics']['map']:
        rows.append('|'.join(v.center(length) for k, v in item.items() if k in header_map.values()))
    # Print rows
    for row in rows:
        print(f'|{row}|')

if __name__ == "__main__":
    markdownTable(data)

请注意,无需使用 jsonToList 函数重组数据。只需遍历现有数据结构即可。此外,我创建了一个 header_map 字典,它将 table headers 映射到源数据中的键。在代码中,只需根据需要调用 header_map.keys()header_map.values()

作为一般 运行,我尽量避免使用 + 进行字符串组合,除非绝对必要。因此,我使用'|'.join()。当然,每次调用 join 都会传递该行的列表理解。

最后,我首先构建了一个 collection of rows,然后遍历它们并打印每一行(添加开始和结束管道)。我本可以打印第一轮,但是在 f-string 中包含整个连接列表理解不是很可读。例如:

print(f"|{'|'.join(s.center(length) for s in header_map.keys())}|")

此外,在第二个循环中,我只需要定义一次打开和关闭管道的格式,这更干(不要重复自己)。回想起来,出于同样的原因,我也可以在第二次迭代中完成 join。这样做的另一个好处是让行保存原始数据,如果需要,可以对其进行额外的处理。

输出结果如下:

|     Name     |     NaN      |   Packages   |
|--------------|--------------|--------------|
|  Location1   |     loc1     | 878607764338 |
|  Location2   |     loc2     | 67989088698  |

请注意,在 center 的情况下,奇数个字符会导致右偏一个字符。因此,Name 列和 Packages 列中的最后一个值似乎偏离中心半个字符。可能,数字应该是 right-justified,这会使代码复杂化。