Metadata-Version: 2.1
Name: rugged
Version: 0.1.0
Summary: ASGI middleware to unflatten JSON request bodies
Project-URL: Homepage, https://github.com/rmasters/rugged
Project-URL: Issues, https://github.com/rmasters/rugged/issues
Author-email: Ross Masters <ross@rossmasters.com>
License-File: LICENSE.md
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.12
Requires-Dist: starlette<=1
Description-Content-Type: text/markdown

# Rugged

An ASGI middleware to unflatten JSON request keys into nested structures.

This behaviour might be familiar [if you've used PHP before][php-form-arrays].

[php-form-arrays]: https://www.php.net/manual/en/faq.html.php#faq.html.arrays

## Example use case

The inspiration for this middleware came from a web app with forms that add
variable numbers of fields dynamically. This web app uses htmx to submit forms
as AJAX requests, and the hx-json-enc extension to send form data as JSON.

For example, with a form like so:

```html
<form method="post" action="/invite" hx-boost="true" hx-ext="json-enc">
    <h1>Invite users</h1>
    <input type="text" name="emails[0]">
    <input type="text" name="emails[1]">
    <input type="text" name="emails[2]">

    <button onclick="addEmailInput()">Add email</button>
    <button type="submit">Send invites</button>
</form>
```

The request body could look like this:

```json
{
    "emails[0]": "foo@example.com",
    "emails[1]": "bar@example.com",
    "emails[2]": "baz@example.com"
}
```

Using the middleware, the request body is unflattened into:

```json
{
    "emails": ["foo@example.com", "bar@example.com", "baz@example.com"]
}
```

This makes inputs easier to handle in a FastAPI application using Pydantic:

```python
class InviteUsers(BaseModel):
    emails: list[str]


@app.post("/invite")
async def invite_users(invite: InviteUsers):
    send_invites(invite.emails)
```

Similarly, this can be nested further:

<table>
<tr>
<td>
```json
{
    "order_id": "1234",
    "product[0][name]": "Product 1",
    "product[0][price]": 100,
    "product[1][name]": "Product 2",
    "product[1][price]": 200,
    "product[2][name]": "Product 3",
    "product[2][price]": 300
}
```
</td>
<td>
```json
{
    "order_id": "1234",
    "product": [
        {"name": "Product 1", "price": 100},
        {"name": "Product 2", "price": 200},
        {"name": "Product 3", "price": 300}
    ]
}
```
</td>
</tr>
</table>

A canonical set of supported inputs can be found by reading the [unit tests][tests]
for the `unflatten()` function.

[tests]: tests/test_unflatteners.py

## Roadmap / future development ideas

-   Support for un-indexed arrays, e.g. `product[]`
-   Support custom delimiters/formats other than square brackets, e.g. `product.0.name`
-   Support alternative JSON decoders
-   Support for re-flattening dictionaries, if useful
-   Investigate whether this middleware can be used with x-www-form-urlencoded and
    multipart/form-data bodies
-   Some performance optimisation - can we avoid regex?
-   Establish minimum Starlette version required
-   Establish minimum Python version required

## License

This project is licensed under the terms of the [MIT license](./LICENSE.md).

