Metadata-Version: 2.4
Name: vcti-fileloader-json
Version: 1.0.2
Summary: Stdlib-backed JSON file loader for the vcti-fileloader framework
Author: Visual Collaboration Technologies Inc.
Requires-Python: <3.15,>=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: vcti-fileloader>=5.1.0
Requires-Dist: vcti-tree>=1.0.0
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: pytest-cov; extra == "test"
Provides-Extra: lint
Requires-Dist: ruff; extra == "lint"
Provides-Extra: typecheck
Requires-Dist: mypy; extra == "typecheck"
Dynamic: license-file

# FileLoader JSON

JSON file loader for the vcti-fileloader framework — parses with the
stdlib `json` module and attaches the parsed object as a single
locked subtree under a caller-supplied parent.

## Overview

`vcti-fileloader-json` is a JSON loader plugin for the `vcti-fileloader`
framework. It implements the `Loader` protocol: `populate(handle, tree,
parent)` parses a JSON file with the Python standard library `json`
module and attaches the parsed value (a dict, list, scalar, or
`None`) as the `.data` of a single child under a one-node subtree.
The returned subtree is structure-locked and payload-locked.

The package depends only on `vcti-fileloader` and `vcti-tree`; the
JSON parser is the Python stdlib. JSON files have no native attribute
mechanism, so the loader stamps no `file_attributes` — callers that
want to enrich with file paths, tags, or derived metadata should use
the `before_lock` hook on `populate`.

## Installation

```bash
pip install vcti-fileloader-json>=1.0.2
```

### In `pyproject.toml` dependencies

```toml
dependencies = [
    "vcti-fileloader-json>=1.0.2",
]
```

---

## Quick Start

```python
from pathlib import Path

from vcti.fileloader.core import DataNode
from vcti.fileloader.json import JsonLoader
from vcti.tree import DictTree

loader = JsonLoader()
tree: DictTree[DataNode] = DictTree(DataNode())

with loader.open(Path("config.json")) as handle:
    subtree_root = loader.populate(handle, tree, tree.root_handle)

# The parsed object lives on the single child's `.data`.
[child_handle] = tree.children(subtree_root)
parsed = tree.payload(child_handle).data
print(parsed)               # e.g. {"name": "demo", "values": [1, 2, 3]}
```

### With a `before_lock` hook

JSON has no native attribute mechanism, so all attribute stamping
happens via the hook. A typical pattern is to attach the file path:

```python
path = Path("config.json")

def stamp_file_path(tree, root):
    tree.payload(root).enricher_attributes["file_path"] = str(path)

with loader.open(path) as handle:
    root = loader.populate(handle, tree, tree.root_handle, before_lock=stamp_file_path)
```

For rule-driven enrichment, pair the hook with
[`vcti-attribute-enricher`](https://github.com/vcollab/vcti-python-attribute-enricher).

---

## What the subtree looks like

For any JSON file (whether it parses to a dict, list, scalar, or
`null`), the subtree shape is uniform:

| Node          | name   | Payload    | data                       | file_attributes |
|---------------|--------|------------|----------------------------|-----------------|
| subtree root  | `None` | `DataNode` | `None`                     | `{}`            |
| child         | `None` | `DataNode` | the parsed object/value    | `{}`            |

The root's payload is an empty `DataNode` (`data` is `None`, no
attributes) — it exists to give callers a single locked anchor
regardless of the parsed value's type. The child carries the parsed
value as `.data`.

---

## API

### JsonLoader

| Method | Description |
|--------|-------------|
| `load(path, **options)` | Parse the JSON file; return a parser handle |
| `open(path, **options)` | Context manager — load and auto-unload |
| `populate(handle, tree, parent, *, before_lock=None, **options)` | Attach the parsed object as a single locked subtree |
| `unload(handle)` | No-op (parsed result is in-memory; nothing to release) |
| `can_load(path)` | Check extension (`.json`) |

### Helpers

| | Description |
|---|---|
| `get_loader_descriptor()` | Create `LoaderDescriptor` for registry |
| `JsonValidator` | Verify the stdlib `json` module is importable |
| `JsonSetup` | No-op setup |

---

## Error Handling

```python
from vcti.fileloader.core import (
    LoadError,
    UnsupportedFormatError,
    TreeAttachmentError,
)

try:
    with loader.open(Path("config.json")) as handle:
        subtree_root = loader.populate(handle, tree, tree.root_handle)
except FileNotFoundError:
    ...
except UnsupportedFormatError:
    # File extension is not .json
    ...
except LoadError:
    # File could not be opened or contained invalid JSON
    ...
except TreeAttachmentError:
    # parent is missing, deleted, or structure-locked in `tree`
    ...
```

If `populate` fails partway (an invalid JSON token, or a `before_lock`
exception), the partial subtree is removed before the exception
propagates — callers never see a half-built subtree.

---

## What this package does NOT do

- **Streaming parses.** `json.load` reads the whole file at once.
  Use a streaming JSON parser (e.g. `ijson`) and a custom loader for
  very large files.
- **Schema validation.** The loader accepts any well-formed JSON.
  Validation belongs in a `before_lock` hook or downstream pass.
- **Expand JSON into a multi-node tree.** The parsed object lands on
  a single leaf as a Python object. If you want each JSON key as a
  separate tree node, that is a separate (future) loader; this one
  is deliberately one-node.

---

## Dependencies

- [vcti-fileloader](https://pypi.org/project/vcti-fileloader/) (>=5.1.0) — `Loader` protocol, `SubtreeBuilder`, `DataNode` (import from `vcti.fileloader.core`)
- [vcti-tree](https://pypi.org/project/vcti-tree/) (>=1.0.0) — `LockableTree` protocol

The JSON parser is `json` from the Python standard library — no
third-party dependency.
