tson.io
Comparison

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.

Built inPartial — an annotation, a convention, an add-on, or part of the featureNot provided
Built-in types

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: int8int256, uint8uint256
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..255)
Not provided:
Sign-restricted integers
Built in: positive_integer, non_negative_integer, …
Built in: positiveInteger, nonNegativeInteger, …
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.type.Decimal
Partial: REAL constrained to base 10
Not provided: float only
Rational
Built in: rational2/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 .inf and .nan
Unicode text
Built in: text
Built in: string, token, normalizedString
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, hexBinary — two distinct types
Partial: contentEncoding, 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 gYear, gYearMonth, …
Partial: format: date
Partial: google.type.Date, 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.type.TimeOfDay, a common type
Partial: TIME-OF-DAY (local), or TIME with settings
Not provided:
Date-time instant
Built in: datetime — offset mandatory
Built in: dateTimeStamp; dateTime has an optional offset
Partial: format: date-time
Built in: google.protobuf.Timestamp
Built in: GeneralizedTime, UTCTime
Partial: !!timestamp (YAML 1.1)
Local date-time (no offset)
Not provided: excluded by design: an instant needs its offset
Built in: dateTime without an offset
Not provided: RFC 3339 requires the offset
Partial: google.type.DateTime, zone optional
Built in: DATE-TIME
Not provided:
Elapsed duration
Built in: duration — seconds, totally ordered
Built in: dayTimeDuration
Partial: format: duration, months and seconds mixed
Built in: google.protobuf.Duration
Built in: DURATION, months and seconds mixed
Not provided:
Calendar period
Built in: period — months, totally ordered
Built in: yearMonthDuration
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: anyURI
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.

any schema

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}" }
any schema

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 {}
a meta-schema

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?
  }
}
Data structures

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: complexType 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 additionalProperties: false
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: additionalProperties + propertyNames; 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: maxOccurs, list types
Built in: array with items
Built in: repeated
Built in: SEQUENCE OF
Built in: sequence
Size bounds
Built in: [T; 1..10], and the same on maps
Built in: minOccurs / maxOccurs, length facets
Built in: minItems / maxItems
Not provided:
Built in: SIZE (1..10)
Not provided:
Tuple
Built in: [number, number]
Partial: sequence of named elements
Built in: prefixItems 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: uniqueItems; 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: oneOf / anyOf — 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: oneOf 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: minOccurs, 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: minOccurs="0", 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: allOf 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: allOf 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: $dynamicRef 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.protobuf.Any
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:
Foundations

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.

Lengthidentified by positionidentified 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}
Proto-schema Part 4: Sequences

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_type 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 prefixItems), 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.

  1. 1
    Template

    Types are still blank. Nothing can be validated until they are filled.

    pair => <T, U> { first: T  second: U }
  2. 2
    Type

    Every type is known and the values are blank. A default, ~, is a value placed but replaceable.

    config => { host: text  port: integer ~ 8080 }
  3. 3
    Refinement

    Some values are fixed, =, and can no longer change. The result still IS-A the type it refines.

    production => config ^ { host: = "prod.example.com" }
  4. 4
    Value

    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_name>), and a bare template name is never read as the family of its instantiations: every reference binds all its parameters, pair<text, integer>.

Proto-schema Part 5: Templates
Constraints

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.

§5.5
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_min, max / exclusive_max — on dates too
Built in: minInclusivemaxExclusive
Built in: minimumexclusiveMaximum
Not provided: the protovalidate add-on
Built in: value ranges, (0<..<10)
Not provided:
Multiple of
Built in: multiple_of
Not provided:
Built in: multipleOf
Not provided:
Not provided:
Not provided:
Decimal digits
Built in: total_digits, fraction_digits
Built in: totalDigits, fractionDigits
Not provided:
Not provided:
Not provided:
Not provided:
Text length
Built in: min_length, max_length, length
Built in: minLength, maxLength, length
Built in: minLength, maxLength
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: explicitTimezone, 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, dependentRequired, dependentSchemas
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
Documents

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, .proto
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.proto
Not provided:
Not provided:
Data names its schema
Built in: !!schema, hash-pinned
Partial: xsi:schemaLocation, 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 .proto
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.

Going deeper

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.