dynojson 0.1.2

Marshall/unmarshall JSON to/from DynamoDB JSON format
Documentation
# dynojson

[![CI](https://github.com/cykruss/dynojson/actions/workflows/CI.yml/badge.svg)](https://github.com/cykruss/dynojson/actions/workflows/CI.yml)
[![PyPI](https://img.shields.io/pypi/v/dynojson)](https://pypi.org/project/dynojson/)
[![crates.io](https://img.shields.io/crates/v/dynojson)](https://crates.io/crates/dynojson)
[![Python](https://img.shields.io/pypi/pyversions/dynojson)](https://pypi.org/project/dynojson/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

**Marshall / unmarshall JSON to and from DynamoDB JSON format** — a fast Python library backed by Rust.

Convert between regular JSON and [DynamoDB JSON format](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.LowLevelAPI.html) on the terminal or in your Python code. Powered by a Rust core via [PyO3](https://pyo3.rs) for maximum performance.

- 🔥 Works on all platforms (Linux, macOS, Windows)
- 📄 Convert a file from a given file path
- ✏️ Convert a JSON string directly
- ⛓ Read from stdin — pipe in JSON from another command
- 🍰 Extract and convert only a subset of the JSON
- 🤝 Output can be piped or redirected to other commands
- 🧰 Integrate into your workflow with AWS DynamoDB CLI

## Installation

```sh
pip install dynojson
```

## Python API

All functions accept **JSON strings** or **Python dicts/lists** directly. The return type matches the input type.

```python
from dynojson import marshall, unmarshall, get_property

# ── With dicts (recommended) ──────────────────────────────────────

result = marshall({"name": "Alice", "age": 30})
# {"name": {"S": "Alice"}, "age": {"N": "30"}}  ← returns a dict

result = unmarshall({"name": {"S": "Alice"}, "age": {"N": "30"}})
# {"name": "Alice", "age": 30}  ← returns a dict

# ── With strings (also supported) ─────────────────────────────────

result = marshall('{"name": "Alice", "age": 30}')
# '{"name":{"S":"Alice"},"age":{"N":"30"}}'  ← returns a string

result = unmarshall('{"name":{"S":"Alice"},"age":{"N":"30"}}')
# '{"name":"Alice","age":30}'  ← returns a string

# ── Extract a property ────────────────────────────────────────────

subset = get_property({"Items": [{"type": {"S": "fruit"}}]}, "Items")
result = unmarshall(subset)
# [{"type": "fruit"}]
```

## CLI Usage

```sh
dynojson <command> [options] <json>

Commands:
  dynojson unmarshall, u    Convert DynamoDB JSON to regular JSON
  dynojson marshall, m      Convert regular JSON to DynamoDB JSON

Options:
  -g <path>                 Extract a property before converting (dot-separated, supports *)
  --version                 Show version
  --help                    Show help
```

### Unmarshall (DynamoDB JSON → regular JSON)

```sh
# From a JSON string
$ dynojson u '{"name":{"S":"Alice"},"age":{"N":"30"}}'
{"name":"Alice","age":30}

# From a file
$ dynojson u data.json

# From stdin
$ cat data.json | dynojson u -

# From AWS CLI
$ aws dynamodb get-item --table-name users --key '{"id":{"S":"1"}}' | dynojson u -g "Item" -
```

### Marshall (regular JSON → DynamoDB JSON)

```sh
# From a JSON string
$ dynojson m '{"name":"Alice","age":30}'
{"name":{"S":"Alice"},"age":{"N":"30"}}

# From a file
$ dynojson m data.json

# From stdin
$ echo '{"name":"Alice"}' | dynojson m -
```

### Extract a subset of JSON with `-g`

Use dot-notation to select a property. Supports numeric array indices and `*` to expand all array items.

```sh
# Get a specific property
$ dynojson m -g "fruits.0.benefits" food.json

# Expand all items
$ dynojson m -g "fruits.*.name" food.json

# With AWS CLI scan output
$ aws dynamodb scan --table-name food | dynojson u -g "Items" -
```

## DynamoDB type mapping

| JSON type | DynamoDB descriptor |
|-----------|---------------------|
| String    | `{"S": "…"}`       |
| Number    | `{"N": "…"}`       |
| Boolean   | `{"BOOL": …}`     |
| Null      | `{"NULL": true}`   |
| Array     | `{"L": […]}`      |
| Object    | `{"M": {…}}`      |

String sets (`SS`), number sets (`NS`), and binary types (`B`, `BS`) are also supported during unmarshalling.

## Benchmarks

The dict path is faster than the string path because it skips two JSON serialization round-trips (`json.dumps` + `json.loads` on the Python side).

| Payload | Marshall speedup | Unmarshall speedup |
|---------|------------------|--------------------|
| Small (1 item) | 1.28x | 1.15x |
| Medium (100 items) | 1.20x | 1.22x |
| Large (10,000 items) | 1.14x | 1.22x |

Run the benchmark yourself:

```sh
maturin develop --release
python benchmarks/bench_marshall.py
```

## Development

### Prerequisites

- [Rust]https://rustup.rs/ (stable)
- Python 3.10+
- [uv]https://docs.astral.sh/uv/ (recommended) or pip
- [maturin]https://www.maturin.rs/

### Setup

```sh
# Clone the repo
git clone https://github.com/cykruss/dynojson.git
cd dynojson

# Create a virtual environment
uv venv
source .venv/bin/activate

# Install build and test dependencies
uv pip install maturin pytest

# Build and install in development mode
maturin develop

# Run Rust tests
cargo test --lib

# Run Python tests
pytest tests/ -v
```

### Project structure

```
dynojson/
├── Cargo.toml              # Rust package manifest
├── pyproject.toml           # Python package manifest (maturin build)
├── README.md
├── LICENSE
├── CONTRIBUTING.md
├── src/
│   ├── lib.rs               # PyO3 module + re-exports
│   ├── error.rs             # Custom error types (thiserror)
│   ├── marshall.rs          # Regular JSON → DynamoDB JSON
│   ├── unmarshall.rs        # DynamoDB JSON → regular JSON
│   └── property.rs          # Dot-path property extraction
├── python/
│   └── dynojson/
│       ├── __init__.py      # Public Python API
│       ├── _dynojson.pyi    # Type stubs
│       └── cli.py           # CLI entry point
├── benchmarks/
│   └── bench_marshall.py   # String vs dict path benchmark
└── tests/
    ├── test_marshall.py
    ├── test_unmarshall.py
    ├── test_property.py
    └── test_cli.py
```

## Acknowledgements

This project was inspired by [ddbjson](https://github.com/duartealexf/ddbjson) by [Alexandre Duarte](https://github.com/duartealexf), an excellent Node.js CLI tool for converting DynamoDB JSON. `dynojson` is a ground-up Rust rewrite with Python bindings, building on the same ideas.

## License

[MIT](LICENSE)