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:
{"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 |
|---|---|---|---|---|
|
Numeric/Decimal |
|
|
|
|
Date |
|
|
|
|
DateTime |
|
|
|
|
Time (Hour) |
|
|
|
Decode-only Types (native to JSON, used in XML)
Code |
Name |
Wire Format |
Python |
JavaScript |
|---|---|---|---|---|
|
Long/Integer |
|
|
|
|
Real/Float |
|
|
|
|
Boolean |
|
|
|
|
Text/String |
|
|
|
Note:
DH(naive datetime) is deprecated but still decoded for backward compatibility.
Encoding Rules
JSON Format
Scalars with special types get the type suffix:
Decimal("100.50") → "100.50::N" date(2025, 1, 15) → "2025-01-15::D"Native JSON types pass through unchanged:
"hello" → "hello" 42 → 42 True → true None → null
Objects/Arrays containing special types get
::JSsuffix:{"price": Decimal("100.50")} → '{"price": "100.50::N"}::JS' [Decimal("1"), Decimal("2")] → '["1::N", "2::N"]::JS'Plain objects/arrays (no special types) have no suffix:
{"name": "Widget", "qty": 5} → '{"name": "Widget", "qty": 5}'
DateTime Serialization
All datetime values are serialized in UTC with millisecond precision:
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:
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:
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 |
|
XML |
|
MessagePack |
|
Query String Encoding
Typed values in query strings use the same suffix format:
?date=2025-01-15::D&price=100.50::N&active=1::B
Header Encoding
Custom headers with x-tytx- prefix can carry typed values:
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:
<order id="123::L">
<price>100.50::N</price>
<date>2025-01-15::D</date>
<active>1::B</active>
<name>Widget</name>
</order>
XML Input Structure
Important: The XML encoder requires a strict {value: ..., attrs?: {...}} structure for every element.
# ✅ 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 Noneattrs(optional): Attributes dict, defaults to{}
from decimal import Decimal
from datetime import date
# Simple scalar
data = {"price": {"value": Decimal("100.50")}}
# → <price>100.50::N</price>
# With attributes
data = {
"order": {
"attrs": {"id": 123, "date": date(2025, 1, 15)},
"value": {"total": {"value": Decimal("100.50")}}
}
}
# → <order id="123::L" date="2025-01-15::D"><total>100.50::N</total></order>
# Nested structure
data = {
"invoice": {
"value": {
"header": {
"value": {
"number": {"value": 12345}
}
}
}
}
}
# → <invoice><header><number>12345::L</number></header></invoice>
# Repeated elements (list of dicts)
data = {
"order": {
"value": {
"item": [
{"attrs": {"name": "Widget"}, "value": Decimal("10.50")},
{"attrs": {"name": "Gadget"}, "value": Decimal("25.00")}
]
}
}
}
# → <order><item name="Widget">10.50::N</item><item name="Gadget">25.00::N</item></order>
# Direct list as value (creates _item tags)
data = {
"prices": {
"value": [
{"value": Decimal("1.1")},
{"value": Decimal("2.2")}
]
}
}
# → <prices><_item>1.1::N</_item><_item>2.2::N</_item></prices>
The root Parameter
Use root to wrap arbitrary data in a root element:
# root=True wraps in <tytx_root>
to_xml({"price": {"value": Decimal("100")}}, root=True)
# → <tytx_root><price>100::N</price></tytx_root>
# root="custom" wraps in custom tag
to_xml({"price": {"value": Decimal("100")}}, root="data")
# → <data><price>100::N</price></data>
# root={...} adds attributes to tytx_root
to_xml({"price": {"value": Decimal("100")}}, root={"version": 1})
# → <tytx_root version="1::L"><price>100::N</price></tytx_root>
Auto-unwrap tytx_root
When decoding, if the root element is tytx_root, it is automatically unwrapped:
from_xml("<tytx_root><price>100::N</price></tytx_root>")
# Returns: {"price": {"attrs": {}, "value": Decimal("100")}}
# NOT: {"tytx_root": {"attrs": {}, "value": {...}}}
from_xml("<order><price>100::N</price></order>")
# Returns: {"order": {"attrs": {}, "value": {"price": {...}}}}
# (regular root elements are NOT unwrapped)
Decoded Structure
Decoded XML always returns {tag: {attrs: {...}, value: ...}} structure:
from_xml("<price>100.50::N</price>")
# Returns: {"price": {"attrs": {}, "value": Decimal("100.50")}}
from_xml('<item name="Widget" price="10::L" />')
# Returns: {"item": {"attrs": {"name": "Widget", "price": 10}, "value": None}}
MessagePack Format
MessagePack uses ExtType(42) for TYTX values:
# 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:
big.js(preferred)decimal.jsNative
Number(fallback - precision loss warning)
You can force a specific library:
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:
from_tytx(' {"price": "100::N"}::JS ')
# Works correctly
Error Handling
Unknown Type Codes
Unknown suffixes are returned as strings:
from_tytx('"something::UNKNOWN"')
# Returns: "something::UNKNOWN" (string)
Invalid Values
Invalid values for a type code raise TYTXDecodeError:
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.