Markus
Markus

Reputation: 727

Create a JSON type nested dictionary from a list in Python

I wish to create a JSON type nested dictionary from a list of lists. The lists contained a full directory path, but I broke them into their individual components as I thought it would make the creation of the nested dictionaries easier.

An example list:

["root", "dir1", "file.txt"] 

The expected result:

{
    "type": "directory",
    "name": "root",
    "children": [
        {
            "type": "directory",
            "name": "dir1",
            "children": [
                {
                    "type": "file",
                    "name": "file.txt",
                }
            ]
        }
    ]
}      

I've tried using a recursive method but couldn't quite get there (new to recursive methods and my head continually spun out). Also tried an iterative method from an idea I found here (stack overflow) which inverted the list and build the dict backwards, which I kind of got to work, but was unable to solve one of the solution requirements, which is that the code can deal with duplication in parts of the directory paths as it iterates over the list of lists. For example following on from the last example, the next inputted list is this:-

["root", "dir1", "dir2", "file2.txt"]

and it need to build onto the JSON dictionary to produce this:-

{
    "type": "directory",
    "name": "root",
    "children": [
        {
            "type": "directory",
            "name": "dir1",
            "children": [
                {
                    "type": "file",
                    "name": "file.txt",
                }
                {
                    "type": "directory",
                    "name": "dir2",
                    "children": [
                        {
                            "type": "file",
                            "name": "file2.txt"
                        }
                    ]
                }
            ]
        }
    ]
} 

and so on with an unknown number of lists containing directory paths. Thanks.

Upvotes: 2

Views: 1808

Answers (3)

ggorlen
ggorlen

Reputation: 56865

Here's a naive recursive solution that simply walks through the tree structure, adding children as necessary, until the last element of path is reached (assumed to be a file).

import json


def path_to_json(path, root):
    if path:
        curr = path.pop(0)

        if not root:
            root["type"] = "file"
            root["name"] = curr

            if path:
                root["children"] = [{}]
                root["type"] = "directory"
                path_to_json(path, root["children"][0])
        elif path:
            try:
                i = [x["name"] for x in root["children"]].index(path[0])
                path_to_json(path, root["children"][i])
            except ValueError:        
                root["children"].append({})
                path_to_json(path, root["children"][-1])

    return root


if __name__ == "__main__":
    paths = [["root", "dir1", "file.txt"], 
             ["root", "dir1", "dir2", "file2.txt"]]
    result = {}
    print(json.dumps([path_to_json(x, result) for x in paths][0], indent=4))

Output:

{
    "type": "directory",
    "name": "root",
    "children": [
        {
            "type": "directory",
            "name": "dir1",
            "children": [
                {
                    "type": "file",
                    "name": "file.txt"
                },
                {
                    "type": "directory",
                    "name": "dir2",
                    "children": [
                        {
                            "type": "file",
                            "name": "file2.txt"
                        }
                    ]
                }
            ]
        }
    ]
}

Try it!

Upvotes: 1

slider
slider

Reputation: 12990

A recursive solution with itertools.groupby is as follows (assuming all paths are absolute paths). The idea is to group paths by the first element in the path list. This groups similar directory roots together, allowing us to call the function recursively on that group.

Also note that file names cannot be duplicated in a directory, so all files will be grouped as single element lists by groupby:

from itertools import groupby
from operator import itemgetter

def build_dict(paths):
    if len(paths) == 1 and len(paths[0]) == 1:
        return {"type": "file", "name": paths[0][0]}
    dirname = paths[0][0]
    d = {"type": "directory", "name": dirname, "children": []}
    for k, g in groupby(sorted([p[1:] for p in paths], key=itemgetter(0)),
                        key=itemgetter(0)):
        d["children"].append(build_dict(list(g)))
    return d    

paths = [["root", "dir1", "file.txt"], ["root", "dir1", "dir2", "file2.txt"]]
print(build_dict(paths))

Output

{
  "type": "directory",
  "name": "root",
  "children": [
    {
      "type": "directory",
      "name": "dir1",
      "children": [
        {
          "type": "directory",
          "name": "dir2",
          "children": [
            {
              "type": "file",
              "name": "file2.txt"
            }
          ]
        },
        {
          "type": "file",
          "name": "file.txt"
        }
      ]
    }
  ]
}

Upvotes: 1

aydow
aydow

Reputation: 3801

Given not much detail has been provided, here is a solution that uses a reference to enter each nested dict

In [537]: structure = ["root", "dir1", "dir2", "file2.txt"]

In [538]: d = {}

# Create a reference to the current dict
In [541]: curr = d

In [542]: for i, s in enumerate(structure):
     ...:     curr['name'] = s
     ...:     if i != len(structure) - 1:
     ...:         curr['type'] = 'directory'
     ...:         curr['children'] = {}
     ...:         curr = curr['children']          # New reference is the child dict
     ...:     else:
     ...:         curr['type'] = 'file'
     ...:

In [544]: from pprint import pprint

In [545]: pprint(d)
{'children': {'children': {'children': {'name': 'file2.txt', 'type': 'file'},
                           'name': 'dir2',
                           'type': 'directory'},
              'name': 'dir1',
              'type': 'directory'},
 'name': 'root',
 'type': 'directory'}

I don't know if this will work for all of your questions as the spec isn't very detailed

Upvotes: 0

Related Questions