# 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

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

# Regular JSON → DynamoDB JSON
result = marshall('{"name": "Alice", "age": 30}')
# '{"name":{"S":"Alice"},"age":{"N":"30"}}'

# DynamoDB JSON → Regular JSON
result = unmarshall('{"name":{"S":"Alice"},"age":{"N":"30"}}')
# '{"name":"Alice","age":30}'

# Extract a property before converting
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.

## Development

### Prerequisites

- [Rust](https://rustup.rs/) (stable)
- Python 3.9+
- [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
└── 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)
