[![CI](https://github.com/dankogai/raku-sion/actions/workflows/ci.yml/badge.svg)](https://github.com/dankogai/raku-sion/actions/workflows/ci.yml)

# NAME

SION - a serialization format a little more expressive than JSON

# SYNOPSIS

```raku
use SION;    # exports to-sion() and from-sion()

my $data = from-sion($sion-text);    # Str or utf-8 Blob
my $sion = to-sion($data);

# options, a la JSON::Fast
my $sion = to-sion($data, :pretty, :sorted-keys);
```

# DESCRIPTION

[SION](https://dankogai.github.io/SION/) is a data serialization format
whose origin is Swift literals, just like JSON's origin is JavaScript
literals.  It is a little more expressive than JSON:

```swift
[
    "nil":      nil,                // nil is allowed
    "bool":     true,
    "int":      -42,                // Int is distinguished from Double
    "double":   0x1.518f5c28f5c29p+5,   // C99 hexadecimal notation
    "string":   "漢字、カタカナ、ひらがなの入ったstring😇",
    "array":    [nil, true, 1, 1.0, "one", [1], ["one":1.0]],
    "dictionary": ["nil":nil, "array":[], "object":[:]],
    "data":     .Data("R0lGODlh"),  // binary data in Base64
    "date":     .Date(0x0p+0),      // date in seconds since epoch
    "ext":      .Ext("1NTU"),       // msgpack-style extension
    1:          "non-string keys are also allowed", // comments, too!
]
```

This module implements a SION encoder/decoder for
[Raku](https://raku.org).  It is standalone — no dependency on other
modules.

## MAPPING

Raku is expressive enough that almost every SION type maps to a native
Raku type:

| SION         | Raku                                    |
|--------------|-----------------------------------------|
| `nil`        | `Any` (undefined)                       |
| `true/false` | `Bool`                                  |
| `Int`        | `Int`                                   |
| `Double`     | `Num`                                   |
| `String`     | `Str`                                   |
| `Data`       | `Blob`                                  |
| `Date`       | `DateTime` (fractional epoch preserved) |
| `Ext`        | `SION::Ext`                             |
| `Array`      | `Array`                                 |
| `Dictionary` | `Hash` (object hash for non-`Str` keys) |

### Int vs Double

`Int` and `Num` are distinct types in Raku, so the distinction
round-trips with no scalar-flag tricks: `1` encodes to `1` while `1e0`
encodes to `0x1p+0`.  `Num`s are always encoded in C99 hexadecimal
floating point notation, which is lossless; `nan`, `inf` and `-inf` are
also supported.  `Rat` and other `Real`s encode as Double.

### String vs Data

A `Str` is text and a `Blob` is binary, so no heuristics are needed:
`Str` encodes as a SION String while any `Blob` (including `buf8`)
encodes as `.Data("…")` in Base64, and vice versa on decoding.

### dates

`.Date(n)` — `n` being seconds since epoch, possibly fractional —
decodes to a `DateTime`, and `DateTime`, `Instant` and `Date` all
encode as `.Date(…)`.  The fractional part survives the round trip:
`.Date(0x1p-1)` is `DateTime.new(Instant.from-posix(0.5))`.

### dictionaries

SION allows any value as a `Dictionary` key.  When every key is a
`Str`, `from-sion` returns an ordinary `Hash`; otherwise it returns an
object hash, so `from-sion('[1:"one"]'){1}` works and the `Int`-ness
of the key is preserved — no stringification.

# FUNCTIONS

## from-sion

```raku
my $data = from-sion($sion);                 # Str
my $data = from-sion($octets);               # utf-8 encoded Blob
my $data = from-sion($sion, :max-depth(64));
```

Decodes SION text to a Raku data structure.  Dies on malformed input
with the character offset of the error.  `:max-depth` (default 512)
bounds the nesting level.

## to-sion

```raku
my $sion = to-sion($data);
my $sion = to-sion($data, :pretty);       # multi-line, indented by 4
my $sion = to-sion($data, :sorted-keys);  # deterministic dictionaries
my $sion = to-sion($data, :ascii);        # non-ASCII as \uXXXX escapes
my $sion = to-sion($data, :max-depth(64));
```

Encodes a Raku data structure to SION text.  Compact by default;
`:sorted-keys` sorts dictionary entries by their encoded keys so the
output is deterministic (and a fixed point of
`to-sion(from-sion($_), :sorted-keys)`).

# HELPER CLASSES

## SION::Ext

```raku
my $ext = SION::Ext.new($blob);   # encodes to .Ext("base64")
$ext.data;                        # the Blob payload
```

msgpack-style extension.  It is a value type: two `SION::Ext` objects
with the same payload are `eqv`.

# HEXADECIMAL FLOATING POINT

SION serializes `Double` in C99 hexfloat notation (`0x1.518f5c28f5c29p+5`)
because it is exact.  Rakudo is gaining native support for it —
`sprintf '%a'` landed for `v6.e` in
[rakudo/rakudo#6525](https://github.com/rakudo/rakudo/pull/6525) — but
this module does not require it: it probes at load time and falls back
to a pure-Raku encoder/decoder (exact `Int`/`FatRat` arithmetic, so the
result is correctly rounded).  Subnormals are emitted normalized
(`0x1p-1074`), byte-compatible with
[js-sion](https://github.com/dankogai/js-sion) and BSD `printf %a`.

# SEE ALSO

* [https://dankogai.github.io/SION/](https://dankogai.github.io/SION/)
* [swift-sion](https://github.com/dankogai/swift-sion)
* [js-sion](https://github.com/dankogai/js-sion)
* [p5-sion](https://github.com/dankogai/p5-sion)

# AUTHOR

Dan Kogai

# LICENSE AND COPYRIGHT

Copyright (c) 2026 by Dan Kogai.

This is free software, licensed under the Artistic License 2.0.
