# How It Works Technical details of the TYTX wire format and encoding rules. > **Note**: You don't need to understand this to use TYTX. The middleware handles encoding/decoding automatically. This document is for those who want to understand the internals or implement TYTX in other languages. ## Wire Format Overview TYTX encodes type information using `value::TYPE` suffixes: ```text {"price": "100.50::N", "date": "2025-01-15::D"}::JS ``` The `::JS` suffix marks the entire payload as TYTX-encoded JSON. ## Type Codes ### Encoded Types (non-native to JSON) | Code | Name | Wire Format | Python | JavaScript | |------|------|-------------|--------|------------| | `N` | Numeric/Decimal | `"100.50::N"` | `Decimal("100.50")` | `Decimal("100.50")` | | `D` | Date | `"2025-01-15::D"` | `date(2025, 1, 15)` | `Date` (midnight UTC) | | `DHZ` | DateTime | `"2025-01-15T10:30:00.000Z::DHZ"` | `datetime(...)` | `Date` | | `H` | Time (Hour) | `"10:30:00::H"` | `time(10, 30, 0)` | `Date` (epoch date) | ### Decode-only Types (native to JSON, used in XML) | Code | Name | Wire Format | Python | JavaScript | |------|------|-------------|--------|------------| | `L` | Long/Integer | `"42::L"` | `int` | `number` | | `R` | Real/Float | `"3.14::R"` | `float` | `number` | | `B` | Boolean | `"1::B"` or `"0::B"` | `bool` | `boolean` | | `T` | Text/String | `"hello::T"` | `str` | `string` | > **Note**: `DH` (naive datetime) is deprecated but still decoded for backward compatibility. ## Encoding Rules ### JSON Format 1. **Scalars with special types** get the type suffix: ```text Decimal("100.50") → "100.50::N" date(2025, 1, 15) → "2025-01-15::D" ``` 2. **Native JSON types** pass through unchanged: ```text "hello" → "hello" 42 → 42 True → true None → null ``` 3. **Objects/Arrays** containing special types get `::JS` suffix: ```text {"price": Decimal("100.50")} → '{"price": "100.50::N"}::JS' [Decimal("1"), Decimal("2")] → '["1::N", "2::N"]::JS' ``` 4. **Plain objects/arrays** (no special types) have no suffix: ```text {"name": "Widget", "qty": 5} → '{"name": "Widget", "qty": 5}' ``` ### DateTime Serialization All datetime values are serialized in UTC with millisecond precision: ```python datetime(2025, 1, 15, 10, 30, 45, 123456) # → "2025-01-15T10:30:45.123Z::DHZ" # (microseconds truncated to milliseconds for JS compatibility) ``` Naive datetimes are treated as UTC: ```python datetime(2025, 1, 15, 10, 30) # no tzinfo # → "2025-01-15T10:30:00.000Z::DHZ" ``` ### Time Serialization Time values include milliseconds only when non-zero: ```text time(10, 30, 0) → "10:30:00::H" time(10, 30, 0, 123000) → "10:30:00.123::H" ``` ## HTTP Transport ### MIME Types | Format | Content-Type | |--------|-------------| | JSON | `application/vnd.tytx+json` | | XML | `application/vnd.tytx+xml` | | MessagePack | `application/vnd.tytx+msgpack` | ### Query String Encoding Typed values in query strings use the same suffix format: ```text ?date=2025-01-15::D&price=100.50::N&active=1::B ``` ### Header Encoding Custom headers with `x-tytx-` prefix can carry typed values: ```text x-tytx-timestamp: 10:30:00::H x-tytx-expires: 2025-12-31::D ``` ## XML Format In XML, all values are strings, so all types are encoded: ```xml 100.50::N 2025-01-15::D 1::B Widget ``` ### XML Input Structure **Important**: The XML encoder requires a strict `{value: ..., attrs?: {...}}` structure for every element. ```python # ✅ Correct format {"price": {"value": Decimal("100.50")}} {"price": {"attrs": {}, "value": Decimal("100.50")}} {"order": {"attrs": {"id": 123}, "value": None}} # ❌ Invalid format - will raise ValueError {"price": Decimal("100.50")} # Missing 'value' key ``` ### Building XML Data Each element must be a dict with: - `value` (required): The element content - can be scalar, dict (children), list, or None - `attrs` (optional): Attributes dict, defaults to `{}` ```python from decimal import Decimal from datetime import date # Simple scalar data = {"price": {"value": Decimal("100.50")}} # → 100.50::N # With attributes data = { "order": { "attrs": {"id": 123, "date": date(2025, 1, 15)}, "value": {"total": {"value": Decimal("100.50")}} } } # → 100.50::N # Nested structure data = { "invoice": { "value": { "header": { "value": { "number": {"value": 12345} } } } } } # →
12345::L
# Repeated elements (list of dicts) data = { "order": { "value": { "item": [ {"attrs": {"name": "Widget"}, "value": Decimal("10.50")}, {"attrs": {"name": "Gadget"}, "value": Decimal("25.00")} ] } } } # → 10.50::N25.00::N # Direct list as value (creates _item tags) data = { "prices": { "value": [ {"value": Decimal("1.1")}, {"value": Decimal("2.2")} ] } } # → <_item>1.1::N<_item>2.2::N ``` ### The `root` Parameter Use `root` to wrap arbitrary data in a root element: ```python # root=True wraps in to_xml({"price": {"value": Decimal("100")}}, root=True) # → 100::N # root="custom" wraps in custom tag to_xml({"price": {"value": Decimal("100")}}, root="data") # → 100::N # root={...} adds attributes to tytx_root to_xml({"price": {"value": Decimal("100")}}, root={"version": 1}) # → 100::N ``` ### Auto-unwrap `tytx_root` When decoding, if the root element is `tytx_root`, it is automatically unwrapped: ```python from_xml("100::N") # Returns: {"price": {"attrs": {}, "value": Decimal("100")}} # NOT: {"tytx_root": {"attrs": {}, "value": {...}}} from_xml("100::N") # Returns: {"order": {"attrs": {}, "value": {"price": {...}}}} # (regular root elements are NOT unwrapped) ``` ### Decoded Structure Decoded XML always returns `{tag: {attrs: {...}, value: ...}}` structure: ```python from_xml("100.50::N") # Returns: {"price": {"attrs": {}, "value": Decimal("100.50")}} from_xml('') # Returns: {"item": {"attrs": {"name": "Widget", "price": 10}, "value": None}} ``` ## MessagePack Format MessagePack uses ExtType(42) for TYTX values: ```python # ExtType structure ExtType(42, b"N:100.50") # Decimal ExtType(42, b"D:2025-01-15") # Date ``` ## Decimal Library Detection ### Python Python's `decimal.Decimal` is always available (stdlib). ### JavaScript/TypeScript TYTX auto-detects decimal libraries in this order: 1. `big.js` (preferred) 2. `decimal.js` 3. Native `Number` (fallback - precision loss warning) You can force a specific library: ```bash TYTX_DECIMAL_LIB=big.js # Force big.js TYTX_DECIMAL_LIB=decimal.js # Force decimal.js TYTX_DECIMAL_LIB=number # Force native Number ``` ## Whitespace Handling Decoders trim leading/trailing whitespace before processing: ```python from_tytx(' {"price": "100::N"}::JS ') # Works correctly ``` ## Error Handling ### Unknown Type Codes Unknown suffixes are returned as strings: ```python from_tytx('"something::UNKNOWN"') # Returns: "something::UNKNOWN" (string) ``` ### Invalid Values Invalid values for a type code raise `TYTXDecodeError`: ```python from_tytx('"not-a-date::D"') # Raises TYTXDecodeError ``` ## TYTX Base vs TYTX (Full) | Feature | TYTX Base | TYTX | |---------|:---------:|:----:| | Scalar types (N, D, DHZ, H, B, L, R, T) | ✅ | ✅ | | JSON / XML / MessagePack | ✅ | ✅ | | HTTP middleware | ✅ | ✅ | | Struct schemas (`@`) | ❌ | ✅ | | XTYTX envelope | ❌ | ✅ | | Pydantic integration | ❌ | ✅ | **Use TYTX Base** when you only need scalar types and want a minimal footprint.