Skip to content

How to Convert Folder Structure to JSON in Python

Learn how to recursively convert directory trees into structured JSON using Python's pathlib for file explorers, documentation, or backup manifests with metadata, filtering, and depth control.

Creating a JSON representation of directory trees is useful for file explorers, documentation generators, and backup manifests. Python's pathlib module makes this task straightforward.

Basic Recursive Conversion

Use depth-first traversal to build a nested dictionary structure:

import json
from pathlib import Path

def dir_to_dict(path):
    """Convert directory tree to nested dictionary."""
    p = Path(path)
    node = {"name": p.name, "type": "directory", "children": []}
    
    try:
        for item in sorted(p.iterdir()):
            if item.is_dir():
                node["children"].append(dir_to_dict(item))
            else:
                node["children"].append({"name": item.name, "type": "file"})
    except PermissionError:
        node["error"] = "Access Denied"
    
    return node

tree = dir_to_dict("./my_project")

print(json.dumps(tree, indent=2))

with open("structure.json", "w") as f:
    json.dump(tree, f, indent=2)

Example Output

{
  "name": "my_project",
  "type": "directory",
  "children": [
    {"name": "main.py", "type": "file"},
    {
      "name": "src",
      "type": "directory",
      "children": [
        {"name": "utils.py", "type": "file"},
        {"name": "config.py", "type": "file"}
      ]
    }
  ]
}

Adding File Metadata

Include size, modification time, and extension for richer output:

import json
from pathlib import Path
from datetime import datetime

def dir_to_dict_detailed(path):
    """Convert directory tree with file metadata."""
    p = Path(path)
    
    if p.is_file():
        stat = p.stat()
        return {
            "name": p.name,
            "type": "file",
            "extension": p.suffix,
            "size": stat.st_size,
            "modified": datetime.fromtimestamp(stat.st_mtime).isoformat()
        }
    
    node = {
        "name": p.name,
        "type": "directory",
        "children": []
    }
    
    try:
        for item in sorted(p.iterdir()):
            node["children"].append(dir_to_dict_detailed(item))
    except PermissionError:
        node["error"] = "Access Denied"
    
    return node

tree = dir_to_dict_detailed("./my_project")
print(json.dumps(tree, indent=2))

Output with metadata:

{
  "name": "my_project",
  "type": "directory",
  "children": [
    {
      "name": "main.py",
      "type": "file",
      "extension": ".py",
      "size": 1024,
      "modified": "2024-01-15T09:30:22"
    }
  ]
}

Filtering Files and Directories

Exclude certain patterns like hidden files or specific directories:

from pathlib import Path
import json

def dir_to_dict_filtered(path, exclude_patterns=None):
    """Convert directory tree, excluding specified patterns."""
    exclude_patterns = exclude_patterns or []
    p = Path(path)
    
    def should_exclude(item):
        name = item.name
        for pattern in exclude_patterns:
            if pattern.startswith('.') and name.startswith('.'):
                return True
            if name == pattern:
                return True
            if item.match(pattern):
                return True
        return False
    
    node = {"name": p.name, "type": "directory", "children": []}
    
    try:
        for item in sorted(p.iterdir()):
            if should_exclude(item):
                continue
            
            if item.is_dir():
                node["children"].append(dir_to_dict_filtered(item, exclude_patterns))
            else:
                node["children"].append({"name": item.name, "type": "file"})
    except PermissionError:
        node["error"] = "Access Denied"
    
    return node

tree = dir_to_dict_filtered(
    "./my_project",
    exclude_patterns=[".*", "__pycache__", "node_modules", "*.pyc"]
)
print(json.dumps(tree, indent=2))

Limiting Depth

Prevent deep recursion for large directory trees:

from pathlib import Path
import json

def dir_to_dict_limited(path, max_depth=3, current_depth=0):
    """Convert directory tree with depth limit."""
    p = Path(path)
    node = {"name": p.name, "type": "directory", "children": []}
    
    if current_depth >= max_depth:
        node["truncated"] = True
        return node
    
    try:
        for item in sorted(p.iterdir()):
            if item.is_dir():
                child = dir_to_dict_limited(item, max_depth, current_depth + 1)
                node["children"].append(child)
            else:
                node["children"].append({"name": item.name, "type": "file"})
    except PermissionError:
        node["error"] = "Access Denied"
    
    return node

tree = dir_to_dict_limited("./", max_depth=2)
print(json.dumps(tree, indent=2))

Generating File Paths List

Sometimes a flat list of paths is more useful than nested structure:

from pathlib import Path
import json

def get_all_paths(path, relative=True):
    """Get flat list of all file paths."""
    p = Path(path)
    base = p if relative else Path()
    
    paths = []
    
    for item in p.rglob("*"):
        if item.is_file():
            path_str = str(item.relative_to(base)) if relative else str(item)
            paths.append(path_str)
    
    return sorted(paths)

files = get_all_paths("./my_project")
print(json.dumps(files, indent=2))

Output:

[
  "main.py",
  "src/config.py",
  "src/utils.py",
  "tests/test_main.py"
]

Complete Solution with Options

Combine all features into a flexible function:

from pathlib import Path
from datetime import datetime
import json

def directory_to_json(
    path,
    include_metadata=False,
    exclude_patterns=None,
    max_depth=None,
    output_file=None
):
    """Convert directory to JSON with configurable options."""
    exclude_patterns = exclude_patterns or []
    
    def should_exclude(item):
        return any(item.match(p) for p in exclude_patterns)
    
    def build_tree(p, depth=0):
        if max_depth is not None and depth > max_depth:
            return {"name": p.name, "type": "directory", "truncated": True}
        
        if p.is_file():
            node = {"name": p.name, "type": "file"}
            if include_metadata:
                stat = p.stat()
                node["size"] = stat.st_size
                node["modified"] = datetime.fromtimestamp(stat.st_mtime).isoformat()
            return node
        
        node = {"name": p.name, "type": "directory", "children": []}
        
        try:
            for item in sorted(p.iterdir()):
                if not should_exclude(item):
                    node["children"].append(build_tree(item, depth + 1))
        except PermissionError:
            node["error"] = "Access Denied"
        
        return node
    
    tree = build_tree(Path(path))
    
    if output_file:
        with open(output_file, "w") as f:
            json.dump(tree, f, indent=2)
    
    return tree

tree = directory_to_json(
    "./my_project",
    include_metadata=True,
    exclude_patterns=["__pycache__", "*.pyc", ".*"],
    max_depth=3,
    output_file="structure.json"
)
print(json.dumps(tree, indent=2))

Summary

Feature Implementation
Basic tree Recursive iterdir()
Metadata Add stat() info
Filtering Pattern matching before recursion
Depth limit Track and check depth parameter
Error handling Catch PermissionError

Use pathlib for cross-platform compatibility and json.dump() with indent=2 for readable output. Always handle permission errors to prevent crashes when traversing system directories.

Navigation