Metadata-Version: 2.4
Name: natizon
Version: 0.3.0
Summary: A pure Python, native ZON (Zig Object Notation) parser and serializer.
Keywords: zig,ZON,Zig Object Notation,parser,serializer
Author-email: Eric Joldasov <bratishkaerik@landless-city.net>
Requires-Python: >= 3.12
Description-Content-Type: text/markdown
License-Expression: Apache-2.0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Text Processing :: General
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
License-File: LICENSES/Apache-2.0.txt
License-File: LICENSES/CC0-1.0.txt
Requires-Dist: lark>=1.3.1
Project-URL: Homepage, https://github.com/BratishkaErik/natizon
Project-URL: Issues, https://github.com/BratishkaErik/natizon/issues
Project-URL: Repository, https://github.com/BratishkaErik/natizon

# natizon

`natizon` (short for "native-ZON") is a pure Python parser and serializer for
[ZON (Zig Object Notation)](https://ziglang.org/documentation/0.16.0/std/#std.zon). Built on top of
[Lark](https://github.com/lark-parser/lark), it provides a familiar, `json`-like interface for decoding ZON strings
directly into Python data structures, and for encoding Python data structures back into ZON text.
It relies strictly on standard Python types, without AST wrappers and so on.

> [!NOTE]
> `natizon` is slightly more lenient than the official `std.zon` parser. This flexibility is intentional,
> making it easier to consume and work with data in Python environments.

## Installation

```shell
pip install natizon
```

or:

```shell
uv add natizon
```

## Usage

`natizon` exposes `loads()` and `dumps()` functions that work similarly to the standard library's `json` module.

> [!TIP]
> **Looking to parse `build.zig.zon`?**
>
> Check out this Python script for
> [Parsing and validating build.zig.zon](https://gist.github.com/BratishkaErik/8ec576586ebca98f222e8e7c2bf3d98b)
> to see how to:
> * load Zig 0.16 package metadata using `natizon`,
> * validate fields with `Pydantic`,
> * and print a pretty JSON dump using `Rich`.

### Parsing

```python
from natizon import loads

zon_data = r"""
.{
    .package_name = "network_tools",
    .version = "2.1.0",
    .supported_platforms = .{ .linux, .macos, .windows },
    .dependencies = .{
        .lib_a = .{ .url = "https://server.com/a.tar" },
        .lib_b = .{ .path = "../local_b" }
    }
}
"""

# Parses directly into standard Python dicts and lists
parsed_data = loads(zon_data)

print(parsed_data["package_name"])  # "network_tools"
print(parsed_data["supported_platforms"])  # ["linux", "macos", "windows"]
```

### Serialization

You can serialize standard Python objects back into ZON text using `dumps()`:

```python
from natizon import dumps

data = {
    "package_name": "network_tools",
    "version": "2.1.0",
    "supported_platforms": ["linux", "macos", "windows"],
}

zon_string = dumps(data, indent=2)
print(zon_string)
```

Output:

```zig
.{
  .package_name = "network_tools",
  .version = "2.1.0",
  .supported_platforms = .{
    "linux",
    "macos",
    "windows",
  },
}
```

> [!NOTE]
> While `loads()` and `dumps()` are designed to be compatible, some ZON-specific constructs do not roundtrip exactly:
> * **Enum literals** (e.g., `.linux`) are parsed into Python strings and will serialize back as quoted strings
    (`"linux"`).
> * **Char literals** (e.g., `'a'`) are parsed into Python integers and will serialize back as integers (`97`).
> * **Empty arrays** (`[]`) serialize to `.{}`, which `loads()` parses as an empty dict by default. Use
    `EmptyContainerMode.SEQUENCE` if you need them to parse back as lists/tuples.

## ZON to Python Type Mapping

When you pass a ZON string to `natizon.loads()`, the parser automatically converts ZON primitives and structures into
their closest Python types.

Here's breakdown:

### Primitives and Literals

| ZON Type              | ZON Example          | Python Type | Python Value     | Notes                                                          |
|:----------------------|:---------------------|:------------|:-----------------|:---------------------------------------------------------------|
| **Null**              | `null`               | `NoneType`  | `None`           |                                                                |
| **Boolean**           | `true`, `false`      | `bool`      | `True`, `False`  |                                                                |
| **Integer**           | `42`, `0x2A`         | `int`       | `42`             |                                                                |
| **Float**             | `3.14`, `inf`, `nan` | `float`     | `3.14`           | Supports ZON-specific keywords: `nan` and `inf`.               |
| **Char Literal**      | `'a'`                | `int`       | `97`             | Evaluates to the integer Unicode code point.                   |
| **String**            | `"Hello"`            | `str`       | `"Hello"`        | Handles standard escapes and Unicode `\u{...}`.                |
| **Multiline String**  | `\\Line 1`           | `str`       | `"Line 1"`       | Strips the `\\` prefix and joins multiple lines with newlines. |
| **Enum Literal**      | `.linux`             | `str`       | `"linux"`        | Parsed simply as strings.                                      |
| **Quoted Identifier** | `.@"complex-key!"`   | `str`       | `"complex-key!"` | Slices off the `@` prefix and evaluates the string.            |

### Structures and Containers

| ZON Type            | ZON Example    | Python Type | Python Value | Notes                                                          |
|:--------------------|:---------------|:------------|:-------------|:---------------------------------------------------------------|
| **Array**           | `.{ 1, 2, 3 }` | `list`      | `[1, 2, 3]`  | Parses as a `tuple` if `use_tuples=True` is set.               |
| **Struct**          | `.{ .x = 1 }`  | `dict`      | `{"x": 1}`   | Raises `ValueError` if duplicate field names are encountered.  |
| **Empty Container** | `.{}`          | `dict`      | `{}`         | Parses using Array rules if `empty_mode` is set to `SEQUENCE`. |

### Configuration

* **`use_tuples`** (`bool`, default `False`): If `True`, parses ZON arrays (e.g., `.{ 1, 2, 3 }`) as Python `tuple`s
  instead of `list`s.
* **`empty_mode`** (`EmptyContainerMode`, default `EmptyContainerMode.DICT`): Controls whether an empty container `.{}`
  becomes an empty dictionary (`{}`) or an empty sequence (`[]` / `()`).

```python
from natizon import EmptyContainerMode, loads

data = loads(".{}", use_tuples=True, empty_mode=EmptyContainerMode.SEQUENCE)
print(data)  # Output: ()
```

## Python to ZON Type Mapping

When you pass a Python object to `natizon.dumps()`, it is converted to its natural ZON representation.

| Python Type    | Python Value     | ZON Output        | Notes                                                             |
|:---------------|:-----------------|:------------------|:------------------------------------------------------------------|
| `NoneType`     | `None`           | `null`            |                                                                   |
| `bool`         | `True`, `False`  | `true`, `false`   |                                                                   |
| `int`          | `42`, `-7`       | `42`, `-7`        |                                                                   |
| `float`        | `3.14`           | `3.14`            | `nan`, `inf`, and `-inf` are serialized as ZON keywords.          |
| `str`          | `"hello\nworld"` | `"hello\\nworld"` | Special characters are escaped; output is always a quoted string. |
| `list`/`tuple` | `[1, 2, 3]`      | `.{ 1, 2, 3 }`    | Both `list` and `tuple` map to ZON arrays.                        |
| `dict`         | `{"x": 1}`       | `.{ .x = 1 }`     | Keys become ZON identifiers; non-plain keys use `.@"..."` syntax. |

### Configuration

* **`indent`** (`int | str | None`, default `None`): If a non-negative integer, indents with that many spaces per level.
  If a string (like `"\t"`), uses that string to indent each level. If `None`, outputs a compact, single-line
  representation.
* **`sort_keys`** (`bool`, default `False`): If `True`, dictionary keys are output in sorted order.

## License

This project is licensed under the Apache License 2.0. See the [LICENSES](LICENSES) directory for the full license
text.
