TSON beside the schema languages you know
Built-in types, data structures, constraints and the document model, side by side with XML Schema, JSON Schema, Protocol Buffers, ASN.1 and YAML. YAML has no schema language of its own, so its column is the types its core schema resolves, with the YAML 1.1 types marked where they matter.
Scalar types
What each system names for you, before you write a line of your own. TSON's atoms live in the core library, each pinned to the RFC that defines it, and each states its value space: equality and ordering are over values, never spellings. The core library is a starting set, not a ceiling: new atom types arrive through meta extensions (below).
core.tn| Feature | TSON Revision 35 | XML Schema XSD 1.1 | JSON Schema 2020-12 | Protobuf proto3 / editions | ASN.1 X.680 series | YAML 1.2 core schema |
|---|---|---|---|---|---|---|
| Boolean | Built in: boolean | Built in: boolean | Built in: boolean | Built in: bool | Built in: BOOLEAN | Built in: bool |
| Arbitrary-precision integer | Built in: integer | Built in: integer | Built in: integer | Not provided: 64-bit maximum | Built in: INTEGER | Partial: int, range left to the implementation |
| Fixed-width integers | Built in: int8 … int256, uint8 … uint256 | Built in: byte, short, int, long and unsigned forms, 8–64 bits | Partial: via minimum / maximum | Built in: int32, int64, uint32, uint64, plus sint / fixed wire variants | Partial: via range constraint, INTEGER (0.. | Not provided: |
| Sign-restricted integers | Built in: positive_, non_, … | Built in: positive, non, … | Partial: via minimum | Partial: unsigned widths only | Partial: via range constraint | Not provided: |
| Exact decimal | Built in: number — exact, no NaN or infinity | Built in: decimal | Partial: number; exactness left to the validator | Not provided: a string, or google. | Partial: REAL constrained to base 10 | Not provided: float only |
| Rational | Built in: rational — 2/4 and 1/2 are one value | Not provided: | Not provided: | Not provided: | Not provided: | Not provided: |
| Complex | Built in: complex, components selectable | Not provided: | Not provided: | Not provided: | Not provided: | Not provided: |
| IEEE 754 floating point | Built in: float32, float64 | Built in: float, double | Not provided: one number type | Built in: float, double | Partial: REAL constrained to base 2 | Built in: float, with . and . |
| Unicode text | Built in: text | Built in: string, token, normalized | Built in: string | Built in: string | Built in: UTF8String and a dozen restricted string types | Built in: str |
| Binary | Built in: bytes — the value is octets; the alphabet is a spelling | Built in: base64Binary, hex — two distinct types | Partial: content, an annotation | Built in: bytes | Built in: OCTET STRING, BIT STRING | Partial: !!binary (YAML 1.1) |
| Date | Built in: date | Built in: date, plus partials g, g, … | Partial: format: date | Partial: google., a common type | Built in: DATE | Partial: !!timestamp (YAML 1.1) |
| Time of day | Built in: time — offset mandatory, totally ordered | Built in: time — offset optional, partially ordered | Partial: format: time | Partial: google., a common type | Partial: TIME-OF-DAY (local), or TIME with settings | Not provided: |
| Date-time instant | Built in: datetime — offset mandatory | Built in: date; date has an optional offset | Partial: format: date-time | Built in: google. | Built in: Generalized, UTCTime | Partial: !!timestamp (YAML 1.1) |
| Local date-time (no offset) | Not provided: excluded by design: an instant needs its offset | Built in: date without an offset | Not provided: RFC 3339 requires the offset | Partial: google., zone optional | Built in: DATE-TIME | Not provided: |
| Elapsed duration | Built in: duration — seconds, totally ordered | Built in: day | Partial: format: duration, months and seconds mixed | Built in: google. | Built in: DURATION, months and seconds mixed | Not provided: |
| Calendar period | Built in: period — months, totally ordered | Built in: year | Partial: inside format: duration | Not provided: | Partial: inside DURATION | Not provided: |
| UUID | Built in: uuid (RFC 9562) | Not provided: via pattern | Partial: format: uuid | Not provided: a string or bytes | Not provided: OCTET STRING (SIZE(16)) | Not provided: |
| URI | Built in: uri (RFC 3986) | Built in: any | Partial: format: uri, iri, uri-reference | Not provided: | Not provided: | Not provided: |
| Email address | Built in: email — the dot-atom core of RFC 5322 | Not provided: | Partial: format: email, idn-email | Not provided: | Not provided: | Not provided: |
| IP addresses | Built in: ipv4, ipv6 | Not provided: | Partial: format: ipv4, ipv6 | Not provided: | Not provided: | Not provided: |
| Networks and hardware addresses | Built in: cidr4, cidr6, mac | Not provided: | Not provided: | Not provided: | Not provided: | Not provided: |
| Regular expression | Built in: regex — I-Regexp (RFC 9485) | Not provided: a facet, not a type | Partial: format: regex | Not provided: | Not provided: | Not provided: |
| Object identifier | Not provided: not in core; a meta extension can add it | Not provided: | Not provided: | Not provided: | Built in: OBJECT IDENTIFIER, RELATIVE-OID, OID-IRI | Not provided: |
| Adding new built-in types | Built in: meta extensions declare new atom constructors, facets included | Partial: implementation-defined primitives (1.1) | Partial: custom $vocabulary keywords and format values | Not provided: new types are messages | Not provided: a fixed set of built-in types | Partial: application tags, !mytype |
Extending the vocabulary
The TSON column is what the core library ships, not everything TSON can say. Types grow in three steps, and only the last needs a meta-schema: the meta layer is the format's sanctioned extension point, so new type vocabularies arrive as meta-schemas chaining to the meta-kernel, never as grammar changes. The same route adds non-type vocabulary, such as an HTTP operation binding request and response types.
Refine an atom
Tighten an existing type with its own facets. The result IS-A the type it narrows, so it is accepted wherever that type is.
currency_code => !text ^ { pattern: "[A-Z]{3}" }Found a new family
Apply an existing constructor afresh: same facets and parsing contract, but a distinct type, so a stock ID is refused where an order ID is expected.
stock_id => !uuid_type {}
order_id => !uuid_type {}Extend the meta layer
Declare a new constructor: a new atom family with its own facet vocabulary, pinned to the specification that defines it. This is how every atom in core is built, and the one place constructors may be declared. Processors that read it need to implement its parsing contract.
!!meta:"https://tson.io/2026/35/m/meta-kernel.tn?sha256=…"
!!import:"https://tson.io/2026/35/m/meta-kernel.tn?sha256=…"
{
oid_type => atom & atom_specification & {
spec: = "https://www.itu.int/rec/T-REC-X.660"
max_arcs: non_negative_integer?
}
}Structures and composition
How values combine. TSON gives each container its own bracket form, the same in the schema as in the data, and treats a record as closed: a field the schema does not declare is an error, not a pass-through. Any value, field or declaration can also carry annotations: typed metadata that never changes whether a value is valid.
§5.3| Feature | TSON Revision 35 | XML Schema XSD 1.1 | JSON Schema 2020-12 | Protobuf proto3 / editions | ASN.1 X.680 series | YAML 1.2 core schema |
|---|---|---|---|---|---|---|
| Record (named fields) | Built in: { name: text } | Built in: complex with sequence / all | Built in: object with properties | Built in: message | Built in: SEQUENCE, SET | Partial: mapping, untyped |
| Closed to undeclared fields | Built in: closed; extras go to an @rest map if declared | Built in: closed unless xs:any is declared | Not provided: open unless additional | Not provided: unknown fields are kept and skipped | Partial: closed, or open with an extension marker ... | Not provided: |
| Map with typed keys | Built in: {K => V}, any key type | Not provided: key / keyref identity constraints | Partial: additional + property; keys are strings | Built in: map<K, V>, integer or string keys | Not provided: SEQUENCE OF key/value pairs | Built in: mapping; any node can be a key |
| Array | Built in: [T] | Built in: max, list types | Built in: array with items | Built in: repeated | Built in: SEQUENCE OF | Built in: sequence |
| Size bounds | Built in: [T; 1.., and the same on maps | Built in: min / max, length facets | Built in: min / max | Not provided: | Built in: SIZE (1.. | Not provided: |
| Tuple | Built in: [number, number] | Partial: sequence of named elements | Built in: prefix with items: false | Not provided: | Partial: SEQUENCE of named components | Not provided: |
| Set (unordered, unique) | Built in: set<T>, uniqueness by value | Partial: xs:unique identity constraint | Partial: unique; order still significant | Not provided: | Built in: SET OF | Partial: !!set (YAML 1.1) |
| Enumeration | Built in: !enum [A B C] | Built in: enumeration facet | Built in: enum, const | Built in: enum, open in proto3 | Built in: ENUMERATED | Not provided: |
| Choice between types | Built in: (email | phone) — tagged only where two variants share a form | Partial: union, simple types only | Partial: one / any — matched by validation, untagged | Not provided: wrap in a oneof | Partial: CHOICE — alternatives are labelled | Partial: node tags |
| Labelled alternatives | Built in: field groups, ( a: T | b: U ) | Built in: xs:choice | Partial: one with required | Built in: oneof | Built in: CHOICE | Not provided: |
| Required, optional, default, fixed | Built in: five field states: T, T?, ~ default, = fixed, T? = fixed; defaults filled in on read | Built in: min, default, fixed | Partial: required, const; default is an annotation | Partial: optional; defaults are zero values | Built in: OPTIONAL, DEFAULT | Not provided: |
| Absence | Built in: one spelling, _; no null in the notation | Built in: min, and xsi:nil | Built in: omission, and null | Partial: tracked only for optional; zero is otherwise unset | Built in: OPTIONAL, and NULL | Built in: omission, and null / ~ |
| Recursive types | Built in: yes | Built in: yes | Built in: yes, via $ref | Built in: yes | Built in: yes | Not provided: anchors reuse values, not types |
| Composition (inherit, IS-A) | Built in: A & B & { … } — several parents, no diamonds | Built in: extension, substitution groups; one parent | Partial: all merges constraints; no IS-A | Not provided: nest a message instead | Partial: COMPONENTS OF copies fields | Partial: << merge key (YAML 1.1) |
| Refinement (narrow, keep IS-A) | Built in: T ^ { … } — tighten, default or fix fields; never adds or removes one | Built in: restriction | Partial: all plus keywords; no subtype relation | Not provided: | Built in: subtype constraints, WITH COMPONENTS | Not provided: |
| Subtraction (remove fields) | Built in: A - { password } — breaks IS-A, keeps lineage | Partial: restriction can drop optional parts | Not provided: copy the schema | Not provided: | Partial: WITH COMPONENTS can mark optional components ABSENT | Not provided: |
| Generics | Built in: templates over types and values, vector<T, N> | Not provided: | Partial: $dynamic approximates | Not provided: | Built in: parameterized types (X.683) | Not provided: |
| Open type (value names its type) | Built in: declared, extern, dynamic — validated against the named type | Built in: xs:any, xsi:type | Partial: {} accepts anything, unchecked | Built in: google. | Built in: open types, EMBEDDED PDV | Partial: tags |
| Annotations on data | Built in: @name:value on any value, map key or field; preserved in order | Partial: attributes, where the schema admits them | Not provided: JSON has no metadata channel | Not provided: | Not provided: | Partial: tags and anchors; comments are discarded |
| Typed, validated annotations | Built in: every annotation is a type, deprecated => @annotation text; an unknown one is an error, and @disjoint, @discriminator and @rest are checked at load | Partial: xs:appinfo and foreign attributes, checked only if declared | Partial: annotation keywords; custom ones via $vocabulary, unknown ones ignored | Built in: custom options, typed by extending the *Options messages | Not provided: comments only | Not provided: |
Why these structures, and no others
TSON's containers weren't chosen from other languages' feature lists. The proto-schema research derives them from what a serialized document physically is: linear, immutable, finite and divisible. Once written out, every structure is a sequence, so the question becomes which kinds of sequence a schema needs to name.
| Length | identified by position | identified by a label in the data |
|---|---|---|
| schema fixes the elements | Tuple [number, number] | Record { name: text } |
| data decides how many | Array [T] | Map {K => V} |
Two questions come from the medium
A reader of a linear stream has to know where a sequence ends and what each element means. Either the schema fixes the elements or the data decides how many there are; either an element is known by where it sits or by a label written beside it. Two questions give four cells, and each is a structure every schema language rediscovers under its own name.
Every other property collapses
Mixed element types are a choice, [(integer | text)], not a new container. Order and uniqueness are instructions to the reader, since a serialized sequence is always ordered and can always repeat. So a set is an array with both switched on — in TSON set_ is literally a refinement of array — and vectors, bags, ordered sets and multimaps are constrained configurations of the same four.
What falls outside is declined
The cases the four cannot reach need something a sequence does not have: a tuple flattened into an array (JSON Schema's prefix), structure chosen by an earlier value (if/then/else), or references between values. TSON leaves these out rather than add a fifth container. The research calls this a constructive argument, not a formal proof: every case it examines either names one of the four or reduces to them.
One spectrum, from template to value
The second idea holds the rest together: data and schemas are not separate kinds of thing but points on one line, told apart by how many blanks remain. A template leaves types blank, a type leaves values blank, and a value has none left. The developer guide names this spectrum of completeness as the basis of TSON's templates, and every type operation is a step along it, in one direction only.
- 1Template
Types are still blank. Nothing can be validated until they are filled.
pair => <T, U> { first: T second: U } - 2Type
Every type is known and the values are blank. A default,
~, is a value placed but replaceable.config => { host: text port: integer ~ 8080 } - 3Refinement
Some values are fixed,
=, and can no longer change. The result still IS-A the type it refines.production => config ^ { host: = "prod.example.com" } - 4Value
Nothing is blank. The fixed host and any unstated default are filled in on read.
!production { port: 5432 }
TSON follows the research in keeping blanks out of data: templates are schema-only, and a data document holds values. It also stops short of two ideas Part 5 floats. Parameters carry no bounds (<T: text | full_), and a bare template name is never read as the family of its instantiations: every reference binds all its parameters, pair<text, integer>.
Facets and constraints
What a schema can say about a value beyond its type. In TSON a constrained type is a named refinement — age => !integer ^ { min: 0 max: 150 } — and it IS-A the type it narrows.
| Feature | TSON Revision 35 | XML Schema XSD 1.1 | JSON Schema 2020-12 | Protobuf proto3 / editions | ASN.1 X.680 series | YAML 1.2 core schema |
|---|---|---|---|---|---|---|
| Numeric bounds, inclusive and exclusive | Built in: min / exclusive_, max / exclusive_ — on dates too | Built in: min … max | Built in: minimum … exclusive | Not provided: the protovalidate add-on | Built in: value ranges, (0<..<10) | Not provided: |
| Multiple of | Built in: multiple_ | Not provided: | Built in: multiple | Not provided: | Not provided: | Not provided: |
| Decimal digits | Built in: total_, fraction_ | Built in: total, fraction | Not provided: | Not provided: | Not provided: | Not provided: |
| Text length | Built in: min_, max_, length | Built in: min, max, length | Built in: min, max | Not provided: | Built in: SIZE | Not provided: |
| Pattern | Built in: pattern — I-Regexp, portable across engines | Built in: pattern, XSD's own dialect | Built in: pattern, ECMA-262 | Not provided: | Built in: PATTERN, permitted alphabet FROM | Not provided: |
| Temporal precision | Built in: precision, 0–9 fractional digits | Partial: explicit, or pattern | Not provided: | Not provided: | Partial: time property settings | Not provided: |
| Address ranges | Built in: within / excluding — an empty range is a schema error | Not provided: | Not provided: | Not provided: | Not provided: | Not provided: |
| Cross-field rules | Partial: field groups only; conditionals declined by design | Built in: xs:assert (XPath 2.0), key / keyref | Built in: if / then / else, dependent, dependent | Not provided: CEL through protovalidate | Built in: table and user-defined constraints | Not provided: |
| A misspelt constraint | Built in: refused at load: { minimum: 1 } names no facet of integer | Built in: invalid against the schema for schemas | Not provided: minimun: 1 is ignored and constrains nothing | Built in: a protoc error | Built in: a compiler error | n/a |
Identity, evolution and encodings
How a schema is written, found, trusted and changed. Here the systems differ less in features than in model, so several rows describe rather than score.
§10| Feature | TSON Revision 35 | XML Schema XSD 1.1 | JSON Schema 2020-12 | Protobuf proto3 / editions | ASN.1 X.680 series | YAML 1.2 core schema |
|---|---|---|---|---|---|---|
| Schema written in | Built in: TSON — the data's own notation | Built in: XML | Built in: JSON | its own IDL, . | its own notation | Not provided: no schema language; JSON Schema is used |
| Meta-schema that describes itself | Built in: the meta-kernel | Built in: the schema for schemas | Built in: the meta-schema | Partial: descriptor. | Not provided: | Not provided: |
| Data names its schema | Built in: !!schema, hash-pinned | Partial: xsi:schema, a hint | Not provided: by convention only | Not provided: except Any type URLs | Not provided: | Not provided: |
| Content-hash pinning | Built in: SHA-256 on every !!id, !!meta, !!import and !!schema | Not provided: | Not provided: | Not provided: | Not provided: | Not provided: |
| Types in data without a schema | Built in: !uuid "…", !ipv4 "…" — checked with no schema at all | Built in: xsi:type | Not provided: | Not provided: | Built in: universal tags in BER and DER | Partial: tags, !!int, !!timestamp |
| Evolution model | immutable: a version is a new hash, so a new required field is safe | namespaces, versioned by convention | $id per version, by convention | field numbers: never reuse, avoid required | extension markers ..., version brackets [[ ]] | — |
| Encodings | TSON text; JSON, compact and binary planned | XML, EXI | JSON; YAML in practice | binary wire, ProtoJSON, text format | BER, DER, PER, OER, XER, JER | YAML |
| Documentation metadata | Built in: @doc, @title, @examples, @deprecated, @since | Built in: xs:documentation, xs:appinfo | Built in: title, description, examples, deprecated | Partial: comments, deprecated option | Partial: comments | Not provided: |
| Comments | Not provided: annotations instead, which survive parsing | Built in: <!-- --> | Partial: $comment | Built in: // in . | Built in: -- | Built in: # |
| Standing | 2026 working draft; Java and TypeScript implementations | W3C Recommendation, 2012 | IETF draft series; the widest tooling | Google, open since 2008; code generation for most languages | ITU-T and ISO since 1984; telecoms and PKI | YAML 1.2.2, 2021 |
Where the others go further
A table favours whoever drew it.
Every system here made trade-offs TSON did not, and some of them are the right call for the job in front of you.
XML Schema
XPath assertions and identity constraints state cross-field and cross-document rules TSON deliberately does not, and partial dates (gYear, gMonthDay) have no TSON counterpart.
JSON Schema
Conditionals and dependent schemas express rules over whole objects, and it was designed to validate JSON documents that were written without a schema.
YAML
Comments, anchors and aliases, and multi-document streams make it a comfortable format for hand-edited configuration. TSON leaves anchors and aliases out on purpose: what appears at a position is the whole value, so a reader can check it is what it claims to be without chasing a reference elsewhere in the document (why).
ASN.1
Six standardised encodings from one schema, including the canonical DER that certificates depend on, and forty years of deployment in telecoms and PKI. The cost is access: the standard spans a dozen documents, the ISO/IEC editions are sold (ITU-T publishes the same text free), and the most complete compilers are commercial.
Protobuf
A compact, fast binary wire format with generated code for most languages.
The mapping reports
Each of these languages was mapped onto TSON construct by construct, as input to the revisions that followed. The reports were written against Revision 32, so read them for the reasoning; the table above is the current state. The type vocabulary itself is in Part 1 §5 and core.tn.
Schema mappings synthesis
Five schema languages mapped onto the TSON kernel, and what each mapping asked of it.
JSON Schema to TSON
Construct-by-construct, with OpenAPI, and what TSON declines by design.
Protobuf to TSON
Presence disciplines, oneof as field groups, and protovalidate as refinements.
ASN.1 to TSON
Subtype constraints, CHOICE, parameterized types, and the information object system.