How to convert JSON data into a tree image? How to convert JSON data into a tree image? json json

How to convert JSON data into a tree image?


For a tree like this there's no need to use a library: you can generate the Graphviz DOT language statements directly. The only tricky part is extracting the tree edges from the JSON data. To do that, we first convert the JSON string back into a Python dict, and then parse that dict recursively.

If a name in the tree dict has no children it's a simple string, otherwise, it's a dict and we need to scan the items in its "children" list. Each (parent, child) pair we find gets appended to a global list edges.

This somewhat cryptic line:

name = next(iter(treedict.keys()))

gets a single key from treedict. This gives us the person's name, since that's the only key in treedict. In Python 2 we could do

name = treedict.keys()[0]

but the previous code works in both Python 2 and Python 3.

from __future__ import print_functionimport jsonimport sys# Tree in JSON formats = '{"Harry": {"children": ["Bill", {"Jane": {"children": [{"Diane": {"children": ["Mary"]}}, "Mark"]}}]}}'# Convert JSON tree to a Python dictdata = json.loads(s)# Convert back to JSON & print to stderr so we can verify that the tree is correct.print(json.dumps(data, indent=4), file=sys.stderr)# Extract tree edges from the dictedges = []def get_edges(treedict, parent=None):    name = next(iter(treedict.keys()))    if parent is not None:        edges.append((parent, name))    for item in treedict[name]["children"]:        if isinstance(item, dict):            get_edges(item, parent=name)        else:            edges.append((name, item))get_edges(data)# Dump edge list in Graphviz DOT formatprint('strict digraph tree {')for row in edges:    print('    {0} -> {1};'.format(*row))print('}')

stderr output

{    "Harry": {        "children": [            "Bill",            {                "Jane": {                    "children": [                        {                            "Diane": {                                "children": [                                    "Mary"                                ]                            }                        },                        "Mark"                    ]                }            }        ]    }}

stdout output

strict digraph tree {    Harry -> Bill;    Harry -> Jane;    Jane -> Diane;    Diane -> Mary;    Jane -> Mark;}

The code above runs on Python 2 & Python 3. It prints the JSON data to stderr so we can verify that it's correct. It then prints the Graphviz data to stdout so we can capture it to a file or pipe it directly to a Graphviz program. Eg, if the script is name "tree_to_graph.py", then you can do this in the command line to save the graph as a PNG file named "tree.png":

python tree_to_graph.py | dot -Tpng -otree.png

And here's the PNG output:

Tree made by Graphviz


Based on the answer of PM 2Ring I create a script which can be used via command line:

#!/usr/bin/env python# -*- coding: utf-8 -*-"""Convert a JSON to a graph."""from __future__ import print_functionimport jsonimport sysdef tree2graph(data, verbose=True):    """    Convert a JSON to a graph.    Run `dot -Tpng -otree.png`    Parameters    ----------    json_filepath : str        Path to a JSON file    out_dot_path : str        Path where the output dot file will be stored    Examples    --------    >>> s = {"Harry": [ "Bill", \                       {"Jane": [{"Diane": ["Mary", "Mark"]}]}]}    >>> tree2graph(s)    [('Harry', 'Bill'), ('Harry', 'Jane'), ('Jane', 'Diane'), ('Diane', 'Mary'), ('Diane', 'Mark')]    """    # Extract tree edges from the dict    edges = []    def get_edges(treedict, parent=None):        name = next(iter(treedict.keys()))        if parent is not None:            edges.append((parent, name))        for item in treedict[name]:            if isinstance(item, dict):                get_edges(item, parent=name)            elif isinstance(item, list):                for el in item:                    if isinstance(item, dict):                        edges.append((parent, item.keys()[0]))                        get_edges(item[item.keys()[0]])                    else:                        edges.append((parent, el))            else:                edges.append((name, item))    get_edges(data)    return edgesdef main(json_filepath, out_dot_path, lr=False, verbose=True):    """IO."""    # Read JSON    with open(json_filepath) as data_file:        data = json.load(data_file)    if verbose:        # Convert back to JSON & print to stderr so we can verfiy that the tree        # is correct.        print(json.dumps(data, indent=4), file=sys.stderr)    # Get edges    edges = tree2graph(data, verbose)    # Dump edge list in Graphviz DOT format    with open(out_dot_path, 'w') as f:        f.write('strict digraph tree {\n')        if lr:            f.write('rankdir="LR";\n')        for row in edges:            f.write('    "{0}" -> "{1}";\n'.format(*row))        f.write('}\n')def get_parser():    """Get parser object for tree2graph.py."""    from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter    parser = ArgumentParser(description=__doc__,                            formatter_class=ArgumentDefaultsHelpFormatter)    parser.add_argument("-i", "--input",                        dest="json_filepath",                        help="JSON FILE to read",                        metavar="FILE",                        required=True)    parser.add_argument("-o", "--output",                        dest="out_dot_path",                        help="DOT FILE to write",                        metavar="FILE",                        required=True)    return parserif __name__ == "__main__":    import doctest    doctest.testmod()    args = get_parser().parse_args()    main(args.json_filepath, args.out_dot_path, verbose=False)