# Internet Object 1.0

Thin, schema-first and robust data-interchange object format for Internet

**Internet Object** is a text-based, schema-first, document-oriented data interchange format — JSON's readability without its repeated keys, missing schema, absent comments, or lack of streaming. This site is the official specification and serves as the format's reference documentation.

> **Status.** This is the **1.0 Draft** of the specification, published alongside the public beta of the reference implementation. The specification and the implementation are still converging, so some pages note behavior that is ahead of the current implementation. Implementations version independently and declare the specification version they conform to.

## Start here

* [Manifesto](/internet-object/manifesto) — the convictions behind the format, and why it exists
* [Why Internet Object?](/internet-object/why-internet-object) — how it compares to JSON, CSV, and YAML
* [Getting Started](/internet-object/getting-started) — a five-minute tour in pure Internet Object
* [Internet Object Document](/structure-and-syntax/introduction) — the structure of a document
* [Schema Definition Language](/schema-definition-language/internet-object-schema) — types, constraints, and validation
* [Conformance Requirements](/conformance/requirements) — what a conformant implementation must do

| Field                 | Value                                                                             |
| --------------------- | --------------------------------------------------------------------------------- |
| Author and Researcher | Mohamed Aamir Maniar at ManiarTech® Lab                                           |
| Contact               | <aamir@internetobject.org>                                                        |
| Version               | `1.0 Draft`                                                                       |
| Status                | `Work-in-Progress` `Draft`                                                        |
| Last updated          | `2026-06-05`                                                                      |
| Website               | <https://internetobject.org>                                                      |
| Docs                  | <https://github.com/maniartech/InternetObject-specs>                              |
| License               | Spec: [CC BY-ND 4.0](/appendices/license) · Examples: CC0 · Libraries: Apache-2.0 |


# Manifesto

A declaration of the convictions behind Internet Object — why it exists and what it refuses to compromise.

Data is the substance of the Internet, and we still move it wastefully.

JSON proved that humans must be able to read their data — and then it stayed wasteful, lossy, and schemaless by default. The binary formats proved that the wire must be small — and then they made data unreadable and forked their schemas into a separate toolchain. Both were half-right. We accepted the choice between them for twenty years. We don't have to anymore.

Internet Object begins from a refusal: **we will not choose between readable and efficient, between expressive and exact, between human and machine.** A format for the Internet must serve all of them, at the scale the Internet actually runs.

These are the convictions it is built on.

**1. The wire is not free.** A format used everywhere, constantly, has no right to be wasteful. Every key repeated on every record is bytes paid for again and again — in storage, in bandwidth, in energy, in latency. We declare structure once and never repeat it.

**2. Structure is not data.** Keys, types, and constraints belong to the schema. A record carries only its values. Conflating the two was the inherited mistake; separating them is what makes data compact, validated, and self-describing all at once.

**3. The model is the contract, not the bytes.** A schema and its data are one truth, validated one way. Whether that truth is serialized as human-readable text or as a compact binary form, it means the same thing and validates the same way. The encoding is a choice; the meaning never changes.

**4. Data must tell the truth.** A `decimal` is exact. A `bigint` is whole. A date is a date, and binary is binary. No silent coercion, no precision lost in transit. Where other formats shrug, Internet Object is precise.

**5. Meaning belongs in the format.** Validation is not a second language in a second file. The shape of data — its types, its constraints — is declared with the data, in the same syntax as the data, and enforced as a property of the format itself.

**6. The Internet is a stream, not a file.** Data arrives over time, in volume, between parties who often already agree on its shape. Documents, collections, and streaming are first-class — not features bolted onto a format that assumed everything fits in memory at once.

**7. Open to all. Locked to none.** Internet Object is an openly specified contract on the wire, not a library in a language or a product from a vendor. Anyone can implement it; no one can lock you into it. Data you store today will still be understood long after the tools that wrote it are gone.

***

Internet Object is a promise: that data can be readable *and* small, expressive *and* validated, precise *and* fast — bound to no language and no vendor.

Not a better library. **A better default.**

## See Also

* [Abstract](/internet-object/abstract) — the technical statement of what Internet Object is
* [The Poetic Principles](/internet-object/the-zen-of-internet-object) — the same values in verse
* [Objectives](/internet-object/objectives) — the concrete goals the format is designed to meet
* [Why Internet Object?](/internet-object/why-internet-object) — how it compares to JSON, CSV, and YAML


# Abstract

A text-based, schema-first, document-oriented, streamable data interchange format.

Internet Object is a data interchange format designed for modern web communication over the internet. This specification introduces Internet Object as a text-based, schema-first, document-oriented, and streamable format that prioritizes human readability and language independence. By separating the schema from the data, it serializes structured data compactly for efficient transmission between servers and clients across the web, while preserving the clarity and approachability of a plain-text format.

## See Also

* [Introducing Internet Object](/internet-object/introduction) — a guided walkthrough
* [Objectives](/internet-object/objectives) · [Why Internet Object?](/internet-object/why-internet-object)


# The Poetic Principles

This poem encapsulates the core guiding principles that shape the design and objectives of the Internet Object format.

This poem distils the foundational principles of Internet Object into a few memorable verses. It is an informative companion to the specification: the lines below restate, in artistic form, the design values explained throughout these pages — small size, readability, the separation of data and definitions, the independence of records, and a healthy distrust of unvalidated input.

## Poem

Size holds weight, in bytes confined,\
Small prevails, large left behind.

Simplicity shines over complexity's shroud,\
Readability echoes, accurate and loud.

Reusability births productivity's rise,\
Verbosity's burden efficiency defies.

Data, definitions, separate ways,\
Together they clutter, apart they amaze.

Headers and data, distinctions drawn,\
Confusion dissolves, clarity's dawn.

Errors and statuses, data's divide,\
Their entanglement brings chaos inside.

Two lone records, states unswayed,\
No interference, connections unmade.

Trust not the sender, vigilance displayed,\
Expect the unanticipated, foundations laid.

Surprises, enchanting, yet beware,\
Not all of them good, handle with care.

## The principles behind the verses

The verses echo the format's [objectives](/internet-object/objectives):

* **Small over large** — compact payloads; keys live in the schema, not in every record.
* **Readability and simplicity** — plain text that is easy to read and to write by hand.
* **Separation of data and definitions** — the header holds schema and metadata; the data stays clean below it.
* **Record independence** — records do not depend on one another, so one bad record never breaks the rest.
* **Distrust the sender** — validate incoming data and expect the unexpected.

## See Also

* [Objectives](/internet-object/objectives) — the design goals stated plainly
* [Abstract](/internet-object/abstract) · [Introducing Internet Object](/internet-object/introduction)


# Objectives

The design goals that shape the Internet Object format.

The Internet Object serialization format aims to redefine data interchange on the internet by addressing key challenges and limitations present in existing formats.

The inception of Internet Object began as a side project aimed at addressing limitations observed in the JSON format. Over time, it evolved into an independent research endeavor focused on tackling data-transfer challenges such as size, schema validation, data streaming, header and metadata support, and more. The design of the Internet Object format revolves around the following key objectives:

## Uninfluenced development

To optimize the format for internet wire transfer, Internet Object MUST be conceived and developed without being excessively influenced by existing mechanisms. However, it MAY draw inspiration from other formats as needed.

## Human friendly

Internet Object documents SHOULD be text-based, human-friendly, and easy to work with. Developers SHOULD be able to write these documents using plain-text IDEs without needing frameworks, libraries, or utilities.

## Minimal footprint

To ensure a small footprint, the Internet Object format SHOULD separate data and schema, allowing data to be sent alone over the network.

## Schema first

To uphold data integrity during wire transfer, the Internet Object format SHOULD prioritize a schema-first approach.

## Document oriented

Embracing a comprehensive document-oriented approach, the Internet Object format SHOULD facilitate the bundling of essential components—including records, data, definitions, schemas, and comments—within a single document. This approach helps keep related information together, improving organization and maintainability.

## Complex data types

Internet Object MUST support complex data types so that large numbers and complex data structures can be serialized and deserialized efficiently for the wire.

## Streaming friendly

The Internet Object format SHOULD support streaming of independent records, allowing for efficient and continuous data transfer. The failure of a single record MUST NOT affect the processing of other records.

## Platform and language independence

The Internet Object format SHOULD work seamlessly across platforms, operating systems, and programming languages to ensure broad adoption and versatility.

## Comments

By supporting inline comments, the Internet Object format allows users to document schemas and definitions directly within the data itself. This feature enhances readability and maintainability.

## Reusability

To increase adaptability, Internet Object SHOULD promote reusability through references and variables. This capability enables customization of data structures and more effective data manipulation.

## See Also

* [Abstract](/internet-object/abstract) — the format in one paragraph
* [Introducing Internet Object](/internet-object/introduction) · [Why Internet Object?](/internet-object/why-internet-object)
* [The Poetic Principles](/internet-object/the-zen-of-internet-object) — the same goals in verse


# Introducing Internet Object

A guided walkthrough of Internet Object and how it compares to JSON.

Internet Object (IO) is a document-oriented data serialization format designed to optimize data transmission over networks. This specification introduces IO as an alternative to existing formats such as JSON, offering a structured approach to data representation and exchange.

## Core structure

The fundamental structure of IO is an ordered collection of values, analogous to CSV (Comma-Separated Values) but with extended capabilities. These capabilities include support for nested objects, arrays, and inline keys, providing enhanced expressiveness and flexibility.

## Key features

* **Document-oriented design**: In contrast to value-oriented formats, IO adopts a document-centric approach, facilitating the separation of data from definitions to enhance clarity and maintainability.
* **Ordered collection with extended functionality**: IO's core structure maintains an ordered collection of values while supporting complex data structures such as nested objects and arrays.
* **Schema-first approach**: IO emphasizes schema-first design to ensure data consistency and predictability. While schemas are optional, their inclusion significantly enhances data integrity and validation.
* **Concise syntax**: The syntax of IO is optimized for readability and efficiency, minimizing data size without compromising clarity.
* **Metadata integration**: IO documents can incorporate metadata, variables, and multiple schemas within the header section, providing comprehensive context for the data.

## Illustrative examples

### Basic IO document structure

The following example demonstrates a basic IO document structure:

```ruby
name, age, active, address: {street, city}
---
John Doe, 25, T, {Bond Street, New York}
```

This structure illustrates IO's concise syntax and inherent schema support. For comparison, an equivalent JSON representation would be:

```json
{
  "name": "John Doe",
  "age": 25,
  "active": true,
  "address": {
    "street": "Bond Street",
    "city": "New York"
  }
}
```

### IO document with collections and data types

IO supports collections and various data types, as demonstrated in the following example:

```ruby
name:string, age:int, active:bool, address: {street:string, city:string}
---
~ John Doe, 25, T, {Bond Street, New York}
~ Jane Doe, 20, T, {Main Street, San Francisco}
```

This example illustrates several key features:

* Explicit data type definitions in the schema (`string`, `int`, `bool`)
* Nested object structures (`address`)
* Collection of objects denoted by the tilde (`~`) prefix
* Correspondence between the order of values and the schema definition

The equivalent JSON representation would be:

```json
[
  {
    "name": "John Doe",
    "age": 25,
    "active": true,
    "address": {
      "street": "Bond Street",
      "city": "New York"
    }
  },
  {
    "name": "Jane Doe",
    "age": 20,
    "active": true,
    "address": {
      "street": "Main Street",
      "city": "San Francisco"
    }
  }
]
```

This comparison demonstrates IO's capacity to represent structured data collections efficiently, offering a compact and readable format while maintaining an ordered structure.

## Advanced examples

### Separate schema and document with collection

In many scenarios, it is beneficial to define schemas separately from the data. This approach allows for schema reuse, versioning, and easier maintenance. Here is an example of a separate schema followed by a document using that schema:

First, the schema, defined on its own (for example, in a file named `person.io`):

```ruby
# Person schema
name:string, age:int, active:bool, address: {street:string, city:string}, skills:[string]
```

Then, a document that carries metadata in its header and a collection of records below:

```ruby
~ schemaUrl: "https://example.com/schemas/person.io"
~ recordCount: 3
~ page: 1
~ totalPages: 1
---
~ John Doe, 25, T, {Bond Street, New York}, [JavaScript, Python]
~ Jane Doe, 30, F, {Main Street, San Francisco}, [Java, C++, Rust]
~ Bob Smith, 28, T, {Park Avenue, Chicago}, [Ruby, Go]
```

In this example:

* The schema is defined separately, potentially in a file named `person.io`.
* The document references the schema URL in its metadata.
* The document includes additional metadata such as record count and pagination information.
* The collection contains multiple records, each prefixed with `~`.
* Each record follows the structure defined in the schema, including an array of skills.

This structure allows for efficient data transmission, as the schema only needs to be sent once and can be cached by the receiving system. It also facilitates updates to the schema without necessarily changing the data format.

## Conclusion

Internet Object represents a significant advancement in data serialization technology. By combining the simplicity of ordered collections with the robustness of schema-based validation, Internet Object offers a powerful yet accessible solution for modern data exchange needs. Its key strengths include:

1. Efficiency in data transmission and storage
2. Clarity through its schema-first approach and document-oriented design
3. Flexibility in handling various data structures and types
4. Compatibility with existing JSON-based systems

These attributes make Internet Object suitable for a wide range of applications, from web-based and networked environments to data storage and interchange in diverse domains such as IoT, cloud computing, and enterprise systems.

The subsequent sections of this specification provide comprehensive details on Internet Object's syntax, schema definition language, supported data types, and advanced features. This information enables developers, system architects, and data engineers to fully leverage the capabilities of Internet Object in their projects and applications.

## See Also

* [Getting Started](/internet-object/getting-started) — a five-minute tour in pure Internet Object
* [Why Internet Object?](/internet-object/why-internet-object) — how it compares to JSON, CSV, and YAML
* [Internet Object Document](/structure-and-syntax/introduction) — the structure in depth


# Why Internet Object?

Why choose Internet Object over JSON, CSV, YAML, and binary formats.

Internet Object is a text-based, schema-first data format for interchange over the internet. It keeps JSON's readability while removing its biggest costs: repeated keys, no schema, no comments, and no native streaming.

## The core idea

The same data, in JSON and in IO:

```json
[
  { "name": "John Doe", "age": 30, "email": "john@example.com", "active": true },
  { "name": "Jane Doe", "age": 25, "email": "jane@example.com", "active": false }
]
```

```ruby
~ $schema: { name: string, age: int, email: email, active: bool }
---
~ John Doe, 30, john@example.com, T
~ Jane Doe, 25, jane@example.com, F
```

The keys are stated once in the schema, so each record carries only its values. For collections of similar objects this is dramatically smaller — closer to CSV's density, but with types, nesting, and validation.

## How it compares

| Need                                | JSON | CSV | YAML    | Internet Object |
| ----------------------------------- | ---- | --- | ------- | --------------- |
| Human-readable                      | ✓    | ✓   | ✓       | ✓               |
| No repeated keys                    | ✗    | ✓   | ✗       | ✓               |
| Built-in schema & validation        | ✗    | ✗   | ✗       | ✓               |
| Nested / structured data            | ✓    | ✗   | ✓       | ✓               |
| Comments                            | ✗    | ✗   | ✓       | ✓               |
| Streaming of records                | ✗    | ✓   | ✗       | ✓               |
| Precise numerics (bigint, decimal)  | ✗    | ✗   | ✗       | ✓               |
| Dates/times & binary as first-class | ✗    | ✗   | partial | ✓               |

## What you gain

* **Smaller payloads** — keys live in the schema, not in every record.
* **Validation built in** — types and constraints travel with the data; bad values are reported with precise errors.
* **Comments** — annotate documents inline with `#`.
* **Collections & streaming** — emit and consume records one at a time.
* **Richer types** — `int`/`uint`/`decimal`/`bigint`, `date`/`time`/`datetime`, `binary`, and reusable named types.
* **JSON-compatible where it counts** — quoted-key object syntax is accepted, easing migration. See [JSON Compatibility](/interoperability/json-compatibility).

## When JSON is still fine

For one-off, schema-less, small payloads — or where ubiquitous tooling matters most — JSON is perfectly adequate. Internet Object pays off when you have **many similar records**, want **validation**, or care about **size and streaming**.

## See Also

* [Getting Started](/internet-object/getting-started) · [Objectives](/internet-object/objectives)
* [JSON Compatibility](/interoperability/json-compatibility)


# Getting Started

A short, language-agnostic tour of Internet Object in pure IO.

This is a five-minute tour of Internet Object (IO) using the format itself — no programming language required.

## 1. A single object

The simplest document is one object. Fields are comma-separated; the header line names them:

```ruby
name: string, age: int, email: email
---
John Doe, 30, john@example.com
```

Above the `---` is the **header** (here, the schema); below it is the **data**. Because the schema fixes the field order, the data is just values — no repeated keys.

## 2. A schema and a collection

Define the schema once in the header with `$schema`, then stream many records, each beginning with `~`:

```ruby
~ $schema: { name: string, age: int, email: email, active: bool }
---
~ John Doe, 30, john@example.com, T
~ Jane Doe, 25, jane@example.com, F
```

`T`/`F` are booleans. A collection of records shares one schema — compact and validated.

## 3. Constraints

Fields can carry constraints. Invalid data is reported, not silently accepted:

```ruby
~ $schema: { name: string, age: { int, min: 0, max: 120 } }
---
~ John, 30      # ✓
~ Mary, 200     # ✗ invalid-range
```

## 4. Nesting and reuse

Define a shape once and reference it with `$`:

```ruby
~ $address: { street, city }
~ $schema: { name: string, address: $address }
---
~ John, { Main St, NYC }
```

## 5. Metadata

Header keys without a prefix carry document metadata, kept separate from the data:

```ruby
~ count: 2
~ $schema: { name, age: int }
---
~ John, 30
~ Jane, 25
```

## Where to next

* [Why Internet Object?](/internet-object/why-internet-object) — how it compares to JSON and others
* [Internet Object Document](/structure-and-syntax/introduction) — header and data in depth
* [Internet Object Schema](/schema-definition-language/internet-object-schema) — the schema language
* [Collections](/collections/collection) — records and streaming


# Document-Oriented Nature

The document as the unit of exchange — header, data, and sections.

Internet Object is **document-oriented**: the unit of exchange is a self-contained *document*, not a bare value or a loose row. A single document bundles three things that other formats usually keep apart — the **schema** that describes the data, the **data** itself, and any **metadata** about it — into one stream. A `---` separator divides the document into two regions: a **header** and a **data** section.

```ruby
~ count: 2
~ $schema: { name: string, age: int }
---
~ John, 30
~ Jane, 25
```

Everything above `---` is the header (here, a `count` metadatum and the schema); everything below is the data (two records). The header is read once and governs all the data that follows.

## Anatomy of a document

A document is two regions separated by a single `---`:

* **Header** — information *about* the data: the schema, reusable definitions, and metadata.
* **Data** — the values themselves: one object, or a collection of records.

The header is **optional**. The simplest document is just a value, with no header and no separator at all:

```ruby
John, 30
```

As soon as you need a schema, definitions, or metadata, you add a header and close it with `---`. A document may also be header-*only* (a header followed by `---` with no data) — useful for sending a schema or configuration on its own.

## The header — information about the data

Each header entry sits on its own line, introduced by a tilde `~`. The header carries three kinds of thing:

* **Schema** — the shape and types of the data. The reserved key `$schema` names the document's *default* schema.
* **Definitions** — reusable building blocks: value variables (`@name`) and references (`$name`) that the schema or data can point to.
* **Metadata** — plain keys such as `count`, `status`, or paging fields. Metadata describes the payload and is surfaced separately from the data, not mixed into it.

Because the header is parsed once and then applied to every record, the cost of describing the data is paid a single time, no matter how many records follow. See [Definitions](/definitions/definitions) for the full header model.

## The data — the values

The data section holds either a **single object** or a **collection** of records, each record introduced by `~`. The defining trait is what the records *don't* carry: since the field names and types live in the header, each record carries only **values**, not repeated keys.

```ruby
~ $schema: { name: string, age: int, city: string }
---
~ John, 30, Phoenix
~ Jane, 25, Dallas
```

Compare this to repeating `"name":`, `"age":`, and `"city":` on every record, as a key-per-value format would. With no schema at all, the values are still accepted and mapped to positional keys (`0`, `1`, `2`, …). See [Data Sections](/structure-and-syntax/introduction/data) and [Collection](/collections/collection).

## One document, many sections

A document is not limited to a single dataset. Additional `---` separators introduce further **sections**, each able to name its own schema — so related datasets travel together in one document:

```ruby
~ $person: { name, age: int }
~ $address: { street, city }
--- $person
~ John, 30
~ Jane, 25
--- $address
~ Main St, NYC
```

Each section may carry a name, a schema, or both; an unnamed section takes the default name `data`. This makes one document a natural container for, say, a result set plus its lookup tables, or several record types from one API response. The precise rules for naming and selecting section schemas are in [Data Sections](/structure-and-syntax/introduction/data).

## Self-contained and self-describing

Because a document carries its own schema and metadata, it is **self-describing**: a receiver can understand and validate exactly what was sent, with no out-of-band agreement.

> **Versus JSON.** JSON transmits data but has no place for a schema or document-level metadata — the contract is shipped and versioned separately, and the two can drift apart. An Internet Object document keeps the contract and the data in one stream.

> **Versus CSV.** CSV has rows but no types, no nesting, and no metadata. Internet Object records are typed by the header and may nest objects and arrays, while staying just as compact row-to-row.

For a fuller comparison, see [Why Internet Object?](/internet-object/why-internet-object).

## Why it matters

* **Separation of concerns** — structure, metadata, and data are stated in distinct regions, so each can be read and reasoned about on its own.
* **Compactness** — keys and types are declared once in the header; records repeat values, not names.
* **Self-describing & portable** — schema, metadata, and data move as one unit, so a document validates itself wherever it lands.
* **Streaming** — once the header is read, records can be produced and consumed incrementally, one at a time, without waiting for the whole document.
* **Many datasets, one document** — sections bundle related data without inventing an envelope format.

## See Also

* [Internet Object Document](/structure-and-syntax/introduction) — the document in depth
* [Header](/structure-and-syntax/introduction/header) · [Data Sections](/structure-and-syntax/introduction/data)
* [Schema-First Design](/core-concepts/schema-first) — the other half of the model
* [Why Internet Object?](/internet-object/why-internet-object) — how it compares to JSON, CSV, and YAML


# Schema-First Design

The schema-first philosophy — same-syntax schemas, progressive typing, and reuse.

Internet Object is **schema-first**: you declare the shape of the data up front and the data conforms to it. Where some formats leave structure implicit (JSON infers it from each value) or external (JSON Schema lives in a separate file and language), Internet Object writes the schema in the **same object syntax as the data** — there is no second language to learn: if you can write the data, you can write its schema. That schema can travel inside the document or be shared between endpoints (see [Where the schema lives](#where-the-schema-lives)).

Declaring the shape first is the idea that makes the rest of the format possible. Once the **structure** — the keys and their types — lives in the schema, the data no longer has to carry it: each record holds only **values**, while the names and types stay in one place. And because every value now has a declared type and constraints, the format can **validate** the data against that shape. Separating the data from its structure and validating it are not two unrelated features — they are both direct consequences of putting the schema first.

## Why declare a schema first

Putting the shape first changes what the format can do for you:

* **Validation** — every value is checked against its type and constraints. Errors are precise: reported per field and per record, each with a stable [error code](/parsing-and-errors/error-model).
* **Compactness** — field names and types are stated once in the header instead of being repeated on every record, so the data section stays terse.
* **Self-documentation** — the schema is a precise, readable contract that describes the data better than prose can, and travels with it.
* **Tooling** — a declared shape is what lets editors complete fields, generators emit types, and converters map cleanly to and from other formats.
* **Fewer ambiguities** — a value's type and meaning are fixed by the schema, not guessed from how it happens to be written.

## A schema is just an object

A schema is written with the same grammar as data — members, positional or keyed, nesting, and arrays. Each member of the schema describes the corresponding value of each record:

```ruby
~ $schema: { name: string, age: int }
---
~ John, 30
```

Here the schema has two members, `name` and `age`. The record supplies two positional values, which map in order: `John` → `name`, `30` → `age`. The reserved key `$schema` marks this object as the document's default schema.

## Progressive typing

You adopt **as much structure as you need**, and tighten it over time without changing the data's shape. The same field can be untyped, typed, or typed and constrained:

```ruby
# untyped — accepts any value
name, age

# typed
name: string, age: int

# typed and constrained
name: { string, maxLen: 100 }, age: { int, min: 0, max: 120 }
```

On top of the type, **member modifiers** express optionality, nullability, defaults, and allowed values:

```ruby
name: string, nickname?: string, age*: int, role: { string, guest, [guest, admin, owner] }
```

`nickname?` is optional (it may be omitted), `age*` is nullable (it may be `null`), and `role` has a default of `guest` and is restricted to the listed `choices`. Start loose while prototyping; move to typed and constrained schemas for production — the data you already have keeps working. The full rules live in [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef) and [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef).

## Where the schema lives

A schema-first format does **not** require the schema to be embedded in every document. Two deployment modes are both first-class, and you choose per use case.

**Embedded (self-contained).** The schema sits in the document header, so the document is self-describing and self-validating — a receiver validates exactly what was sent, with no prior agreement. Best for storage, archival, logs, and APIs where the shape can vary.

```ruby
~ $schema: { name: string, age: { int, min: 0, max: 120 } }
---
~ John, 30      # ✓
~ Mary, 200     # ✗ invalid-range
```

**Shared (out-of-band).** A publisher and a subscriber can agree on the schema once, at their endpoints, and then move only **data** on the wire. Each message carries just its data section; its header, if present, holds metadata and definitions — but not the schema — and the consumer validates against the schema it already holds. This is the most compact mode and suits high-volume streaming between known parties.

```ruby
~ count: 2
---
~ John, 30
~ Mary, 25
```

Either way the data conforms to the *same* schema, written in the same syntax; only its location differs. And in both modes each record is validated **independently**, so one bad record does not invalidate the others — the processor reports the failure and keeps going. See the [Validation Model](/conformance/validation-model) for the parse → validate → load pipeline, and [Data Streaming](/collections/data-streaming) for the streaming case.

## Reuse and composition

Shapes and values are defined once and referenced by name, keeping schemas DRY. A reference (`$name`) names a reusable shape; the schema then points at it wherever that shape recurs:

```ruby
~ $address: { street, city }
~ $schema: { name: string, home: $address, office?: $address }
---
~ John, { Main St, NYC }, { 5th Ave, NYC }
```

`home` and `office` both reuse the `$address` shape, defined in one place. References resolve after the whole header is read, so their order is not significant. See [Schema References](/definitions/schema-references) and [Open & Dynamic Schemas](/schema-definition-language/advanced-schema-concepts/dynamic-schema).

## Schema-first, not schema-required

Schema-first is the *recommended* default, not a hard requirement. A document with no schema is still valid — its values are simply accepted and mapped to positional keys (`0`, `1`, `2`, …):

```ruby
---
~ John, 30
~ Jane, 25
```

This is handy for quick, exploratory, or fully self-evident data. Reach for an explicit schema once the data has a stable shape, leaves your control, or needs validation — see [Best Practices & Guidelines](/best-practices/best-practices).

## See Also

* [Document-Oriented Nature](/core-concepts/document-oriented) — the other half of the model
* [Internet Object Schema](/schema-definition-language/internet-object-schema) — the schema language in full
* [Schema References](/definitions/schema-references) · [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)
* [Why Internet Object?](/internet-object/why-internet-object) — how it compares to JSON, CSV, and YAML


# Internet Object Document

The two-part structure of an Internet Object document — header and data.

Internet Object is a document-oriented format built on a clear separation between a **header** and **data**. This mirrors how HTTP and MIME keep headers apart from the message body: the header describes the payload, and the data carries it.

The header is optional and, when present, holds schemas and definitions. The data section begins with the `---` separator. That separator is the boundary between the two parts: when a header is present, `---` is required to mark where it ends and the data begins.

## Document shapes

A document can take one of a few shapes depending on whether it carries a header, data, or both.

### Full document

A document with both a header and a data section is a *full document*. The header declares the schema; the data conforms to it.

```ruby
name, age: int, address: {street, city, state}, active
---
John Doe, 25, {Bond Street, New York, NY}, T
```

### Data-only document

When the schema is not needed — or is already known to the recipient — a document can carry data alone. With a single object, the leading `---` is optional:

```ruby
---
John Doe, 25, {Bond Street, New York, NY}, T
```

A collection of records can be written without a separator at all; each record begins with `~`:

```ruby
~ John Doe, 25, {Bond Street, New York, NY}
~ Jane Doe, 48, {Malibu Point 10880, Malibu, CA}
```

### Header-only document

Sometimes a request yields no rows — for example, a query that returns result metadata but an empty result set. The header carries the metadata, and the `---` separator marks an empty data section:

```ruby
~ recordCount: 0
~ pageSize: 10
~ currentPage: 1
~ nextPage: N
~ prevPage: N
---
```

### Document with multiple sections

A single document can hold multiple data sections, letting related datasets travel together. Each section starts with its own `---` separator and names the schema it uses:

```ruby
~ $address: {street, city, state, zip}
~ $person: {firstName, lastName, age, gender}
--- $person
~ John, Doe, 25, M
~ Jane, Doe, 22, F
--- $address
~ Bond Street, New York, NY, 500001
~ George Street, New York, NY, 500002
```

## See Also

* [Header](/structure-and-syntax/introduction/header) — schemas, definitions, and metadata
* [Data Sections](/structure-and-syntax/introduction/data) — separators, objects, and collections
* [Document-Oriented Nature](/core-concepts/document-oriented) — why the split exists


# Header

The header section — schemas, definitions, variables, and metadata.

The header sits at the beginning of an Internet Object document and defines the schema and definitions for the data that follows. It carries the metadata, context, variables, and schema references needed to interpret the data consistently. By stating this information once, up front, the header keeps the data section compact and unambiguous.

## Default schema

A schema defines the structure and meaning of the data in a document. When the header contains only a schema — with no other definitions — that schema is the document's *default schema*. It describes the shape of the data while keeping the structure separate from the data itself, which makes the data more compact and easier to process.

```ruby
name, age: int, address, isActive?, remark
---
```

This header declares five members:

1. **`name`** — an untyped member, typically a string.
2. **`age: int`** — an explicitly typed member that must hold an integer.
3. **`address`** — an untyped member that may hold a string or a nested object.
4. **`isActive?`** — the `?` suffix marks the member as optional; it may be omitted from the data.
5. **`remark`** — an untyped member, typically a free-text note.

Alongside the structure, the schema records type annotations and optionality, which sharpens validation and documents the data model in one place. For the full schema syntax, see [the Internet Object schema](/schema-definition-language/internet-object-schema).

## Definitions

Definitions are key-value pairs declared in the header to hold metadata, variables, reusable schemas, and other shared values. Each definition is written on its own line, prefixed with `~`.

```ruby
~ pageSize: 1
~ currentPage: 1
~ recordCount: 4
~ $address: {street, city, state}
~ $schema: {name, age, $address}
---
```

Here the header mixes response metadata with schema definitions instead of using a default schema. The metadata records the page size (`pageSize`), the current page (`currentPage`), and the total record count (`recordCount`). It also defines a reusable address shape (`$address`) with the members `street`, `city`, and `state`, and a top-level schema (`$schema`) that references it. The `$schema` key is reserved: it names the default schema applied to the data section.

For the full treatment of metadata, value variables (`@`), and references (`$`) — including how they are resolved — see the [Definitions](/definitions/definitions) chapter.

## See Also

* [Data Sections](/structure-and-syntax/introduction/data) — what follows the `---` separator
* [Definitions](/definitions/definitions) — variables and schema references in depth
* [Internet Object Schema](/schema-definition-language/internet-object-schema) — the schema language


# Data Sections

The data section — section separators, objects, and collections.

The data section is where the actual data of an Internet Object document resides. A document can have one or more data sections, each introduced by a separator line (`---`) and optionally labelled with a section name and schema. The data itself is either a single object or a collection of objects, giving a flexible yet structured way to represent information. The diagram below shows the shape of a data section.

![Internet Object document data section structure](/files/DNJ5W9wts5HgZVhdJt1z)

## Structure overview

### Section separator line

Each data section begins with a separator line (`---`) that divides the document into distinct sections. The separator can carry two optional elements:

* **Section name** — identifies the section and its purpose.
* **Schema name** — names the schema that constrains the section, prefixed with `$`.

> **Separator line.** The separator line must end with a newline (`\n`) or EOF (end of file).

The separator can take several forms, from least to most detailed, each ending with a newline (`\n`) or EOF:

* **Without name and schema** — the simplest form, just the separator (`---`).
* **With section name** — the separator followed by a name (`--- employee`).
* **With section name and schema** — a name and schema name, separated by a colon (`--- employee : $employee`).
* **With only schema** — the separator followed by just the schema name (`--- $employee`).

### Rules for section names and schemas

* **Omitting the section name** — in a multi-section document, the section name may be omitted only once. When omitted, the name is derived from the associated schema (e.g. `--- $employee` implies the section name `employee`).
* **Default section name and schema** — if both the name and schema are omitted, the section name defaults to `data` and the document's default schema is used.
* **Unique section names** — each section must have a unique name; duplicate names are not allowed.

### Examples of section separators

#### Separator line without name and schema

The simplest form. It uses the default section name (`data`) and the document's default schema.

```ruby
---
~ John Doe, 25, Male, {Bond Street, New York, NY}, [agile, swift]
~ Jane Doe, 20, Male, {Duke Street, New York, NY}
```

#### Separator line with a section name

Here the section name is `employee`. The schema is the document's default schema.

```ruby
--- employee
~ John Doe, 25, Male, {Bond Street, New York, NY}, [agile, swift]
~ Jane Doe, 20, Male, {Duke Street, New York, NY}
```

#### Separator line with a section name and schema

Here both the name and schema are stated explicitly, as `employee` and `$employee`.

```ruby
--- employee : $employee
~ John Doe, 25, Male, {Bond Street, New York, NY}, [agile, swift]
~ Jane Doe, 20, Male, {Duke Street, New York, NY}
```

#### Separator line with only a schema

Here only the schema is named. The section name is derived from the schema name (`employee`). If that name is already used elsewhere in the document, it is an error.

```ruby
--- $employee
~ John Doe, 25, Male, {Bond Street, New York, NY}, [agile, swift]
~ Jane Doe, 20, Male, {Duke Street, New York, NY}
```

### Data

After the separator line comes the data. It is either a single object or a collection of objects — the flexibility that lets the format carry many kinds of information efficiently.

#### Objects

Objects are structured entities composed of key-value pairs. Each object is written within curly braces `{}` and may contain nested objects or other values, forming a hierarchy.

#### Collections

Collections are lists of objects, allowing multiple records within one data section. Each object in a collection is written the same way as a standalone object but belongs to the broader collection. See [Collection](/collections/collection) for record syntax, type promotion, and validation rules.

### Examples of data

#### Single object

A single object can follow the separator directly.

```ruby
---
John Doe, 25, Male, {Bond Street, New York, NY}, [agile, swift]
```

A single-section document with no header or schema does not need a separator, so the example above can also be written as:

```ruby
John Doe, 25, Male, {Bond Street, New York, NY}, [agile, swift]
```

#### Collection of objects

A collection lists objects, each prefixed with `~` on its own line:

```ruby
---
~ John Doe, 25, Male, {Bond Street, New York, NY}, [agile, swift]
~ Jane Doe, 20, Male, {Duke Street, New York, NY}
```

### Empty data section

A data section may be empty — just the separator line with no data.

```ruby
---
```

An entirely empty document needs no separator at all.

```ruby
```

### Multi-section document example

A document can include multiple sections, each with its own data:

```ruby
--- $library
# Bookville Library
City Central Library, "123 Library St, Bookville"

--- $books
~ The Great Gatsby, "F. Scott Fitzgerald", 1234567890, T, [Fiction, Classic], 1925
~ "1984", George Orwell, 2345678901, F, [Fiction, Dystopian], 1949, { user123, d"2024-02-20"}

--- subscribers: $users
~ user123, John Doe, Standard, [{2345678901, d"2024-01-20"}]
~ user456, Jane Smith, Premium, []
```

Organized by separators and built from objects and collections, the data section offers a robust, flexible way to carry data — keeping documents clear, consistent, and effective across a wide range of applications.

## See Also

* [Header](/structure-and-syntax/introduction/header) — what precedes the `---` separator
* [Objects](/structure-and-syntax/values/object) · [Arrays](/structure-and-syntax/values/array) — value syntax
* [Collections](/collections/collection) — records and collection rules


# Structural Elements

The characters and tokens that structure and delimit an Internet Object document.

The Internet Object format uses a small set of structural characters, literals, and other special characters to structure and delimit data. Working together with objects, strings, arrays, numbers, and whitespace, these elements compose the format's grammar and let documents express complex, flexible data structures.

## Categories

* [Structural Characters & Separators](/structure-and-syntax/structural-elements/structural-characters-n-keywords) — core syntax characters that organize data
* [Literals](/structure-and-syntax/structural-elements/literals) — predefined constant values (booleans, null, special numbers)
* [Other Special Characters](/structure-and-syntax/structural-elements/other-special-characters) — functional modifiers for variables, schemas, and values
* [Whitespace & Indentation](/structure-and-syntax/structural-elements/whitespaces) — recognized Unicode whitespace and its handling

## See Also

* [Value Representations](/structure-and-syntax/values) — data types and how values are written
* [Comments](/structure-and-syntax/comments) — comment syntax and usage
* [Encoding](/structure-and-syntax/encoding) — character encoding and Unicode support


# Structural Characters & Separators

The core characters that organize and delimit data in an Internet Object document.

Structural characters define the syntax and organization of data within an Internet Object document. They form the foundation of the format's grammar and control how data is parsed and interpreted.

## Character set

| Symbol | Name                 | Unicode  | Function            | Context                                         |
| ------ | -------------------- | -------- | ------------------- | ----------------------------------------------- |
| `,`    | Comma                | `U+002C` | Value separator     | Separates items in arrays and objects           |
| `~`    | Tilde                | `U+007E` | Record delimiter    | Marks the start of a new record in a collection |
| `:`    | Colon                | `U+003A` | Key-value separator | Separates a key from its value                  |
| `[`    | Open square bracket  | `U+005B` | Array start         | Begins an array                                 |
| `]`    | Close square bracket | `U+005D` | Array end           | Ends an array                                   |
| `{`    | Open curly bracket   | `U+007B` | Object start        | Begins an object                                |
| `}`    | Close curly bracket  | `U+007D` | Object end          | Ends an object                                  |
| `---`  | Triple hyphen        | `U+002D` | Section separator   | Separates the header and data sections          |
| `#`    | Hash                 | `U+0023` | Comment delimiter   | Starts a single-line comment                    |
| `"`    | Double quote         | `U+0022` | String delimiter    | Encloses a string value                         |
| `'`    | Single quote         | `U+0027` | String delimiter    | Alternative string delimiter                    |

## Usage examples

### Basic structure

```ruby
# Surrounding braces define the object,
# and key-value pairs are separated by colons and commas
~ { name: "John Doe", age: 30, active: true }

# An array encloses its values in square brackets
~ [ "item1", "item2", "item3" ]
```

### Collections and records

```ruby
# Schema definition
~ $person: {name: string, age: number}
# Triple hyphens separate the header from the data
---
# A tilde marks each new record in the collection
~ "Alice", 25
~ "Bob", 30
~ "Charlie", 35
```

### String delimiters

```ruby
{
    message1: "Hello World",      # Double-quoted string
    message2: 'Hello World',      # Single-quoted (equivalent)
    quote: "She said \"hi\""      # Escape an inner quote with a backslash
}
```

For values that contain many backslashes — such as Windows paths or regular expressions — use a [raw string](/structure-and-syntax/values/string/raw-strings) (`r"C:\Temp\new"`), where backslashes are literal.

## Structural rules

* **Balanced delimiters** — every opening bracket or brace must have a matching closing one.
* **Proper nesting** — structures may nest but must preserve a well-formed hierarchy.
* **Separator consistency** — commas separate elements at the same structural level.
* **Section division** — triple hyphens (`---`) separate the header and data sections.
* **Comment scope** — a hash (`#`) comment extends to the end of the line only.
* **String equivalence** — single and double quotes are functionally equivalent.

## See Also

* [Other Special Characters](/structure-and-syntax/structural-elements/other-special-characters) — variable, schema, and sign modifiers
* [Literals](/structure-and-syntax/structural-elements/literals) — predefined constant values
* [Comments](/structure-and-syntax/comments) — comment syntax and usage
* [The Structure](/collections/collection) — collection structure and records


# Literals

Predefined constant values — booleans, null, and special numbers.

Literals are predefined constant values that represent common data states and special values. They offer a concise way to express boolean values, null states, and special numeric values without quotes or extra syntax.

## Supported literals

Internet Object supports the following literals:

| Literal | Type    | Represents                      | Case sensitive |
| ------- | ------- | ------------------------------- | -------------- |
| `true`  | Boolean | True value                      | Yes            |
| `T`     | Boolean | True value (short form)         | Yes            |
| `false` | Boolean | False value                     | Yes            |
| `F`     | Boolean | False value (short form)        | Yes            |
| `null`  | Null    | Null / empty value              | Yes            |
| `N`     | Null    | Null / empty value (short form) | Yes            |
| `Inf`   | Number  | Positive infinity               | Yes            |
| `-Inf`  | Number  | Negative infinity               | Yes            |
| `NaN`   | Number  | Not a Number                    | Yes            |

## Examples

```ruby
# Boolean literals
~ isActive: true, verified: F, isDeleted: false, visible: T

# Null literals
~ middleName: null, nickname: N

# Special numeric literals
~ maxValue: Inf, minValue: -Inf, result: NaN
```

## Rules

* **Case sensitive** — literals must use exact case; `True`, `FALSE`, and `NULL` are invalid.
* **No quotes** — literals are written without quotes; quoting one makes it an ordinary string.
* **Short forms** — `T`, `F`, and `N` are single-letter shortcuts for `true`, `false`, and `null`.

## See Also

* [Booleans](/structure-and-syntax/values/booleans) — the boolean type in detail
* [Nulls](/structure-and-syntax/values/null) — null and optional values
* [NaN and Infinity](/structure-and-syntax/values/number/nan-and-infinity) — special numeric values


# Other Special Characters

Functional modifiers — variable, schema, optional, nullable, and sign characters.

Special characters work alongside structural characters and literals to add functionality or context to an Internet Object document. Each has a specific semantic meaning and modifies the behavior of schemas, values, or parsing.

## Special character set

| Symbol | Name           | Unicode  | Context  | Application                                                                                         |
| ------ | -------------- | -------- | -------- | --------------------------------------------------------------------------------------------------- |
| `@`    | At sign        | `U+0040` | Variable | Prefixed to a name, declares or references a variable                                               |
| `$`    | Dollar sign    | `U+0024` | Schema   | Prefixed to a name, declares or references a schema                                                 |
| `?`    | Question mark  | `U+003F` | Schema   | Suffixed to a member name, marks the member optional                                                |
| `*`    | Asterisk       | `U+002A` | Schema   | Suffixed to a member name, marks the member nullable; also makes a schema accept undeclared members |
| `-`    | Hyphen / minus | `U+002D` | Numeric  | Marks a negative value                                                                              |
| `+`    | Plus           | `U+002B` | Numeric  | Marks a positive value                                                                              |

## Usage examples

### Variable references and schema definitions

```ruby
# Variable declarations
~ @r: red
~ @g: green
~ @b: blue
# A schema using variables in an inline constraint
~ $schema: {
    name: string,
    email: email,
    joiningDt: date,
    color: {string, choices: [@r, @g, @b]}
}
---
# Data using variable references
~ John Doe, 'john.doe@example.com', d'2020-01-01', @r
```

### Schema modifiers

```ruby
# Optional and nullable member declarations
~ $user: {
    name: string,          # Required member
    email?: string,        # Optional member (may be omitted)
    avatar*: string,       # Nullable member (may be null)
    metadata*?: object     # Optional and nullable member
}

# A schema that accepts undeclared members
~ $flexible: {
    id: string,
    name: string,
    *                      # Accept additional, undeclared members
}
```

### Numeric signs

```ruby
# Positive and negative numbers
temperature: +23.5         # Explicit positive
balance: -150.75           # Negative value
elevation: +8848           # Positive integer
debt: -5000                # Negative integer
```

## Character rules

* **Context sensitive** — a character's meaning depends on its position and context.
* **Variable prefix** — `@` prefixes variable declarations and references.
* **Schema prefix** — `$` prefixes schema definitions and references.
* **Schema suffixes** — `?` and `*` are suffixed to member names in a schema.
* **Numeric prefixes** — `+` and `-` prefix numeric values to indicate sign.
* **Case sensitive** — all special characters are case-sensitive.
* **Reserved usage** — these characters are reserved for their specific functions.

## See Also

* [Definitions](/definitions/definitions) — variables and schema references
* [Numeric Values](/structure-and-syntax/values/number) — numeric formatting and signs
* [Structural Elements](/structure-and-syntax/structural-elements) — overview of all structural characters


# Whitespace & Indentation

Recognized whitespace characters and how the parser treats them.

In the Internet Object format, whitespace is any character with a Unicode code point less than or equal to `U+0020` (the range `U+0000` to `U+0020`). This range covers both non-printable control characters and common whitespace such as the horizontal tab (`U+0009`), newline (`U+000A`), vertical tab (`U+000B`), form feed (`U+000C`), carriage return (`U+000D`), and space (`U+0020`).

Because the format is not whitespace-sensitive, indentation carries no meaning: it is ordinary whitespace between tokens. You may indent objects, arrays, and definitions freely for readability without changing how a document parses.

## EBNF definition

```ebnf
whitespace         = ascii_whitespace | unicode_whitespace ;

ascii_whitespace   = ? any character with Unicode code point U+0000 to U+0020 ? ;
unicode_whitespace = U+1680 | U+2000 | U+2001 | U+2002 | U+2003 | U+2004
                   | U+2005 | U+2006 | U+2007 | U+2008 | U+2009 | U+200A
                   | U+2028 | U+2029 | U+202F | U+205F | U+3000 | U+FEFF ;
```

Beyond the `U+0000`–`U+0020` range, the format also treats characters in the Unicode whitespace category as whitespace, such as the non-breaking space (`U+00A0`), em space (`U+2003`), and en space (`U+2002`). Recognizing these makes the format easier to work with in languages that use non-Latin scripts, such as Arabic, Chinese, or Japanese.

The format also recognizes the zero-width non-breaking space (`U+FEFF`) as whitespace. This character is often used as a byte order mark (BOM) in Unicode-encoded documents.

## Whitespace characters

The following table lists the valid whitespace characters:

| Code points          | Description                                  | Notes                                                                                     |
| -------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `U+0000` to `U+0020` | Space, line feed, carriage return, tab, etc. | Any character with code point `<= 0x20`. Includes the ASCII space and control characters. |
| `U+1680`             | Ogham space mark                             | Space used in Ogham script.                                                               |
| `U+2000`             | En quad                                      | Space equal to the width of the lowercase letter "n".                                     |
| `U+2001`             | Em quad                                      | Space equal to the width of the uppercase letter "M".                                     |
| `U+2002`             | En space                                     | Space equal to half the width of the em space.                                            |
| `U+2003`             | Em space                                     | Space equal to the width of the em space.                                                 |
| `U+2004`             | Three-per-em space                           | Space equal to one-third of an em space.                                                  |
| `U+2005`             | Four-per-em space                            | Space equal to one-quarter of an em space.                                                |
| `U+2006`             | Six-per-em space                             | Space equal to one-sixth of an em space.                                                  |
| `U+2007`             | Figure space                                 | Space equal to the width of a numeral.                                                    |
| `U+2008`             | Punctuation space                            | Space used for punctuation.                                                               |
| `U+2009`             | Thin space                                   | Space narrower than the regular space.                                                    |
| `U+200A`             | Hair space                                   | Very narrow space used for special purposes.                                              |
| `U+2028`             | Line separator                               | Separates lines of text.                                                                  |
| `U+2029`             | Paragraph separator                          | Separates paragraphs of text.                                                             |
| `U+202F`             | Narrow no-break space                        | Non-breaking space narrower than the regular space.                                       |
| `U+205F`             | Medium mathematical space                    | Space used in mathematical notation.                                                      |
| `U+3000`             | Ideographic space                            | Space used in East Asian scripts.                                                         |
| `U+FEFF`             | Byte order mark (BOM)                        | Zero-width non-breaking space, often used as a BOM.                                       |

## Rules

* **Whitespace insensitive** — the parser ignores whitespace surrounding values and structural elements.
* **Preserved inside strings** — whitespace within a value or string is kept exactly as written.
* **Recognized by code point** — whitespace is identified by Unicode code point, per the table above.
* **Reserved** — these whitespace characters must not appear within identifiers or keys.

## Best practices

* **Aid readability** — use spaces and tabs to format a document so it is easy to read.
* **Avoid clutter** — excessive whitespace adds no meaning and reduces readability.
* **Stay consistent** — apply whitespace uniformly across a document for easier maintenance.
* **Watch for invisible characters** — zero-width and other invisible spaces can slip into keys or values unnoticed; avoid pasting them in.

## See Also

* [Encoding](/structure-and-syntax/encoding) — Unicode handling and document encoding
* [Strings](/structure-and-syntax/values/string) — whitespace handling inside strings


# Value Representations

Overview of the value types Internet Object can represent.

Internet Object supports a rich set of **value types**, from simple scalars such as numbers and strings to structured values such as objects and arrays. Values are the fundamental building blocks of every document.

All values are designed to be:

* **Human-readable** — easy to read and write by hand.
* **Machine-parseable** — efficient to process.
* **Type-clear** — each value has an unambiguous type.
* **Expressive** — rich enough to model complex data.

## Value categories

### Scalar values

Scalar values represent single, atomic data:

* [Numbers](/structure-and-syntax/values/number) — integers, floating-point, and special numeric values
* [Strings](/structure-and-syntax/values/string) — text in open, regular, and raw forms
* [Booleans](/structure-and-syntax/values/booleans) — true / false values
* [Nulls](/structure-and-syntax/values/null) — the absence of a value
* [Binary](/structure-and-syntax/values/binary) — binary data encoded as Base64
* [Date and Time](/structure-and-syntax/values/date-and-time) — temporal values with ISO 8601 compatibility

### Structured values

Structured values contain other values:

* [Objects](/structure-and-syntax/values/object) — key-value pairs representing entities
* [Arrays](/structure-and-syntax/values/array) — ordered collections of values

## String types

Internet Object provides three [string forms](/structure-and-syntax/values/string) for different text scenarios:

| Form                                                                  | Syntax                 | Description                          | Typical use                     |
| --------------------------------------------------------------------- | ---------------------- | ------------------------------------ | ------------------------------- |
| [Regular string](/structure-and-syntax/values/string/regular-strings) | `"text"` or `'text'`   | Quoted string with escape sequences  | General text, user input        |
| [Open string](/structure-and-syntax/values/string/open-strings)       | `unquoted text`        | Unquoted string                      | Simple identifiers, plain words |
| [Raw string](/structure-and-syntax/values/string/raw-strings)         | `r"text"` or `r'text'` | Literal string, no escape processing | File paths, regex, code         |

## Numeric types

Internet Object supports several [numeric forms](/structure-and-syntax/values/number) for different precision and range needs:

| Form                                                                   | Syntax               | Description                    | Range                     |
| ---------------------------------------------------------------------- | -------------------- | ------------------------------ | ------------------------- |
| [Number](/structure-and-syntax/values/number/number)                   | `42`, `3.14`, `1e10` | Standard floating-point number | IEEE 754 double precision |
| [BigInt](/structure-and-syntax/values/number/bigint)                   | `42n`, `0x1ABn`      | Arbitrary-precision integer    | Unbounded                 |
| [Decimal](/structure-and-syntax/values/number/decimal)                 | `42.5m`, `3.14159m`  | High-precision decimal         | Configurable precision    |
| [Special values](/structure-and-syntax/values/number/nan-and-infinity) | `NaN`, `Inf`, `-Inf` | Non-finite numeric values      | IEEE 754 special values   |

## Temporal types

Internet Object has built-in [date and time values](/structure-and-syntax/values/date-and-time):

| Form     | Syntax                     | Description            | Example                        |
| -------- | -------------------------- | ---------------------- | ------------------------------ |
| Date     | `d'2024-03-20'`            | Date only              | `d'2024-03-20'`, `d'2024'`     |
| Time     | `t'14:30:45'`              | Time only              | `t'14:30:45.123'`, `t'09:00'`  |
| DateTime | `dt'2024-03-20T14:30:45Z'` | Combined date and time | `dt'2024-03-20T14:30:45.123Z'` |

## Binary data

For binary data, Internet Object uses a [Base64 byte string](/structure-and-syntax/values/binary) (`b'SGVsbG8='`), an efficient way to carry bytes as text.

## Value syntax at a glance

```ruby
# Scalar values
42                                     # Number
"Hello, World!"                        # Regular string
'Single quotes work too'               # Regular string
unquoted string                        # Open string
r"C:\Users\file.txt"                   # Raw string
true                                   # Boolean
false                                  # Boolean
null                                   # Null
b'SGVsbG8gV29ybGQ='                    # Base64 byte string
d'2024-03-20'                          # Date
t'14:30:45'                            # Time
dt'2024-03-20T14:30:45Z'               # DateTime

# Structured values
{ name: "John Doe", age: 30, active: true }   # Object
[1, 2, 3, "four", true]                       # Array
```

## Type handling

Internet Object maintains strict type boundaries:

* **No implicit conversion** — values keep their declared types.
* **Syntax-driven typing** — a value's type follows from how it is written.
* **Validation** — type constraints are enforced during validation, against the schema.

## Comments and whitespace

Values can be annotated with comments and laid out with whitespace for readability:

```ruby
{
  # User information
  name: "John Doe",    # Full name
  age: 30,             # Age in years

  # Contact details
  email: "john@example.com"
}
```

## See Also

* [Internet Object Document](/structure-and-syntax/introduction) — the overall document structure
* [Schema Data Types](/schema-definition-language/data-types) — typing and validation
* [Best Practices & Guidelines](/best-practices/best-practices) — effective use of the format


# Objects

Object value syntax — open and closed objects, keyed and unkeyed values.

Objects are a fundamental element of Internet Object documents, providing a clear, compact way to represent structured data.

An object is **a sequence of values and/or key-value pairs separated by commas** (`,`, `U+002C`). For readability and flexibility, the format supports two object modes:

* **Open objects** — written without curly braces; allowed **only at the top level**.
* **Closed objects** — enclosed in `{}`; allowed at any level.

An object may contain:

* **Sequential (unkeyed) values**
* **Inline keyed values** (`key: value`)
* **Any combination and ordering** of keyed and unkeyed values

All values in an object are accessed by **position** (0-based). A value that has a key may also be accessed by **key**, especially when a schema is applied.

> **Design note.** Internet Object began as a compact, expressive format for transmitting structured objects across the internet — an object-oriented serialization model structurally similar to JSON. As it evolved, it adopted a document-oriented approach with sections, schemas, metadata, and stream-friendly constructs. The object remains the core unit of structure, and the compact syntax still reflects that original vision.

> **Implementation note.** In many programming languages, "object" is a built-in or base type. To avoid clashes, an implementation MAY expose the Internet Object value under a distinct name (for example, `InternetObject`) while conforming fully to the object syntax and behavior defined here.

## Syntax

### Closed object

```ebnf
object         = "{" [ objectEntries ] "}"
objectEntries  = entry *( "," entry )

entry          = keyedValue | unkeyedValue
keyedValue     = key ":" value
unkeyedValue   = value

key            = string
value          = any valid Internet Object value
```

### Open object

```ebnf
objectOpen = objectEntries
```

> Keys must be valid [strings](/structure-and-syntax/values/string). Values must be valid [Internet Object values](/structure-and-syntax/values). Keyed and unkeyed values may appear in any order.

## Structural characters

| Symbol | Name                | Unicode  | Description                           |
| ------ | ------------------- | -------- | ------------------------------------- |
| `{`    | Open curly bracket  | `U+007B` | Begins a closed object                |
| `}`    | Close curly bracket | `U+007D` | Ends a closed object                  |
| `:`    | Colon               | `U+003A` | Separates a key from its value        |
| `,`    | Comma               | `U+002C` | Separates values or key-value entries |

## Valid forms

### Open object with unkeyed and keyed values (any order)

```ruby
name: John, Doe, 25
John, age: 25, gender: M
name: John, age: 25, gender: M, T
John Doe, 25, T
```

### Closed object with mixed values

```ruby
{name: John, Doe, 25}
{John, age: 25, gender: M}
{name: John, age: 25, gender: M, T}
{John Doe, 25, T}
```

### Fully keyed object

```ruby
{
  name: John Doe,
  age: 25,
  gender: M,
  isActive: T
}
```

### Keys as strings (quoted forms)

```ruby
{
  "name": John Doe,
  'isActive': T,
  address: {Bond Street, New York, NY}
}
```

### JSON-compatible object

The following Internet Object is also a valid JSON object:

```ruby
{"name": "John", "age": 30, "isActive": true}
```

> Keys are double-quoted strings and all values use standard JSON types. **Child objects must always be enclosed in curly braces `{}`.** Only the top-level object may use the open form; every nested or embedded object must use the closed form.

## Invalid forms

```ruby
{name: John Doe 25}        # ✗ missing commas between values
{John age: 25 gender: M}   # ✗ missing commas between values
```

## Optional behaviors

### Whitespace and formatting

Whitespace is allowed and ignored:

```ruby
{ name : John , age : 25 }
```

### Empty objects

```ruby
{}     # ✓ valid
```

### Empty values

Empty value positions (via `,,`) are valid:

```ruby
John Doe,,true,,{NY}
```

### Trailing commas

Trailing commas are allowed and ignored:

```ruby
John, 25, T,,,,
```

## Comments

Comments are allowed between entries or alongside values:

```ruby
{
  name: John,     # name of person
  age: 25,        # years old
  isActive: T
}
```

> Comments must not appear inside string literals or values.

## Access semantics

* All values are accessed by **position** (0-based).
* A keyed value **may also be accessed by key**, especially when a schema is applied.
* Keys do not affect a value's index position.
* Keys are optional but must be **well-formed strings**.

## Preservation of structure

Internet Object preserves:

* Value order and keyed/unkeyed structure
* Whitespace (non-significant)
* Optional comments

It does **not** enforce:

* Key uniqueness
* Key-based access without a schema
* The required presence of any key

## See Also

* [Value Representations](/structure-and-syntax/values) — all value types
* [Strings](/structure-and-syntax/values/string) — valid keys and string values
* [Object (SchemaDef)](/schema-definition-language/data-types/object) — schemas for objects
* [Comments](/structure-and-syntax/comments) — comment syntax
* [JSON Compatibility](/interoperability/json-compatibility) — round-tripping with JSON


# Arrays

Array value syntax — ordered, comma-separated collections of values.

An **array** is an ordered collection of values enclosed in square brackets. Arrays are containers used to express lists, sequences, and multi-dimensional tabular structures.

Each value in an array may be:

* A scalar (string, number, boolean, null), or
* A structured value (an object or another array).

Arrays are syntactically compact, support nesting, and avoid ambiguity by requiring every value to be present — trailing and elided elements are not allowed.

## Syntax

An array begins with `[` and ends with `]`, containing zero or more comma-separated values.

```ebnf
array = "[" [ value *("," value) ] "]"
```

Here, `value` is any valid Internet Object value, as defined in [Value Representations](/structure-and-syntax/values). The syntax and behavior of each value type (strings, numbers, booleans, objects, arrays, null) is defined in its own page.

## Structural characters

| Symbol | Name                 | Unicode  | Description                      |
| ------ | -------------------- | -------- | -------------------------------- |
| `[`    | Open square bracket  | `U+005B` | Starts an array                  |
| `]`    | Close square bracket | `U+005D` | Ends an array                    |
| `,`    | Comma                | `U+002C` | Separates values within an array |

## Valid forms

```ruby
[]                       # Empty array
[apple, banana, cherry]  # String values
[1, 2, 3]                # Number values
[T, F, N]                # Boolean and null values
[{x:1}, {y:2}]           # Array of objects
[1, [2, 3], [4, [5, 6]]] # Nested arrays
[[1,2],[3,4]]            # 2D array
```

## Optional behaviors

### Whitespace and formatting

Whitespace is permitted around elements and structural characters for readability.

```ruby
[ a , b , c ]   # Valid
```

All forms with an equivalent value structure are interpreted identically.

### Empty array

An empty array is written as a pair of brackets with nothing between them:

```ruby
[]
```

This is a valid array with no elements.

### Nesting

Arrays may contain other arrays, allowing arbitrarily deep structures.

```ruby
[1, [2, 3], [[4]]]
```

## Comments

Comments are allowed around and within arrays, following the format's general comment syntax.

```ruby
[
  1, 2,  # inline comment
  3
]
```

> Comments must not break value boundaries, and must not appear inside strings or object keys.

## Invalid forms

```ruby
[a, b, ]     # ✗ trailing comma
[a,,c]       # ✗ elided value
[ , ]        # ✗ missing value
[,a]         # ✗ starts with a comma
[a b c]      # ✗ missing separators
```

### Corrected versions

```ruby
[a, b]       # ✓ valid
[a, null, c] # ✓ use null for a missing value
```

## Preservation of structure

Internet Object preserves:

* Value order
* Whitespace (non-significant in interpretation)
* Syntactic fidelity (as written)

It does **not** interpret:

* The meaning of order
* Whether values must be unique

Those semantics belong to the schema, the validator, or the application.

## See Also

* [Value Representations](/structure-and-syntax/values) — all value types
* [Objects](/structure-and-syntax/values/object) — the other structured value
* [Array](/schema-definition-language/data-types/array) — schemas for arrays


# Strings

The three string forms — open, regular, and raw.

Strings represent sequences of Unicode code points. They carry textual data and preserve whitespace and formatting within their boundaries.

Internet Object supports three string forms, each with its own syntax and use cases:

```ebnf
stringValue = openString | regularString | rawString
```

| Form                                                                  | Description                                                                | Example                      |
| --------------------------------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------- |
| [Open string](/structure-and-syntax/values/string/open-strings)       | Unquoted; the simplest form; ends at a structural character or whitespace. | `John Doe`                   |
| [Regular string](/structure-and-syntax/values/string/regular-strings) | Quoted with single or double quotes; supports escaping.                    | `"John Doe"`                 |
| [Raw string](/structure-and-syntax/values/string/raw-strings)         | Prefixed with `r`; quoted; backslashes are literal.                        | `r'C:\path'` or `r"C:\path"` |

All three forms preserve whitespace and Unicode content as written.

## When to use each form

* **Open string** — simple, unstructured text with no leading or trailing whitespace and no structural characters.
* **Regular string** — text that needs structural characters, leading/trailing whitespace, or escape sequences.
* **Raw string** — text with many backslashes or quotes (file paths, regular expressions), where escaping would be cumbersome.

## See Also

* [String Types](/schema-definition-language/data-types/string) — schemas for strings
* [Numeric Values](/structure-and-syntax/values/number) — the numeric forms
* [Value Representations](/structure-and-syntax/values) — all value types


# Open Strings

Open strings — the unquoted string form.

An **open string** is the simplest string form: a sequence of Unicode code points with no enclosing quotes. Open strings suit simple, unstructured text that does not begin or end with whitespace and does not require escaping of special or structural characters.

Open strings are scalar values. They preserve all internal whitespace and Unicode content, but cannot start or end with whitespace.

## Syntax

An open string begins with any non-whitespace code point and ends at the first whitespace or structural character, or at the end of the document.

```ebnf
openString    = nonWhitespace (codepoint)*
nonWhitespace = any Unicode code point except whitespace
codepoint     = any Unicode code point except a structural character or document end
```

## Structural characters

| Symbol             | Name                 | Unicode  | Description                                   |
| ------------------ | -------------------- | -------- | --------------------------------------------- |
| (space, tab, etc.) | Whitespace           | Multiple | Terminates the string; cannot start or end it |
| `:`                | Colon                | `U+003A` | Structural character (terminates the string)  |
| `,`                | Comma                | `U+002C` | Structural character (terminates the string)  |
| `{`                | Open curly bracket   | `U+007B` | Structural character (terminates the string)  |
| `}`                | Close curly bracket  | `U+007D` | Structural character (terminates the string)  |
| `[`                | Open square bracket  | `U+005B` | Structural character (terminates the string)  |
| `]`                | Close square bracket | `U+005D` | Structural character (terminates the string)  |
| `"`                | Double quote         | `U+0022` | Allowed; does not terminate or need escaping  |
| `'`                | Single quote         | `U+0027` | Allowed; does not terminate or need escaping  |

## Valid forms

Examples of valid open strings:

```ruby
John Doe
Peter D'mello
जॉन डो
Wow Great
😃
```

Multiple open strings in an object:

```ruby
जॉन डो, Wow Great, 😃
```

A multiline open string (no escaping required):

```ruby
Lorem ipsum dolor sit amet consetetur sadipscing elitr sed
diam nonumy eirmod.

Tempor invidunt ut labore et dolore magna aliquyam erat
sed diam voluptua
```

## Optional behaviors

* **Whitespace** — an open string cannot start or end with whitespace, but preserves all internal whitespace.
* **No escaping** — character escaping is not processed; quotes and other characters appear as-is.
* **Multiline** — an open string can span multiple lines as long as no structural character interrupts it.

## Comments

Comments are not allowed inside open strings, but may appear outside or between values, per the format's comment rules.

## Invalid forms

Examples of invalid open strings:

```ruby
 John Doe      # ✗ starts with whitespace (use a regular string: " John Doe")
"John Doe"     # ✗ quoted (this is a regular string, not an open string)
```

## Preservation of structure

Internet Object preserves:

* All Unicode code points and internal whitespace as written
* The unquoted, open form of the string

It does **not** interpret or enforce:

* Escaping or encoding
* Leading or trailing whitespace (which is disallowed)
* Application-specific constraints

## See Also

* [Strings](/structure-and-syntax/values/string) — the three string forms
* [Regular Strings](/structure-and-syntax/values/string/regular-strings) — quoted strings with escaping
* [Raw Strings](/structure-and-syntax/values/string/raw-strings) — literal strings without escape processing


# Regular Strings

Regular strings — quoted strings with escape sequences.

A **regular string** is a sequence of Unicode code points enclosed in single quotes (`'`, `U+0027`) or double quotes (`"`, `U+0022`). Regular strings allow any character — including whitespace and structural characters — and support escape sequences for special code points. This makes them suitable for text that needs leading or trailing whitespace, structural characters, or escaping.

Regular strings are scalar values. They preserve all content as written, including whitespace and Unicode characters.

## Syntax

A regular string is enclosed in single or double quotes and may contain any Unicode code point, with support for escape sequences.

```ebnf
regularString     = '"' { dqChar | escapeSequenceDQ } '"' | "'" { sqChar | escapeSequenceSQ } "'"
dqChar            = any Unicode code point except '"' or '\'
sqChar            = any Unicode code point except "'" or '\'
escapeSequenceDQ  = '\' ( '"' | "'" | '\' | 'b' | 'f' | 'r' | 'n' | 't' | unicodeEscape | hexEscape | other )
escapeSequenceSQ  = '\' ( "'" | '"' | '\' | 'b' | 'f' | 'r' | 'n' | 't' | unicodeEscape | hexEscape | other )
unicodeEscape     = 'u' hex4
hexEscape         = 'x' hex2
hex4              = 4 hexadecimal digits (must form a valid Unicode code point)
hex2              = 2 hexadecimal digits
other             = any character except 'u' or 'x'
```

## Structural characters

| Symbol             | Name                   | Unicode  | Description                                  |
| ------------------ | ---------------------- | -------- | -------------------------------------------- |
| `"`                | Double quote           | `U+0022` | Encloses the string; must be escaped inside  |
| `'`                | Single quote           | `U+0027` | Encloses the string; must be escaped inside  |
| `\`                | Reverse solidus        | `U+005C` | Escape character                             |
| (space, tab, etc.) | Whitespace             | Multiple | Preserved as written                         |
| Any                | Any Unicode code point | Multiple | Allowed, except an unescaped enclosing quote |

## Valid forms

Examples of valid regular strings:

```ruby
"John Doe"
'John Doe'
"   John Doe   "                 # leading/trailing whitespace preserved
"Peter D'mello"                  # single quote needs no escape in a double-quoted string
'Peter D\'mello'                 # escaped single quote in a single-quoted string
"जॉन डो"
'Can contain unicode characters 😃'
"She said, \"I Love it\""        # escaped double quotes
'She said, "I Love it"'          # double quotes need no escape in a single-quoted string
"Line one\nLine two"             # \n is interpreted as a newline
"\x3A"                           # two-digit hex escape -> ":"
"\u00AF"                         # four-digit unicode escape -> "¯"
"\uD83D\uDE00"                   # UTF-16 surrogate pair -> "😀"
```

## Optional behaviors

* **Whitespace** — leading, trailing, and internal whitespace are preserved.
* **Escaping** — only these escape sequences are interpreted: `\n`, `\"`, `\\`, `\'`, `\b`, `\f`, `\r`, `\t`, `\u` (exactly 4 hex digits, forming a valid code point), and `\x` (exactly 2 hex digits). For any other sequence (e.g. `\o`), the backslash is dropped and the following character is kept literally — so `"hell\o"` emits `hello`.
* **Multiline** — newline and carriage-return characters are preserved.
* **Equivalence** — escaped and unescaped forms are equal when they represent the same code points.

## Comments

Comments are not allowed inside regular strings, but may appear outside or between values, per the format's comment rules.

## Invalid forms

Examples of invalid regular strings:

```ruby
John Doe                  # ✗ not quoted (use "John Doe" or 'John Doe')
"John Doe                 # ✗ missing closing quote
"She said, "I Love it""   # ✗ unescaped inner quote (use \"I Love it\")
```

> **Lenient escapes.** An unrecognized escape such as `\q` is *not* an error — the backslash is dropped and the character is kept, so `"\q"` emits `q`. The genuine errors above are an unquoted value, an unterminated string, and an unescaped enclosing quote.

## Preservation of structure

Internet Object preserves:

* All Unicode code points and whitespace as written
* Escaped and unescaped forms (syntactic fidelity)

It does **not** interpret or enforce:

* Application-specific constraints
* Normalization of escape sequences beyond equivalence

## See Also

* [Strings](/structure-and-syntax/values/string) — the three string forms
* [Open Strings](/structure-and-syntax/values/string/open-strings) — the unquoted form
* [Raw Strings](/structure-and-syntax/values/string/raw-strings) — literal strings without escape processing


# Raw Strings

Raw strings — literal strings where backslashes are not escapes.

A **raw string** is a sequence of Unicode code points prefixed with `r` (or `R`) and enclosed in single quotes (`'`, `U+0027`) or double quotes (`"`, `U+0022`). Raw strings suit text with many backslashes, quotes, or structural characters — file paths or regular expressions, for example. They process no escape sequences except the enclosing quote, which is written by doubling it inside the string.

Raw strings are scalar values. They preserve all content as written, including whitespace, newlines, and Unicode characters.

## Syntax

A raw string is prefixed with `r` or `R` and enclosed in single or double quotes. The only special rule is that the enclosing quote, when it appears inside the string, must be written as two consecutive enclosing quotes.

```ebnf
rawString = "r" (singleQuotedRaw | doubleQuotedRaw)
singleQuotedRaw = "'" { character | doubleSingleQuote } "'"
doubleQuotedRaw = '"' { character | doubleDoubleQuote } '"'
character = any Unicode code point except the enclosing quote
doubleSingleQuote = "''" (a single quote inside a single-quoted raw string)
doubleDoubleQuote = '""' (a double quote inside a double-quoted raw string)
```

## Structural characters

| Symbol             | Name                   | Unicode  | Description                                             |
| ------------------ | ---------------------- | -------- | ------------------------------------------------------- |
| `r`                | Raw prefix             | `U+0072` | Marks the string as raw                                 |
| `'`                | Single quote           | `U+0027` | Encloses the string; doubled inside to represent itself |
| `"`                | Double quote           | `U+0022` | Encloses the string; doubled inside to represent itself |
| (space, tab, etc.) | Whitespace             | Multiple | Preserved as written                                    |
| Any                | Any Unicode code point | Multiple | Allowed, except an unescaped enclosing quote            |

> **Backslash is literal.** The reverse solidus (`\`, `U+005C`) is always a literal character in a raw string — there is no backslash escaping.

## Valid forms

Examples of valid raw strings:

```ruby
r'C:\program files\example\app.exe'
r"C:\program files\example\app.exe"
r'^(19|20)\d\d([- /.])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$'
r"^(19|20)\d\d([- /.])(0[1-9]|1[012])\2(0[1-9]|[12][0-9]|3[01])$"
r'जॉन डो'
r"Can contain unicode characters 😃"
r'Jonas D''costa'        # a single quote inside, written as two single quotes
r"He said, ""Hello!"""   # a double quote inside, written as two double quotes
```

## Optional behaviors

* **Whitespace** — leading, trailing, and internal whitespace are preserved.
* **No escaping** — no escape sequences are processed except doubling the enclosing quote.
* **Multiline** — newline and carriage-return characters are preserved.

## Comments

Comments are not allowed inside raw strings, but may appear outside or between values, per the format's comment rules.

## Invalid forms

Examples of invalid raw strings:

```ruby
rC:\program files\app.exe    # ✗ missing quotes (use r'...' or r"...")
r'Jonas D'costa'             # ✗ unescaped single quote inside (use r'Jonas D''costa')
r"He said, "Hello!""         # ✗ unescaped double quote inside (use r"He said, ""Hello!""")
r'Unclosed string            # ✗ missing closing quote
```

## Preservation of structure

Internet Object preserves:

* All Unicode code points and whitespace as written
* The doubled-quote convention for an embedded enclosing quote

It does **not** interpret or enforce:

* Application-specific constraints
* Any escaping beyond doubled enclosing quotes

## See Also

* [Strings](/structure-and-syntax/values/string) — the three string forms
* [Open Strings](/structure-and-syntax/values/string/open-strings) — the unquoted form
* [Regular Strings](/structure-and-syntax/values/string/regular-strings) — quoted strings with escaping


# Numeric Values

The numeric value forms — Number, BigInt, and Decimal.

Internet Object provides accurate numeric representation for everything from simple counting to exact financial arithmetic. It supports three numeric forms — **Number**, **BigInt**, and **Decimal** — each suited to a different requirement.

## Numeric forms

* [Number](/structure-and-syntax/values/number/number) — a 64-bit IEEE 754 double-precision value, for general-purpose calculations and fractional values.
* [BigInt](/structure-and-syntax/values/number/bigint) — an arbitrary-precision integer, for whole numbers beyond the 64-bit range.
* [Decimal](/structure-and-syntax/values/number/decimal) — a fixed-precision decimal with exact arithmetic, for financial values and other cases that require precise decimal representation.

## Number bases and notations

Internet Object accepts several notations. The table distinguishes integer-only bases from fractional notations and gives a recommendation.

> **Integer-only bases.** Binary (base 2), octal (base 8), and hexadecimal (base 16) can represent only integers. For fractional values, use base-10 decimal or scientific notation.

| Notation                  | Supported forms | Recommendation                                                                                          |
| ------------------------- | --------------- | ------------------------------------------------------------------------------------------------------- |
| Decimal integer (base 10) | Number, BigInt  | BigInt for large integers; Number for general use                                                       |
| Decimal fractional        | Number, Decimal | Decimal for exact/financial values; Number otherwise                                                    |
| Binary (base 2)           | Number, BigInt  | BigInt for large binary integers; Number otherwise                                                      |
| Octal (base 8)            | Number, BigInt  | BigInt for large octal integers; Number otherwise                                                       |
| Hexadecimal (base 16)     | Number, BigInt  | BigInt for large hex integers; Number otherwise                                                         |
| Scientific notation       | Number, Decimal | Decimal for precise values; Number otherwise                                                            |
| Special values            | Number only     | [NaN and Infinity](/structure-and-syntax/values/number/nan-and-infinity) for undefined/infinite results |

## Distinguishing the forms

Each form is identified by a distinct suffix (Number has none):

```ruby
42          # Number  (standard floating-point)
42n         # BigInt  (arbitrary-precision integer)
42.50m      # Decimal (fixed-precision decimal)
```

> **Two senses of "decimal".** *Decimal (base 10)* is the common numeral system used by all forms. *Decimal (the form)* is the fixed-precision type for exact arithmetic.

## Choosing a form

| Use case              | Recommended form | Reason                                 |
| --------------------- | ---------------- | -------------------------------------- |
| General calculations  | Number           | Standard performance and compatibility |
| Financial amounts     | Decimal          | Exact precision, no rounding error     |
| Large counters or IDs | BigInt           | No precision limit for integers        |
| Scientific notation   | Number           | Built-in floating-point support        |
| Cryptographic values  | BigInt           | Handles arbitrarily large integers     |

## See Also

* [Value Representations](/structure-and-syntax/values) — all value types
* [NaN and Infinity](/structure-and-syntax/values/number/nan-and-infinity) — special numeric values (Number only)
* [Numeric Types](/schema-definition-language/data-types/number) — numeric schemas and constraints


# Number

Standard 64-bit IEEE 754 floating-point numbers.

A **number** is a 64-bit double-precision floating-point value conforming to IEEE 754. Numbers are scalar values used to express integers, fractional values, and special numeric constants.

Numbers support several representations: decimal, the alternative bases (binary, octal, hexadecimal), scientific notation, and the special values `NaN` and `Inf`.

## Syntax

A number can be written in several forms:

```ebnf
number = ["-" | "+"] (
    decimalNumber
  | binaryNumber
  | octalNumber
  | hexNumber
  | scientificNumber
) | specialValue

decimalNumber    = digit+ [ "." digit* ] | "." digit+
binaryNumber     = "0b" binaryDigit+
octalNumber      = "0o" octalDigit+
hexNumber        = "0x" hexDigit+
scientificNumber = ( digit+ [ "." digit* ] | "." digit+ ) ("e" | "E") ["-" | "+"] digit+
specialValue     = "NaN" | "Inf" | "-Inf" | "+Inf"

digit       = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
binaryDigit = "0" | "1"
octalDigit  = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7"
hexDigit    = digit | "A" | "B" | "C" | "D" | "E" | "F" | "a" | "b" | "c" | "d" | "e" | "f"
```

## Structural characters

| Symbol    | Name          | Unicode  | Description                                       |
| --------- | ------------- | -------- | ------------------------------------------------- |
| `0`–`9`   | Digits        | Multiple | Decimal digits                                    |
| `.`       | Decimal point | `U+002E` | Separates the integer and fractional parts        |
| `-`       | Minus sign    | `U+002D` | Negative value                                    |
| `+`       | Plus sign     | `U+002B` | Explicit positive value (not preserved on output) |
| `e` / `E` | Exponent      | Multiple | Scientific-notation exponent                      |
| `0b`      | Binary prefix | Multiple | Begins a binary number                            |
| `0o`      | Octal prefix  | Multiple | Begins an octal number                            |
| `0x`      | Hex prefix    | Multiple | Begins a hexadecimal number                       |

## Valid forms

### Decimal numbers

Decimal numbers may be integers or fractional values, with an optional sign. A leading or trailing decimal point is allowed (`.5` and `5.`).

```ruby
# Integers
42                   # integer
-17                  # negative integer
+17                  # positive integer (explicit sign, not preserved)

# Fractional
3.14159              # fractional
-0.5                 # negative fractional
.5                   # leading dot -> 0.5
5.                   # trailing dot -> 5

# Zero
0
+0
-0
```

### Alternative bases

Numbers can be written in binary, octal, or hexadecimal. The prefix is case-insensitive, and hex digits may be upper or lower case.

```ruby
# Binary (0b / 0B), digits 0-1
0b1010               # 10
0B1111               # 15
-0b1010              # -10

# Octal (0o / 0O), digits 0-7
0o755                # 493
0O644                # 420

# Hexadecimal (0x / 0X), digits 0-9 A-F
0xFF                 # 255
0XDeadBeef           # 3735928559
0xff                 # 255 (lower-case digits)
```

### Scientific notation

Scientific notation uses `e` or `E` (case-insensitive) for the exponent, which may be signed.

```ruby
1.23e4               # 1.23 × 10^4  = 12300
1.23e-4              # 1.23 × 10^-4 = 0.000123
-2.5e+3              # -2.5 × 10^3  = -2500
5e3                  # 5 × 10^3     = 5000
.5e2                 # 0.5 × 10^2   = 50
6.022e23             # Avogadro's number
```

### Equivalent forms

The same value can be written in several bases and notations:

```ruby
---
42, 0x2A, 0b101010, 0o52, 4.2e1
```

All five values above are `42`.

## Invalid forms

The following are genuine syntax errors (the parser reports an error):

```ruby
---
0o89                 # ✗ invalid octal digit
```

```ruby
0b                   # ✗ missing binary digits
0x                   # ✗ missing hex digits
0xGH                 # ✗ invalid hex digits
0b 1010              # ✗ space between the prefix and the digits
```

## Implementation status (beta)

The reference implementation is currently lenient with some malformed numeric tokens. These are tracked as implementation issues; a conformant parser SHOULD reject them rather than accept or coerce them:

* An incomplete exponent is accepted instead of rejected: `1e` and `1e+` both yield `1`.
* A token that begins like a number but is ill-formed falls back to an open string instead of raising an error: `0b12` → `"0b12"`, `1.2.3` → `"1.2.3"`, `1.23ee4` → `"1.23ee4"`.

## Preservation of structure

Internet Object preserves:

* The chosen notation (decimal, binary, octal, hex, scientific)
* Whitespace (non-significant)
* Syntactic fidelity as written, except that an explicit `+` sign is not preserved

It does **not** interpret:

* Mathematical relationships between values
* Precision beyond IEEE 754
* Domain-specific numeric constraints

Those semantics belong to the schema, the validator, or the application.

## See Also

* [Numeric Values](/structure-and-syntax/values/number) — all numeric forms and notations
* [BigInt](/structure-and-syntax/values/number/bigint) — arbitrary-precision integers
* [Decimal](/structure-and-syntax/values/number/decimal) — fixed-precision decimal arithmetic
* [NaN and Infinity](/structure-and-syntax/values/number/nan-and-infinity) — special numeric values
* [Numeric Types](/schema-definition-language/data-types/number) — numeric schemas


# BigInt

Arbitrary-precision integer values for very large whole numbers.

A **BigInt** is an arbitrary-precision integer — a whole number with no upper or lower size limit. It suits values that exceed the safe range of a standard Number, such as cryptographic quantities, large identifiers, and high-volume counters.

A standard Number is exact only for integers within roughly ±2^53−1 (about ±9 quadrillion). A BigInt stays exact at any magnitude.

## Syntax

A BigInt is written as an integer with the `n` suffix:

```ebnf
bigint = ["-" | "+"] (decimalBigInt | binaryBigInt | octalBigInt | hexBigInt)

decimalBigInt = digit+ "n"
binaryBigInt  = "0b" binaryDigit+ "n"
octalBigInt   = "0o" octalDigit+ "n"
hexBigInt     = "0x" hexDigit+ "n"

digit       = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
binaryDigit = "0" | "1"
octalDigit  = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7"
hexDigit    = digit | "A" | "B" | "C" | "D" | "E" | "F" | "a" | "b" | "c" | "d" | "e" | "f"
```

## Structural characters

| Symbol  | Name          | Unicode  | Description                 |
| ------- | ------------- | -------- | --------------------------- |
| `n`     | BigInt suffix | `U+006E` | Marks the value as a BigInt |
| `0`–`9` | Digits        | Multiple | Decimal digits              |
| `-`     | Minus sign    | `U+002D` | Negative value              |
| `0b`    | Binary prefix | Multiple | Begins a binary BigInt      |
| `0o`    | Octal prefix  | Multiple | Begins an octal BigInt      |
| `0x`    | Hex prefix    | Multiple | Begins a hexadecimal BigInt |

## Valid forms

### Decimal BigInt

```ruby
123n                 # positive BigInt
-42n                 # negative BigInt
0n                   # zero
9007199254740992n    # beyond the safe Number range
```

### Alternative bases

A BigInt can also be written in binary, octal, or hexadecimal — each still ending in `n`. The following are all equal to `42n`:

```ruby
---
42n, 0x2An, 0b101010n, 0o52n
```

## Invalid forms

These are genuine syntax errors:

```ruby
0xn                  # ✗ missing hex digits
0bn                  # ✗ missing binary digits
```

A BigInt holds whole numbers only, so a fractional BigInt is an error:

```ruby
---
123.45n              # ✗ a BigInt cannot have a fractional part (use Decimal)
```

> **Lenient fallbacks.** A malformed suffix does not raise an error — it falls back to an open string. `123nn` parses as the text `"123nn"`, and `n123` as the text `"n123"`. A conformant parser SHOULD instead reject these; they are tracked as implementation issues.

## Preservation of structure

Internet Object preserves:

* The chosen notation (decimal, binary, octal, hex)
* Exact integer precision at any magnitude
* Syntactic fidelity as written, except that an explicit `+` sign is not preserved

It does **not** interpret:

* Mathematical relationships between values
* Domain-specific constraints on large integers

Those semantics belong to the schema, the validator, or the application.

## See Also

* [Numeric Values](/structure-and-syntax/values/number) — all numeric forms
* [Number](/structure-and-syntax/values/number/number) — standard floating-point numbers
* [Decimal](/structure-and-syntax/values/number/decimal) — fixed-precision decimal arithmetic


# Decimal

Fixed-precision decimal values for exact, financial-grade arithmetic.

A **Decimal** is a fixed-precision decimal value for cases that demand exact numbers — especially financial calculations, where floating-point approximation can introduce errors. A Decimal stores an exact value with a defined precision and scale.

Unlike a standard floating-point Number, a Decimal does not approximate: `0.1m` is exactly one-tenth, not the nearest binary fraction.

## Syntax

A Decimal is written as a number with the `m` suffix. It requires a leading digit and, if a decimal point is present, at least one digit after it. Scientific notation is **not** supported.

```ebnf
decimal = ["-" | "+"] digit+ [ "." digit+ ] "m"

digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
```

## Structural characters

| Symbol  | Name           | Unicode  | Description                                |
| ------- | -------------- | -------- | ------------------------------------------ |
| `m`     | Decimal suffix | `U+006D` | Marks the value as a Decimal               |
| `0`–`9` | Digits         | Multiple | Decimal digits                             |
| `.`     | Decimal point  | `U+002E` | Separates the integer and fractional parts |
| `-`     | Minus sign     | `U+002D` | Negative value                             |

## Valid forms

```ruby
---
123.45m, 123m, 0.001m, -789.01m, 0m, 0.0m
```

* `123.45m` — fractional decimal
* `123m` — integer decimal
* `0.001m` — leading zeros preserved
* `-789.01m` — negative decimal
* `0m`, `0.0m` — zero, with and without a scale

## Precision and scale

Each Decimal carries a **precision** (the total number of significant digits) and a **scale** (the number of digits after the decimal point):

```ruby
123.45m              # precision 5, scale 2
0.000123m            # precision 6, scale 6
```

A schema can constrain these; see [Decimal](/schema-definition-language/data-types/decimal).

## Invalid forms

A Decimal requires a leading digit and, with a decimal point, a trailing digit. Scientific notation is rejected:

```ruby
---
1.23e2m              # ✗ scientific notation is not supported for Decimal
```

```ruby
.45m                 # ✗ missing leading digit (use 0.45m)
123.m                # ✗ missing trailing digit (use 123.0m or 123m)
123.45mm             # ✗ duplicated suffix
```

> A plain `123.45` (no `m`) is a valid **Number**, not a Decimal — the `m` suffix is what selects fixed precision.

## Preservation of structure

Internet Object preserves:

* Exact decimal precision and scale
* Syntactic fidelity as written, except that an explicit `+` sign is not preserved

It does **not** interpret:

* Rounding behavior for operations
* Currency or unit semantics

Those semantics belong to the schema, the validator, or the application.

## See Also

* [Numeric Values](/structure-and-syntax/values/number) — all numeric forms
* [Number](/structure-and-syntax/values/number/number) — standard floating-point numbers
* [BigInt](/structure-and-syntax/values/number/bigint) — arbitrary-precision integers


# Special Numeric Formats

Hexadecimal, octal, binary, and scientific numeric literals.

Besides ordinary decimal, a number may be written in **hexadecimal**, **octal**, **binary**, or **scientific** notation. These are just different ways of writing the same numeric value — the parser reads them all into one number.

| Notation    | Prefix / form | Example      | Value |
| ----------- | ------------- | ------------ | ----- |
| Decimal     | *(none)*      | `255`        | 255   |
| Hexadecimal | `0x`          | `0xFF`       | 255   |
| Octal       | `0o`          | `0o377`      | 255   |
| Binary      | `0b`          | `0b11111111` | 255   |
| Scientific  | `e` exponent  | `2.55e2`     | 255   |

```ruby
dec: number, hex: number, oct: number, bin: number, sci: number
---
255, 0xFF, 0o377, 0b11111111, 2.55e2
```

All five fields above hold the value `255`.

## Notes

* A leading sign is allowed: `-0x1A`, `+1.5e3`.
* Notation is independent of the field's type — any number type accepts any notation as input.
* How a number is **written back** on serialization is controlled by the schema's `format` option (`decimal`, `hex`, `octal`, `binary`, `scientific`); see [Numeric Types](/schema-definition-language/data-types/number).
* For very large integers use [BigInt](/structure-and-syntax/values/number/bigint) (`123n`); for exact decimals use [Decimal](/structure-and-syntax/values/number/decimal) (`1.50m`).

## See Also

* [Number](/structure-and-syntax/values/number/number) · [BigInt](/structure-and-syntax/values/number/bigint) · [Decimal](/structure-and-syntax/values/number/decimal)
* [NaN and Infinity](/structure-and-syntax/values/number/nan-and-infinity)


# NaN and Infinity

The special numeric values NaN and Infinity.

The special numeric values represent "Not a Number" and infinite quantities, following IEEE 754. They model the edge cases of numeric computation — an undefined result, or a value beyond the finite range. Only the Number form supports them; BigInt and Decimal do not.

## Syntax

The special values are written as fixed keywords:

```ebnf
specialValue  = nanValue | infinityValue
nanValue      = "NaN"
infinityValue = ["+" | "-"] "Inf"
```

## Structural elements

| Token | Name         | Description                                        |
| ----- | ------------ | -------------------------------------------------- |
| `NaN` | Not a Number | An undefined or unrepresentable numeric result     |
| `Inf` | Infinity     | A value beyond the finite range                    |
| `-`   | Minus sign   | Negative infinity (`-Inf`)                         |
| `+`   | Plus sign    | Explicit positive infinity (`+Inf`); not preserved |

## Valid forms

```ruby
NaN                  # Not a Number
Inf                  # positive infinity
-Inf                 # negative infinity
+Inf                 # positive infinity (explicit sign, not preserved)
```

## Not the special value

The special values are **case-sensitive and spelled exactly**. Any other spelling is not an error — it is simply parsed as an open string (text), so it is *not* the numeric special value:

```ruby
nan                  # open string "nan", not NaN
INF                  # open string "INF", not Inf
infinity             # open string "infinity", not Inf
-NaN                 # open string "-NaN" — NaN cannot be signed
```

> To store one of these as a number, write it exactly: `NaN`, `Inf`, `-Inf`, or `+Inf`. To store the word as text, quote it: `"infinity"`.

## Preservation of structure

Internet Object preserves:

* The exact special value written
* The sign of negative infinity
* Syntactic fidelity as written, except that an explicit `+` sign is not preserved

It does **not** interpret:

* The operations that produced these values
* Comparison or equality semantics (for example, that `NaN` equals nothing, including itself)

Those semantics belong to the application.

## See Also

* [Number](/structure-and-syntax/values/number/number) — standard floating-point numbers
* [Numeric Values](/structure-and-syntax/values/number) — all numeric forms


# Binary

Binary values written as Base64 byte strings.

A **binary value** is a sequence of raw bytes carried as text. It is written as a Base64 byte string: the prefix `b` followed by Base64 content in single or double quotes. Binary values suit images, encrypted content, cryptographic keys, or any arbitrary byte sequence in an otherwise text-based document.

The content between the quotes is Base64 per RFC 4648. *Base64* is the encoding; *binary* is the value type.

> **Implementation status (beta).** Binary literals are **not yet available** in the reference implementation: `b'…'` and `b"…"` currently raise a syntax error (`unexpected-token`), and no `binary` schema type is registered. This page documents the intended design; the examples below are illustrative and are not yet executable. Track progress in the [Roadmap](/appendices/roadmap).

## Syntax

A binary value is prefixed with `b` and enclosed in single or double quotes; the content must be valid Base64.

```ebnf
binaryValue = "b" (singleQuotedBase64 | doubleQuotedBase64)
singleQuotedBase64 = "'" base64Content "'"
doubleQuotedBase64 = '"' base64Content '"'
base64Content   = { base64Character }
base64Character = "A"…"Z" | "a"…"z" | "0"…"9" | "+" | "/" | "="
```

## Structural characters

| Symbol                    | Name            | Unicode            | Description                             |
| ------------------------- | --------------- | ------------------ | --------------------------------------- |
| `b`                       | Byte prefix     | `U+0062`           | Marks the value as a Base64 byte string |
| `'`                       | Single quote    | `U+0027`           | Encloses the Base64 content             |
| `"`                       | Double quote    | `U+0022`           | Encloses the Base64 content             |
| `A`–`Z`, `a`–`z`, `0`–`9` | Base64 alphabet | —                  | Base64 data characters                  |
| `+`, `/`                  | Base64 alphabet | `U+002B`, `U+002F` | Base64 data characters                  |
| `=`                       | Padding         | `U+003D`           | Base64 padding                          |

## Valid forms

```ruby
b'SGVsbG8gV29ybGQ='        # "Hello World"
b"SGVsbG8gV29ybGQ="        # same, with double quotes
b'QWxhZGRpbjpvcGVuIHNlc2FtZQ=='   # "Aladdin:open sesame"
b'TWFu'                    # "Man"  (no padding needed)
b'TWE='                    # "Ma"   (one pad)
b'TQ=='                    # "M"    (two pads)
b''                        # empty byte string
```

## Invalid forms

```ruby
bSGVsbG8=                  # ✗ missing quotes
b'SGVsbG8 gV29ybGQ='       # ✗ space within the Base64 content
b'SGVsbG8@V29ybGQ='        # ✗ invalid character '@'
B'SGVsbG8gV29ybGQ='        # ✗ prefix must be lower-case b
```

## Behavior

* **Whitespace** — leading and trailing whitespace around the quotes is ignored; whitespace inside the Base64 content is not allowed.
* **Prefix case** — the prefix must be lower-case `b`; the Base64 content is case-sensitive.
* **Padding** — standard `=` padding is required for correct decoding.
* **Decoding** — a parser decodes the content into a byte sequence (commonly a byte array or buffer) and preserves the exact bytes; invalid Base64 is a parse error.

Internet Object does not interpret the structure of the decoded bytes — any format, compression, or application meaning is the application's concern.

## See Also

* [Value Representations](/structure-and-syntax/values) — all value types
* [Binary](/schema-definition-language/data-types/binary) — the binary schema type
* [Strings](/structure-and-syntax/values/string) — text values (open, regular, raw)


# Date and Time

Temporal values — dates, times, and date-times as annotated strings.

Temporal values are written as **annotated strings** in ISO 8601-compatible formats, with a prefix that selects the kind: `d` for a date, `t` for a time, and `dt` for a combined date-time. A parser converts them to native date/time objects on deserialization.

The content between the quotes must be valid for its kind.

## Syntax

```ebnf
temporalValue = dateValue | timeValue | dateTimeValue
dateValue     = "d"  ("'" dateContent "'"     | '"' dateContent '"')
timeValue     = "t"  ("'" timeContent "'"     | '"' timeContent '"')
dateTimeValue = "dt" ("'" dateTimeContent "'" | '"' dateTimeContent '"')

dateContent     = yearPart [monthPart [dayPart]]
timeContent     = hourPart [minutePart [secondPart [millisecondPart]]]
dateTimeContent = dateContent ["T" timeContent] [timeZone]

yearPart        = digit digit digit digit
monthPart       = ["-"] ( "0" digit | "1" ("0" | "1" | "2") )
dayPart         = ["-"] ( "0" digit | ("1" | "2") digit | "3" ("0" | "1") )
hourPart        = [":"] ( ("0" | "1") digit | "2" ("0" | "1" | "2" | "3") )
minutePart      = [":"] ("0" | "1" | "2" | "3" | "4" | "5") digit
secondPart      = [":"] ("0" | "1" | "2" | "3" | "4" | "5") digit
millisecondPart = "." digit digit digit
timeZone        = "Z" | ("+" | "-") hourPart [minutePart]
```

## Structural characters

| Symbol  | Name            | Unicode           | Description                                       |
| ------- | --------------- | ----------------- | ------------------------------------------------- |
| `d`     | Date prefix     | `U+0064`          | Date-only value                                   |
| `t`     | Time prefix     | `U+0074`          | Time-only value                                   |
| `dt`    | DateTime prefix | —                 | Combined date-time value                          |
| `'` `"` | Quotes          | `U+0027` `U+0022` | Enclose the temporal content                      |
| `-`     | Hyphen          | `U+002D`          | Date separator (optional); also a negative offset |
| `:`     | Colon           | `U+003A`          | Time separator (optional)                         |
| `.`     | Period          | `U+002E`          | Millisecond separator                             |
| `T`     | Letter T        | `U+0054`          | Separates the date and time                       |
| `Z`     | Letter Z        | `U+005A`          | UTC designator                                    |
| `+`     | Plus            | `U+002B`          | Positive timezone offset                          |

## Valid forms

### Dates — `d'…'`

```ruby
d'2024-03-20'            # full date
d'2024-03'              # year and month (day defaults to 01)
d'2024'                # year only (month and day default to 01)
d'20240320'            # without separators
d"2024-12-31"          # double quotes
```

### Times — `t'…'`

```ruby
t'14:30:45.123'         # with milliseconds
t'14:30:45'             # hour, minute, second
t'14:30'                # hour and minute (second defaults to 00)
t'14'                   # hour only
t'143045'               # without separators
```

### Date-times — `dt'…'`

```ruby
dt'2024-03-20T14:30:45.123Z'   # full, UTC
dt'2024-03-20T14:30Z'          # without seconds
dt'2024-03-20T14:30:45+05:30'  # with a timezone offset
dt'2024-03-20'                 # date only (time defaults to 00:00:00.000)
dt"2024-12-31T23:59:59.999Z"   # double quotes
```

### A combined example

```ruby
---
d'2024-03-20', t'14:30:45.123', dt'2024-03-20T14:30Z'
```

## Format specifications

| Kind     | With separators        | Without separators | Partial forms                 | Defaults                                          |
| -------- | ---------------------- | ------------------ | ----------------------------- | ------------------------------------------------- |
| Date     | `YYYY-MM-DD`           | `YYYYMMDD`         | `YYYY-MM`, `YYYY`             | missing month/day → `01`                          |
| Time     | `HH:mm:ss.SSS`         | `HHmmss.SSS`       | `HH:mm:ss`, `HH:mm`, `HH`     | missing parts → `00`                              |
| DateTime | date `T` time `[zone]` | —                  | any date form + optional time | missing time → `00:00:00.000`; missing zone → UTC |

Hours use the 24-hour clock (`00`–`23`). A date-only value is treated as UTC midnight; a time-only value uses a reference date and is treated as UTC.

## Invalid forms

Out-of-range or malformed temporals produce the stable error code `invalid-datetime`:

```ruby
---
d'2024-13-20'           # ✗ invalid-datetime
```

```ruby
t'25:00:00'             # ✗ invalid-datetime (hour out of range)
t'12:60:00'             # ✗ invalid-datetime (minute out of range)
dt'2024-03-20 14:30:00' # ✗ invalid-datetime (missing the T separator)
dt'2024-03-20T14:30+25:00'  # ✗ invalid-datetime (timezone offset out of range)
```

## Timezone handling

* **Explicit UTC** — `dt'2024-03-20T14:30:45Z'` is UTC.
* **Explicit offset** — `dt'2024-03-20T14:30:45+05:30'` carries that offset; its UTC equivalent is `2024-03-20T09:00:45Z`.
* **No timezone** — a date-time with no zone is treated as UTC.
* **Valid offset range** — offsets run from `-12:00` to `+14:00`; both `±HH:mm` and `±HHMM` are accepted on input.
* On serialization, an implementation SHOULD emit offsets in the `±HH:mm` form and round-trip explicit timezone information unchanged.

## Implementation status (beta)

The reference implementation has known gaps in temporal parsing; a conformant parser SHOULD behave as specified above, not as the current implementation does:

* **DateTime drops the seconds component:** `dt'2024-03-20T14:30:45Z'` parses to `…T14:30:00.000Z` (the `45` is lost; milliseconds are kept). Time-only values are not affected — `t'14:30:45'` keeps its seconds.
* **Overflow dates are normalized rather than rejected:** `d'2024-02-30'` becomes `2024-03-01` instead of raising `invalid-datetime`.
* **Over-long fractions are truncated:** a millisecond field with more than three digits is truncated to three rather than rejected.

## See Also

* [Value Representations](/structure-and-syntax/values) — all value types
* [Numeric Values](/structure-and-syntax/values/number) — the numeric forms
* [Date and Time](/schema-definition-language/data-types/date-and-time) — date/time schemas


# Booleans

Boolean values — true and false, in compact and verbose forms.

A **boolean** is a logical value, either true or false. Booleans are scalar values used for flags, binary states, and conditions.

Each value has a compact and a verbose form, letting you trade brevity for explicitness.

## Syntax

```ebnf
boolean        = compactBoolean | verboseBoolean
compactBoolean = "T" | "F"
verboseBoolean = "true" | "false"
```

## Structural elements

| Token   | Name          | Description               |
| ------- | ------------- | ------------------------- |
| `T`     | Compact true  | `true` in compact form    |
| `F`     | Compact false | `false` in compact form   |
| `true`  | Verbose true  | the verbose true keyword  |
| `false` | Verbose false | the verbose false keyword |

## Valid forms

The compact and verbose forms are equivalent; the compact form is recommended for terse data.

```ruby
---
T, F, true, false
```

## Not a boolean

Boolean keywords are **case-sensitive and spelled exactly**. Any other token is not an error — it is parsed as a different value type, so it is *not* a boolean:

```ruby
t                    # open string "t", not true
TRUE                 # open string "TRUE", not true
True                 # open string "True", not true
1                    # the number 1, not true
0                    # the number 0, not false
```

> Under a `bool` schema, a non-boolean value fails validation with `not-a-bool`. Without a schema, the values above are simply kept as their parsed type (string or number).

## See Also

* [Value Representations](/structure-and-syntax/values) — all value types
* [Nulls](/structure-and-syntax/values/null) — the absence of a value
* [Bool](/schema-definition-language/data-types/bool) — the boolean schema type


# Nulls

The null value — an explicit absence of a value.

A **null** represents the absence of a value — data that is missing, unknown, or intentionally empty. Null is a scalar value with a compact and a verbose form.

## Syntax

```ebnf
null        = compactNull | verboseNull
compactNull = "N"
verboseNull = "null"
```

## Structural elements

| Token  | Name         | Description              |
| ------ | ------------ | ------------------------ |
| `N`    | Compact null | `null` in compact form   |
| `null` | Verbose null | the verbose null keyword |

## Valid forms

The compact and verbose forms are equivalent; the compact form is recommended for terse data.

```ruby
---
N, null
```

## Null versus empty

Null is the *absence* of a value, distinct from an empty string or an empty array:

```ruby
N        # null — no value
""       # an empty string (a value)
[]       # an empty array (a value)
```

## Not null

Null keywords are **case-sensitive and spelled exactly**. Any other token is not an error — it is parsed as an open string, so it is *not* null:

```ruby
n                    # open string "n", not null
NULL                 # open string "NULL", not null
Null                 # open string "Null", not null
nil                  # open string "nil", not null
undefined            # open string "undefined", not null
```

> To store one of these as text, that is exactly what happens. To express the absence of a value, write `N` or `null`.

## See Also

* [Value Representations](/structure-and-syntax/values) — all value types
* [Booleans](/structure-and-syntax/values/booleans) — true and false
* [Optional & nullable members](/schema-definition-language/advanced-schema-concepts/memberdef) — `?` and `*` in schemas


# Case Sensitivity Rules

Case sensitivity for keys, keywords, and type names.

Internet Object is **case-sensitive** throughout. Keys, keywords, and type names must be written with exact casing.

## Keys

Member keys are distinct by case — `Name` and `name` are two different fields:

```ruby
Name: string, name: string
---
{ Name: Alice, name: alice }
```

## Keywords

The literal keywords must be written exactly as defined. Their accepted forms are:

| Meaning      | Accepted      | Not accepted        |
| ------------ | ------------- | ------------------- |
| true         | `T`, `true`   | `t`, `True`, `TRUE` |
| false        | `F`, `false`  | `f`, `False`        |
| null         | `N`, `null`   | `n`, `Null`         |
| not-a-number | `NaN`         | `nan`, `NAN`        |
| infinity     | `Inf`, `-Inf` | `inf`, `Infinity`   |

```ruby
active: bool
---
~ T        # ✓
~ true     # ✓
~ t        # ✗ not-a-bool
```

## Type names

Built-in type names are lowercase: `string`, `int`, `uint8`, `bool`, `datetime`, `decimal`, and so on. `String` or `INT` are not recognized types.

## See Also

* [Booleans](/structure-and-syntax/values/booleans) · [Nulls](/structure-and-syntax/values/null)
* [Structural Characters & Separators](/structure-and-syntax/structural-elements/structural-characters-n-keywords)


# Comments

Single-line comments for annotating documents.

Internet Object supports **single-line comments** for documenting and annotating data. A comment starts with a hash sign (`#`) and runs to the end of the line.

## Syntax

* **Start character** — the hash sign (`#`, `U+0023`).
* **Scope** — a single line only.
* **Placement** — anywhere in the document.
* **Content** — everything after `#` on the same line is ignored by the parser.

## Examples

```ruby
# Internet Object document: personnel records

# Address schema definition
~ $address: {street:string, zip:{string, maxLen:5}, city:string}

# Person schema definition
~ $schema: {
    name:string,               # individual's full name
    age:int,                   # age in years
    homeAddress?: $address,    # optional home address
    officeAddress?: $address   # optional office address
}
---
# Personnel records
~ John Doe, 25, {Queens, "50010", NewYork}, {Bond Street, "50001", NewYork}
~ Jane Doe, 20, {Queens, "50010", NewYork}, {Bond Street, "50001", NewYork}
```

### Comment placement

A comment can stand on its own line or trail a value:

```ruby
{
    # person details
    name: John Doe, # inline comment
    age: 30,        # another inline comment

    # contact information
    contact: {
        email: 'john@example.com',
        phone: '+1-555-0123'
    }
}
```

## Rules

* A comment can appear on any line, standalone or trailing a value.
* A comment supports full Unicode text.
* A comment cannot span multiple lines.
* No escaping is needed inside a comment.
* A `#` inside a quoted string is literal text, not a comment.

## Best practices

* **Be clear and concise** — use direct language.
* **Explain why, not what** — focus on reasoning, not the obvious.
* **Keep comments current** — update them when the data or schema changes.
* **Stay consistent** — keep a uniform commenting style across documents.

## See Also

* [Internet Object Document](/structure-and-syntax/introduction) — overall document structure
* [Encoding](/structure-and-syntax/encoding) — Unicode support in text content


# Encoding

Character encoding — UTF-8 is mandatory; Unicode, BOM, and line endings.

The Internet Object format uses **UTF-8** as its default and mandatory encoding for all text. This ensures reliable interchange across platforms, systems, and programming languages.

## UTF-8 requirement

Every conformant implementation MUST support UTF-8. UTF-8 is chosen because it is:

* **Universal** — supported by virtually all modern systems and languages.
* **ASCII-compatible** — the ASCII range (0–127) is encoded identically.
* **Complete** — it can represent every Unicode character.
* **Byte-order independent** — no endianness concerns, unlike UTF-16 or UTF-32.
* **Self-synchronizing** — corruption of one character does not derail later parsing.

## Alternative encodings

UTF-8 is mandatory; an implementation MAY additionally support other encodings for specific needs.

| Encoding   | Support       | Notes                                        |
| ---------- | ------------- | -------------------------------------------- |
| UTF-8      | **Mandatory** | Default and required everywhere              |
| UTF-16     | Optional      | Useful where the platform is natively UTF-16 |
| UTF-32     | Optional      | Fixed width; larger files                    |
| ASCII      | Optional      | A compatible subset (basic characters only)  |
| ISO-8859-1 | Optional      | Legacy Latin-1 support                       |

> **UTF-8 is the baseline.** If another encoding fits your situation, convert to or from UTF-8 at the boundary. Because UTF-8 is the only mandatory encoding, every parser and serializer must handle it.

## Unicode support

Internet Object supports the full Unicode character set through UTF-8:

* **Basic Multilingual Plane** — `U+0000` to `U+FFFF`.
* **Supplementary planes** — `U+10000` to `U+10FFFF`.
* **Control characters** — handled per the Unicode standard; in strings they should be escaped.

For normalization, **NFC** (Normalization Form Canonical Composed) is the recommended form. An implementation should normalize consistently when comparing strings; the internal storage form is unconstrained.

## Byte order mark (BOM)

* A UTF-8 BOM is the byte sequence `EF BB BF` (`U+FEFF`) at the start of a document.
* A parser treats a leading BOM as whitespace and ignores it, so a BOM never causes a parse error.
* A BOM is optional and not recommended for UTF-8; if you use one, do so consistently.

## Line endings

All common line-ending conventions are accepted and treated equivalently:

* **Unix/Linux** — LF (`\n`)
* **Windows** — CRLF (`\r\n`)
* **Classic Mac** — CR (`\r`)

Mixed line endings within one document are handled gracefully.

## Implementation guidance

A conformant parser SHOULD:

1. Accept UTF-8 input and skip a leading BOM if present.
2. Report a clear error for invalid UTF-8 byte sequences and reject overlong encodings.
3. Handle UTF-16 surrogate pairs correctly when decoding `\u` escape sequences.

A conformant serializer SHOULD:

1. Always emit valid UTF-8.
2. Be consistent about including or omitting a BOM for the target system.
3. Emit escape sequences for control characters where needed.

## See Also

* [Whitespace & Indentation](/structure-and-syntax/structural-elements/whitespaces) — recognized whitespace characters
* [Strings](/structure-and-syntax/values/string) — string representation and escaping
* [Comments](/structure-and-syntax/comments) — comment syntax and Unicode support


# Syntax Errors

Common syntax errors and how the parser recovers.

A **syntax error** is a problem in the *shape* of the text — an unbalanced brace, a missing comma, an unterminated string — detected while tokenizing or parsing, before any schema validation. (Errors about *values* — wrong type, out of range — are validation errors; see [Error Model](/parsing-and-errors/error-model).)

## Common syntax errors

### Unbalanced brackets

```ruby
pt: { object, schema: { x: int } }
---
{ 1                      # ✗ expecting-bracket  (the '{' is never closed)
```

The unterminated `{` raises `expecting-bracket`.

### Missing comma

Values must be comma-separated. Without commas, several tokens merge into one open string:

```ruby
~ 101 Thomas 25      # one value "101 Thomas 25", not three
```

### Unterminated string

A quoted string with no closing quote raises a tokenizer error:

```ruby
~ "John Doe          # missing closing quote
```

## Recovery is bounded by structure

On a syntax error the parser **skips ahead to the next boundary** — a record separator `~` or a section separator `---` (or end of file) — records the error, and resumes. So one malformed record does not prevent later records from being parsed. See [Parser Behavior & Recovery](/parsing-and-errors/parser-behavior).

## See Also

* [Error Model](/parsing-and-errors/error-model)
* [Parser Behavior & Recovery](/parsing-and-errors/parser-behavior)
* [Comments](/structure-and-syntax/comments)


# Definitions

The header's definition section — metadata, variables, and references.

Besides the schema, an Internet Object document's **header** can hold *definitions*. A definition is a key–value pair on its own line, introduced by a tilde `~`:

```ruby
~ key: value
```

There are three kinds of definition, distinguished by the key's prefix:

| Prefix   | Kind                | Purpose                                                                                                   |
| -------- | ------------------- | --------------------------------------------------------------------------------------------------------- |
| *(none)* | **Metadata**        | Document-level data (paging, status, …). Surfaces in the output header.                                   |
| `@`      | **Value variable**  | A reusable value referenced as `@name`. See [Variables](/definitions/variables).                          |
| `$`      | **Reference (ref)** | A reusable schema or type referenced as `$name`. See [Schema References](/definitions/schema-references). |

The special key **`$schema`** names the document's default schema.

## Metadata

Bare keys carry document metadata. They appear under a `header` in the loaded result, separate from the data:

```ruby
~ pageSize: 10
~ success: T
~ $schema: { name: string }
---
~ John
~ Jane
```

## Variables and references

`@` defines a value variable; `$` defines a reusable schema (a ref). Both are then used by name:

```ruby
~ @active: T
~ $address: { street, city }
~ $schema: { name: string, addr: $address, isActive: bool }
---
~ John, { Main St, NYC }, @active
~ Jane, { Oak Ave, LA }, @active
```

> **A document may be header-only.** If there is no data, the header still ends with the `---` separator.

## See Also

* [Header](/structure-and-syntax/introduction/header) — where definitions live in a document
* [Variables](/definitions/variables) — value variables (`@`)
* [Schema References](/definitions/schema-references) — schema and type refs (`$`)
* [Internet Object Schema](/schema-definition-language/internet-object-schema)


# Variables

Value variables — reusable values referenced with @.

A **value variable** is a reusable value defined in the header with an `@`-prefixed key and used anywhere a value is expected by writing `@name`. Variables reduce repetition, shrink payloads, and let you keep sensitive values in one place.

> `@` is for **values**. For reusable *schemas and types*, use `$` references — see [Schema References](/definitions/schema-references).

## Defining and using

```ruby
~ @active: T
~ $schema: { name: string, isActive: bool }
---
~ John, @active
~ Jane, @active
```

## In schema constraints

A variable can supply a constraint value, such as a `choices` list:

```ruby
~ @r: red
~ @g: green
~ @b: blue
~ $schema: { name: string, color: { string, choices: [@r, @g, @b] } }
---
~ John, red
```

## Use cases

### Reduce size and repetition

Define a value once and reference it many times:

```ruby
~ @co: 'ACME Corporation'
~ $schema: { name: string, employer: string }
---
~ John, @co
~ Jane, @co
```

### Keep sensitive values together

Variables let you isolate secrets/keys in the header instead of scattering them through the data:

```ruby
~ @key: 'sk_live_8f3a9c2b'
~ $schema: { account: string, apiKey: string }
---
~ acct_001, @key
```

## See Also

* [Definitions](/definitions/definitions) · [Schema References](/definitions/schema-references)


# Schema References

Reusable schemas and types referenced with $.

A **reference** (ref) is a `$`-prefixed definition in the header that names a reusable schema or type. You define it once and refer to it elsewhere as `$name`. Refs come in two forms:

* **Schema reference** — names an object shape (a [SchemaDef](/schema-definition-language/data-types/object)).
* **Type reference** — names a single constrained type (a MemberDef), e.g. a percentage.

The special ref **`$schema`** is the document's default schema.

## Schema references

Define an object shape once, reuse it across fields and schemas:

```ruby
~ $address: { street: string, city: string }
~ $person: { name: string, home: $address, office?: $address }
~ $schema: $person
---
~ John, { Main St, NYC }, { 5th Ave, NYC }
~ Jane, { Oak Ave, LA }
```

`$schema: $person` sets the default schema by reference. A ref can be used as a field's type (`home: $address`) or as an array's element type (`tags: [$address]`).

## Resolution rules

* Refs are resolved after the **entire header** has been read, so order within the header is not significant — a ref MAY appear before the definition it targets. For readability, you SHOULD still define a ref before you use it.
* A ref to a name that is never defined is an error (`schema-not-defined`).
* Reusing a ref many times keeps a document small and consistent.

See [Error Handling in Definitions](/definitions/error-handling) for the resolution errors.

## Type references

A ref whose body is a single constrained type acts as a reusable **type** — your own named shortcut, the document-local counterpart of built-ins like `uint8` or `email`:

```ruby
# A reusable "percent" type and "short text" type
~ $percent: { number, min: 0, max: 100 }
~ $shortText: { string, maxLen: 40 }
~ $schema: { name: $shortText, score: $percent }
```

> **Implementation status (beta).** Type references are being added. Today a top-level `$` definition is compiled as an *object schema* (a SchemaDef), so its braces are read as an object shape — a constrained-type body such as `{ number, min: 0, max: 100 }` is not yet interpreted as a reusable number type. The forms above show the target syntax. Schema references (object shapes) work today.

## See Also

* [Definitions](/definitions/definitions) · [Variables](/definitions/variables)
* [Object (SchemaDef)](/schema-definition-language/data-types/object)
* [Internet Object Schema](/schema-definition-language/internet-object-schema)


# Error Handling in Definitions

Errors that arise from header definitions and references.

Definitions are resolved after the **entire header** has been read, and references are checked again as data is validated. Two errors are specific to definitions:

| Condition                                | Error code             | Cause                                                            |
| ---------------------------------------- | ---------------------- | ---------------------------------------------------------------- |
| Reference to an undefined schema or type | `schema-not-defined`   | `$name` is used but no `$name` is defined anywhere in the header |
| Reference to an undefined variable       | `variable-not-defined` | `@name` is used but no `@name` is defined anywhere in the header |

> Error codes are stable; messages and positions may vary between implementations. Branch on the code, not the message.

## Undefined schema reference

A `$` reference must name a schema or type defined in the header. An undefined name fails with `schema-not-defined`:

```ruby
~ $schema: { name: string, home: $address }
---
~ John, { Main St, NYC }    # ✗ schema-not-defined — $address is never defined
```

## Undefined variable reference

A `@` reference must name a variable defined in the header. An undefined name fails with `variable-not-defined`:

```ruby
~ $schema: { name: string, isActive: bool }
---
~ John, @active             # ✗ variable-not-defined — @active is never defined
```

## Reference order

Because definitions resolve only after the whole header is read, **order within the header is not significant** — a reference MAY appear before the definition it targets. The following resolves even though `$address` is defined after the schema that uses it:

```ruby
~ $schema: { name: string, home: $address }
~ $address: { street, city }
---
~ John, { Main St, NYC }    # ✓
```

For readability you SHOULD still define a reference before you use it; doing so reads top to bottom and makes the dependency obvious.

## See Also

* [Definitions](/definitions/definitions) · [Schema References](/definitions/schema-references) · [Variables](/definitions/variables)
* [Error Model](/parsing-and-errors/error-model) — the full error catalogue


# Collection

The structure of a collection — an ordered sequence of records in a data section.

A **collection** is an ordered sequence of **records** within a data section of a document. Each record is an object, written on its own line and introduced by a tilde `~`. Collections make it efficient to serialize, batch, and stream many objects — datasets, tables, event logs — in a concise, uniform form.

> **Familiar parallels.** A collection is conceptually similar to a dataset in CSV, a stream in JSON Lines, or a record array in Avro — but each record is a full Internet Object object.

A collection is always part of a document, **not** a standalone document. A document has a header and a data section; a data section holds either a single object or a collection of one or more records. Records may be **homogeneous** (the same shape) or **heterogeneous** (each shaped differently); each record is independent, so a failure in one does not affect the rest.

## Syntax

A collection is one or more records. Each record is a tilde `~` followed by an object:

```ebnf
collection     = collectionItem+
collectionItem = "~" [ object ]
```

Here `object` is an object as defined in the [Objects](/structure-and-syntax/values/object) specification, in either open or closed form. An absent body (a bare `~`) is an **empty record**. A bare scalar or array is promoted to an object — see [Type promotion](#type-promotion).

### Structural characters

| Symbol  | Name         | Unicode            | Role                             |
| ------- | ------------ | ------------------ | -------------------------------- |
| `~`     | Tilde        | `U+007E`           | Begins a record                  |
| `,`     | Comma        | `U+002C`           | Separates values within a record |
| `{` `}` | Curly braces | `U+007B`, `U+007D` | Enclose a closed-object record   |

## Records

A **record** (also called a *collection item*) is the top-level object immediately following a tilde in a collection. Every record is parsed as an object:

* A record MUST be a valid object — either open form (comma-separated values) or closed form (enclosed in `{ }`).
* A bare `~` is an **empty record** and loads as an empty object (`{}`).

### Type promotion

If a record looks like a single scalar (number, string, boolean, null) or an array, it is promoted to an open object holding that value at positional index `0`. Unnamed values in an open object always take positional keys (`0`, `1`, `2`, …):

```ruby
---
~ 1                   # record: { "0": 1 }
~ true                # record: { "0": true }
~ [red, green, blue]  # record: { "0": [red, green, blue] }
~ John Doe            # record: { "0": "John Doe" }
~ {1, 2}              # record: { "0": 1, "1": 2 }
~                     # empty record: {}
~ name: John Doe      # record: { "name": "John Doe" }
```

> Every record is an object, whether it is written as a scalar, an array, or an explicit object. A schema later maps these values to named members.

## Valid forms

### Open-object records (recommended)

Open form is the most concise and is the recommended style:

```ruby
---
~ 101, Thomas, 25, HR, {Bond Street, New York, NY}
~                                                    # empty record → {}
~ 102, George, 30, Sales, {Duke Street, New York, NY}
```

### Closed-object records

A record may be a closed object enclosed in `{ }`:

```ruby
---
~ {Jane Doe, 20, f, N/A, [0xFF0000, 0x0000FF], F}
```

### JSON-style records

Quoted keys and standard JSON punctuation are accepted, so JSON-shaped records read naturally:

```ruby
---
~ {"name": "John Doe", "address": {"street": "Main St", "city": "Seattle"}, "is_active": true}
~ {"name": "Eve", "age": 33, "location": {"city": "Dallas", "state": "TX"}, "is_active": false}
```

### Mixed records

Open and closed records may be mixed in one collection:

```ruby
---
~ Dave, 40, m, {Main St, Seattle, WA}, [purple], T
~ {Eve, 33, f, {Elm St, Dallas, TX}, [orange], F}
```

## Whitespace, commas, and comments

* Whitespace around the tilde, commas, and braces is insignificant.
* Records are usually separated by newlines, but any whitespace works.
* A trailing comma inside an object is allowed and ignored.
* Comments (`#` to end of line) may trail a record or stand alone; they are ignored.

## Invalid forms

Some inputs are genuine syntax errors; others parse without error but not the way you might expect. Both are worth recognizing.

### Genuine errors

A record (`~`) cannot follow a bare, non-collection object in the same section:

```ruby
---
101, Thomas, 25        # a single (non-collection) object …
~ 102, George          # ✗ unexpected-token — a record cannot follow a bare object
```

An unterminated object or array, or stray tokens after a closed object, are also errors. (Each is reported against the record it appears in; the surrounding records are unaffected.)

```ruby
~ {101, 25, HR} extra               # ✗ unexpected-token — tokens after a closed object
~ Alice, f, {Third St, NY, [green]  # ✗ expecting-bracket — object/array not closed
```

### Common mistakes

Missing separators do **not** raise an error — spaces never separate values, so a run of words collapses into a single open string:

```ruby
---
~ 101 Thomas 25 HR     # one value: the open string "101 Thomas 25 HR"
~ 101, 25 HR           # two values: 101 and the open string "25 HR"
```

> If a record loads with fewer members than you expect, look for a missing comma — spaces alone never separate values.

## Independent validation

Each record is parsed and validated on its own. If a record fails — a syntax error or a validation error — only that record is reported as an error; the records before and after it still load:

```ruby
~ John, 28, m, {Main St, LA}, [red], T          # loads
~ Jane, N/A, f, {Second St, LA}, [blue], F      # loads
~ Alice, f, {Third St, NY, [green], T           # ✗ expecting-bracket — object not closed
~ Bob, 35, m, {Fourth St, NY}, [yellow], T      # loads — unaffected by the error above
```

A conformant processor SHOULD collect per-record errors and continue rather than stop at the first failure. See [Collection Rules](/collections/collection-rules) for schema validation, empty-record rules, and a worked example.

## Preservation of order

Record order is preserved exactly as written. Whitespace and comments are insignificant and do not appear in the loaded result. How members are named, deduplicated, and mapped to fields is governed by the schema — or, without a schema, by positional index.

## See Also

* [Objects](/structure-and-syntax/values/object) — the object grammar a record follows
* [Creating Collections](/collections/creating-collection) — collections with and without a schema
* [Collection Rules](/collections/collection-rules) — validation, empty records, and error handling
* [Data Streaming](/collections/data-streaming) — collections produced and consumed over time
* [Data Sections](/structure-and-syntax/introduction/data) — where a collection sits in a document
* [Schema Definition Language](/schema-definition-language/internet-object-schema) — validating records


# Creating Collections

Creating collections, with or without a schema.

A collection is a sequence of records in the data section, each introduced by a tilde `~`. You can create one with or without a schema — though a schema is recommended.

## Simple collection (no schema)

Without a schema, each record is parsed on its own and its values are mapped to positional indices. Records may differ in shape:

```ruby
---
~ Ironman, 20, Male, { Bond Street, New York, NY }
~ Spiderman, 25, Male, { Duke Street, New York, NY }, cool
```

## Explicit collection (with schema)

Define a schema in the header; every record is validated against it. Use a keyed reference (`address: $address`) to reuse a shape:

```ruby
~ $address: { street, city, state }
~ $schema: { name: string, age: { int, min: 18 }, address: $address }
---
~ Ironman, 20, { Bond Street, New York, NY }      # ✓
~ Wonderwoman, 25, { Z Street, San Francisco, CA } # ✓
```

> Reference a shape with a **keyed** member (`address: $address`). A bare `$address` in the schema is read as a field literally named `$address`, not as the referenced shape.

## See Also

* [Collection](/collections/collection) · [Collection Rules](/collections/collection-rules)
* [Schema References](/definitions/schema-references)


# Collection Rules

Validation rules for collections — schema-less records, empty records, errors.

## Records without a schema

If no schema is defined, each record may have a different shape, and values are mapped to positional indices (`0`, `1`, `2`, …):

```ruby
---
~ John Doe, 20, female
~ true, false
~ marketing, 123, { Z Street, Los Angeles, CA }
```

The first record loads as `{ "0": "John Doe", "1": 20, "2": "female" }`, and so on.

> It is good practice to define a schema even though collections allow schema-less records.

## Empty records

A record consisting of just `~` is an **empty object** (`{}`). It is valid only if **every** field in the schema is optional and/or nullable:

```ruby
~ $schema: { name?*: string, age?*: { int, max: 25 } }
---
~ John, 25     # ✓
~ William      # ✓ (age omitted)
~              # ✓ (empty object; all fields optional/nullable)
```

If any field is required, an empty record fails:

```ruby
~ $schema: { name: string, age?*: { int, max: 25 } }
---
~ John, 25     # ✓
~              # ✗ value-required — name is required
```

## Independent validation & error handling

Each record is validated independently. A failing record is marked as an error; the rest are unaffected:

```ruby
~ $schema: { name: string, age: { int, max: 25 } }
---
~ James, 20    # ✓
~ Alex, 30     # ✗ invalid-range — age exceeds 25
~ Bob, 22      # ✓
```

A conformant processor SHOULD collect per-record errors and continue, rather than stopping at the first failure.

## See Also

* [Collection](/collections/collection) · [Creating Collections](/collections/creating-collection)
* [Parser Behavior & Recovery](/parsing-and-errors/parser-behavior)


# Data Streaming

How collections enable streaming of records.

Because a collection is a sequence of independent records, it is naturally **streamable**: a producer can emit records over time and a consumer can process each as it arrives, without waiting for the whole document.

```ruby
~ $schema: { name: string, address: { street, city, state }, active: bool }
---
~ John Doe, { Red Street, Phoenix, AZ }, T
~ Alex, { Carnival Street, San Francisco, CA }, T
```

Further records for the same collection can be sent later in additional batches; a processor merges them into the same collection. Each record is validated on its own, so a malformed record does not interrupt the stream.

> **The full protocol lives in its own chapter.** Framing, the stream-item model, schema and state, the error model, and reader and writer obligations are specified normatively in the [Streaming](/streaming/streaming) chapter. This page only shows why a collection is streamable; the contract is there.

## See Also

* [Streaming](/streaming/streaming) — the normative, platform-agnostic streaming protocol
* [Collection](/collections/collection) · [Collection Rules](/collections/collection-rules)
* [Creating Collections](/collections/creating-collection)


# Overview

How Internet Object schemas describe the shape of data, and the pieces that make them up.

An Internet Object **schema** describes the shape of the objects in a document: their members, the type of each value, and the constraints each value must satisfy. A schema is written in the **same object syntax as the data it validates**, so it is compact, readable, and easy to author by hand.

> **Schema and data share one syntax.** Unlike map-based schema languages (JSON Schema, XML Schema), an IO schema looks like the object it validates. There is no second grammar to learn.

A schema is normally declared once in the [header](/structure-and-syntax/introduction/header) — as the default `$schema` or as a reusable `$` reference — and applied to every record in the data section. Validating a value against a schema either succeeds or produces a stable [error code](/parsing-and-errors/error-model).

## The shape of an object

A schema is a comma-separated list of **members**. In its simplest form it is just a list of keys; each value then has the `any` type:

```ruby
name, age, address
---
John, 30, { Main St, NYC }
```

Give a member a type by writing `key: type`:

```ruby
name: string, age: int, isActive: bool
---
John, 30, T
```

Add constraints by replacing the bare type with a **MemberDef** — a small object whose first value is the type and whose remaining entries are constraints:

```ruby
score: { int, min: 0, max: 100 }
---
85
```

A value outside the constraint fails validation with a stable code:

```ruby
score: { int, min: 0, max: 100 }
---
150          # ✗ invalid-range — score is above max
```

Members nest: a member's type can itself be an object schema or an array:

```ruby
name: string, address: { street: string, city: string }
---
John, { Main St, NYC }
```

## The building blocks

A schema is assembled from a few orthogonal pieces. Each has its own reference page:

| Piece                             | What it does                                                        | Reference                                                                                     |
| --------------------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| **MemberDef**                     | One member: a type plus its constraints                             | [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)                   |
| **Data types**                    | The base types and built-in shortcuts (`string`, `int`, `email`, …) | [Schema Data Types](/schema-definition-language/data-types)                                   |
| **TypeDef**                       | The fixed set of options each type accepts                          | [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef)                       |
| **Optional, nullable & defaults** | `?`, `*`, and default values                                        | [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)                   |
| **Open & dynamic schemas**        | Allowing extra members with `*` and `*: type`                       | [Open & Dynamic Schemas](/schema-definition-language/advanced-schema-concepts/dynamic-schema) |
| **References**                    | Reusable schemas and types (`$name`)                                | [Schema References](/definitions/schema-references)                                           |
| **Union types**                   | A value matching one of several types (`anyOf`)                     | [Union Types](/schema-definition-language/advanced-schema-concepts/union-types)               |
| **Composition & reuse**           | Building large schemas from small ones                              | [Composition & Reuse](/schema-definition-language/advanced-schema-concepts/composition)       |

The exact placement rules — open vs. closed objects, keyed vs. positional members, and how the default `$schema` is chosen — are covered in [Schema Representation](/schema-definition-language/schema-representation).

## Optional, nullable, and default members

A member name can carry a marker that changes how a missing or `null` value is treated. These markers are **first-class schema features**, not informal conventions: a conformant validator MUST honor them.

* **Optional** (`?`) — the member MAY be omitted from the data.
* **Nullable** (`*`) — the member MAY be `null` (`N`).
* **Both** (`?*`) — the member may be omitted or `null`.
* **Default** — the second value in a MemberDef supplies a value to use when the member is omitted (equivalently, the keyed `default:` option).

```ruby
~ $schema: { name: string, email?: string, nickname?*: string, role?: { string, guest } }
---
~ John                          # email & nickname omitted, role defaults to "guest"
~ Mary, mary@x.com, N, admin    # nickname is null, role is "admin"
```

See [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef) for the full resolution rules and [Open & Dynamic Schemas](/schema-definition-language/advanced-schema-concepts/dynamic-schema) for accepting members beyond those listed.

## Schema grammar

The shape of a member, informally:

```ebnf
schema    = member ( "," member )*
member    = openMember | [ key modifier? ":" ] typeOrDef
openMember = "*" [ ":" typeOrDef ]
modifier  = "?" | "*" | "?*"
typeOrDef = typeName | ref | memberDef | "{" schema "}" | "[" typeOrDef "]"
memberDef = "{" ( typeName | ref ) ( "," constraint )* "}"
ref       = "$" name
```

The complete, normative grammar for documents and schemas is in the [Formal Grammar](/appendices/grammar) appendix.

## A complete example

A header defines a reusable `$address` and a default `$schema`; the data section is validated against it:

```ruby
~ $address: { street: string, city: string, zip?: int }
~ $schema: {
    name: string,
    age?: int,
    email: { string, pattern: "^[^@]+@[^@]+$" },
    isActive: bool,
    address?: $address
}
---
~ John Doe, 30, john@example.com, T, { Bond Street, New York }
~ Jane Doe, 28, jane@example.com, F
```

Both records validate: `John Doe` supplies an address; `Jane Doe` omits the optional `address`.

## See Also

* [Schema Representation](/schema-definition-language/schema-representation) — open/closed, keyed/positional, default schema
* [Schema Data Types](/schema-definition-language/data-types) — the base types and shortcuts
* [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef) — types, constraints, optional/nullable/default
* [Schema References](/definitions/schema-references) — reusable `$` schemas and types
* [Error Model](/parsing-and-errors/error-model) — the validation error catalogue
* [JSON Compatibility](/interoperability/json-compatibility) — mapping to and from JSON Schema


# Schema Representation

How a schema is written and how data is mapped to it — open/closed, positional/keyed, and the default schema.

A schema is written in the same object syntax as data. This page covers how to write one and how a document's data is mapped to it. For the building blocks of a schema, see the [Overview](/schema-definition-language/internet-object-schema); for types and constraints, see [Schema Data Types](/schema-definition-language/data-types) and [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef).

## Open and closed schema objects

A **top-level** schema may be written in open form, without surrounding braces:

```ruby
name, age, address
---
John, 30, { Main St, NYC }
```

A **nested** object member must be enclosed in braces, because the braces are what mark the value as an object:

```ruby
name: string, address: { street: string, city: string }
---
John, { Main St, NYC }
```

A member with no type defaults to `any`, so `name, age, address` is shorthand for three `any` members. See the [any](/schema-definition-language/data-types/any) type.

## How data maps to a schema

Every schema member has a name. A data object can supply its values **positionally** (matched to the schema's members in order) or **by key**:

```ruby
~ $schema: { name: string, age: int }
---
~ John, 30                # positional: maps to name, age
~ { name: Mary, age: 25 } # keyed: maps by name
```

### Mixing positional and keyed values

Within one object, positional values MUST come before any keyed values. Once a keyed value appears, every later value MUST also be keyed. A positional value after a keyed one fails:

```ruby
~ $schema: { name: string, age: int }
---
~ { John, age: 30 }       # ✓ positional then keyed
~ { name: John, 30 }      # ✗ unexpected-positional-member
```

> Prefer positional data only when all members are required and the order is unambiguous; otherwise use keyed values for clarity and resilience to schema changes.

## Nested objects and arrays

A member's type can be a nested object schema or an array. Use `{ … }` for an object and `[ … ]` for an array; an array's element type goes inside the brackets:

```ruby
~ $schema: {
    name: string,
    address: { street: string, city: string, state: string },
    tags: [string],
    skills: [{ title: string, level: int }]
}
---
~ Jane Doe, { X Street, New York, NY }, [lead, mentor], [{ Coding, 5 }, { Design, 3 }]
```

Here `tags` is an array of strings and `skills` is an array of objects. See [Array](/schema-definition-language/data-types/array) and [Object (SchemaDef)](/schema-definition-language/data-types/object) for details.

## Choosing the schema

### The default schema

The default schema is the header definition named **`$schema`**. It is applied to every record in the data section:

```ruby
~ $schema: { name: string, age: int }
---
~ John, 30
~ Mary, 25
```

The name is case-sensitive: `$Schema` or `$mySchema` is an ordinary reference, **not** the default. A document with named references but no `$schema` has no default schema.

### Reusable named schemas

Define a shape once with a `$` reference and reuse it by name. Reference a shape with a **keyed** member (`home: $address`); a bare `$address` in a schema is read as a member literally named `$address`, not as the referenced shape:

```ruby
~ $address: { street: string, city: string, state: string }
~ $schema: { name: string, home: $address, work?: $address }
---
~ John Doe, { Main St, NYC, NY }, { 5th Ave, NYC, NY }
~ Jane Doe, { Oak Ave, LA, CA }
```

See [Schema References](/definitions/schema-references) for resolution rules and type references.

### No schema

When there is no `$schema` — an empty header, or a header with only metadata and references — each value is mapped to a positional index key (`"0"`, `"1"`, …):

```ruby
---
John Doe, 25, { X-street, California, US }
```

loads as:

```ruby
{ "0": "John Doe", "1": 25, "2": { "0": "X-street", "1": "California", "2": "US" } }
```

## See Also

* [Overview](/schema-definition-language/internet-object-schema) — the building blocks of a schema
* [Schema Data Types](/schema-definition-language/data-types) — the base types and shortcuts
* [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef) — constraints, optional, nullable, and defaults
* [Schema References](/definitions/schema-references) — reusable `$` schemas and types
* [Collection Rules](/collections/collection) — applying a schema across many records


# Schema Data Types

The Internet Object schema type system — base types, shortcuts, and TypeDefs.

A schema constrains each member to a **type**. The type system is small by design: a handful of **base types**, plus a closed set of **shortcuts** — names that stand for a base type with preset constraints baked in. Every type is configured through its **TypeDef**, the fixed set of options it accepts.

## Base types

| Type                       | Validates                                    | Reference                                                             |
| -------------------------- | -------------------------------------------- | --------------------------------------------------------------------- |
| `string`                   | Text                                         | [String Types](/schema-definition-language/data-types/string)         |
| `number`                   | An IEEE-754 number                           | [Numeric Types](/schema-definition-language/data-types/number)        |
| `bigint`                   | An arbitrary-precision integer (`123n`)      | [BigInt](/schema-definition-language/data-types/bigint)               |
| `decimal`                  | A fixed-precision decimal (`123.45m`)        | [Decimal](/schema-definition-language/data-types/decimal)             |
| `bool`                     | `true` / `false`                             | [Bool](/schema-definition-language/data-types/bool)                   |
| `date`, `time`, `datetime` | Temporal values (`d'…'`, `t'…'`, `dt'…'`)    | [Date and Time](/schema-definition-language/data-types/date-and-time) |
| `binary`                   | Byte data, written as base64 (`b'…'`)        | [Binary](/schema-definition-language/data-types/binary)               |
| `object`                   | A structured shape (a SchemaDef)             | [Object (SchemaDef)](/schema-definition-language/data-types/object)   |
| `array`                    | An ordered list of values                    | [Array](/schema-definition-language/data-types/array)                 |
| `any`                      | Any value; the default when no type is given | [Any](/schema-definition-language/data-types/any)                     |

A member written without a type defaults to `any`, so `name, age` declares two `any` members.

> `date`, `time`, and `datetime` are **their own types**, not subtypes of `string`. Earlier drafts described them as string-derived; they are temporal types with their own literal values. See [Date and Time](/schema-definition-language/data-types/date-and-time).

## Shortcuts

A **shortcut** is a built-in name equal to a base type plus preset constraints. It is not a new type — it is a convenient, validated configuration of a base type. A conformant validator MUST recognize every shortcut name.

| Base     | Shortcuts                                                                     | Each shortcut is…                                    |
| -------- | ----------------------------------------------------------------------------- | ---------------------------------------------------- |
| `string` | `email`, `url`                                                                | `string` with a built-in pattern                     |
| `number` | `int`, `uint`, `int8`, `int16`, `int32`, `uint8` (`byte`), `uint16`, `uint32` | `number` restricted to whole values in a fixed range |

See [Numeric Types](/schema-definition-language/data-types/number) for the full numeric family and ranges, and [String Types](/schema-definition-language/data-types/string) for `email` and `url`.

> **Reserved.** `int64`, `uint64`, `float32`, and `float64` are reserved for future use and are not yet validated by the reference implementation.

## TypeDef and MemberDef

Each type defines a **TypeDef** — the exact set of options it accepts (for example, `string` accepts `pattern`, `minLen`, `maxLen`; `number` accepts `min`, `max`, `multipleOf`). Supplying a type together with chosen options produces a **MemberDef**, the definition of a single member:

```ruby
name:  { string, minLen: 1, maxLen: 100 },   # a string MemberDef
score: { int, min: 0, max: 100 }             # a number MemberDef
---
~ John, 85
```

The first value in a MemberDef is the type; the second is the default; the third is `choices`. Remaining options are written as `key: value` pairs. An option a type does not define is rejected. Each type page lists its TypeDef in full.

## See Also

* [Overview](/schema-definition-language/internet-object-schema) — how schemas are built
* [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef) — the option-contract model
* [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef) — types, constraints, optional/nullable/default
* [Numeric Types](/schema-definition-language/data-types/number) · [String Types](/schema-definition-language/data-types/string) · [Date and Time](/schema-definition-language/data-types/date-and-time)


# Any

The any type — accepts any value, optionally constrained by anyOf or choices.

The **`any`** type accepts a value of **any** type. It is the default when a field is declared without a type (`name` is the same as `name: any`). You can still narrow it with `choices` or, for a union of types, `anyOf`.

```ruby
a, b: any, c: { type: any }
---
~ hello, 42, T        # ✓ — anything goes
```

## Declaring alternatives (anyOf)

`anyOf` lets a field accept any one of several types or MemberDefs — Internet Object's union type.

```ruby
id: { any, anyOf: [string, int] }
---
~ 42        # ✓ matches int
~ abc       # ✓ matches string
```

```ruby
flag: { any, anyOf: [bool, int] }
---
~ hello     # ✗ matches neither
```

Each alternative may be a full MemberDef or a SchemaDef:

```ruby
value: { any, anyOf: [{ int, multipleOf: 5 }, { int, multipleOf: 3 }] }
---
~ 10        # ✓ multiple of 5
~ 9         # ✓ multiple of 3
```

## TypeDef

An `any` MemberDef accepts only the options below.

| Option     | Type                    | Description                                                  |
| ---------- | ----------------------- | ------------------------------------------------------------ |
| `type`     | string                  | The type name `any`.                                         |
| `default`  | any                     | Value used when the member is omitted.                       |
| `choices`  | array                   | Restricts the value to a fixed set (of any type).            |
| `anyOf`    | array of MemberDef/type | The value must match one of these.                           |
| `optional` | bool                    | If `true`, the member may be omitted. Shorthand: `?` suffix. |
| `null`     | bool                    | If `true`, the member may be `null`. Shorthand: `*` suffix.  |

## choices

```ruby
pick: { any, choices: [1, One, T] }
---
~ One       # ✓
~ Two       # ✗ invalid-choice
```

## Optional, nullable & defaults

```ruby
note?*: any
---
~ {}        # ✓ omitted → absent
~ N         # ✓ null
```

## Implementation status (beta)

* Keyed `null:` is not yet honored — use the `*` suffix.

## See Also

* [Union Types (anyOf)](/schema-definition-language/advanced-schema-concepts/union-types)
* [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef) · [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)


# String Types

The string type and its email and url shortcuts.

The **`string`** type validates text. It has two **predefined shortcuts** that are `string` with a built-in pattern: [Email](/schema-definition-language/data-types/string/email) and [URL](/schema-definition-language/data-types/string/url).

> `date`, `time`, and `datetime` are **not** string subtypes — they are their own types with their own values. See [Date and Time](/schema-definition-language/data-types/date-and-time).
>
> For how strings are *written* (open, quoted, raw), see [Strings](/structure-and-syntax/values/string).

## The string family

| Type     | Is                                          |
| -------- | ------------------------------------------- |
| `string` | any text                                    |
| `email`  | `string` validated against an email pattern |
| `url`    | `string` validated against a URL pattern    |

```ruby
contact: email, site: url
---
~ a@b.com, 'https://example.com'    # ✓
~ notanemail, 'https://example.com' # ✗ invalid-email
```

> **Quote values containing `:` or spaces** (URLs, times-of-day, "Last, First"). An unquoted `https://x.com` is misread because the open string ends at `:`.

## TypeDef

A `string` MemberDef accepts only the options below. Any other key is invalid.

| Option        | Type            | Description                                                     |
| ------------- | --------------- | --------------------------------------------------------------- |
| `type`        | string          | `string`, `email`, or `url`. First positional value.            |
| `default`     | string          | Value used when the member is omitted. Second positional value. |
| `choices`     | array of string | Restricts the value to a fixed set. Third positional value.     |
| `pattern`     | string          | A regular expression the value must match.                      |
| `flags`       | string          | Regex flags for `pattern` (e.g. `i`).                           |
| `len`         | int ≥ 0         | Exact length in characters.                                     |
| `minLen`      | int ≥ 0         | Minimum length.                                                 |
| `maxLen`      | int ≥ 0         | Maximum length.                                                 |
| `format`      | string          | Serialization form: `auto` (default), `regular`, `raw`.         |
| `encloser`    | string          | Quote character used when serializing: `"` (default) or `'`.    |
| `escapeLines` | bool            | Whether to escape line breaks on serialization.                 |
| `optional`    | bool            | If `true`, the member may be omitted. Shorthand: `?` suffix.    |
| `null`        | bool            | If `true`, the member may be `null`. Shorthand: `*` suffix.     |

> **`len` precedence.** When `len` is set, `minLen` and `maxLen` are ignored.

## Constraints

### minLen / maxLen / len

```ruby
name: { string, minLen: 5, maxLen: 20 }
---
~ Ethan              # ✓
~ Alexandra Daddario # ✓
~ Leo                # ✗ invalid-min-length
```

### pattern

A regular expression. Use a [raw string](/structure-and-syntax/values/string/raw-strings) (`r'…'`) to avoid escaping backslashes.

```ruby
ssn: { string, pattern: r'^[0-9]{3}-[0-9]{2}-[0-9]{4}$' }
---
~ '123-45-6789'   # ✓
~ '12345678'      # ✗ invalid-pattern
```

### choices

```ruby
dept: { string, choices: [cs, mech, civil] }
---
~ cs     # ✓
~ art    # ✗ invalid-choice
```

> Quote choices that look like numbers or contain commas, e.g. `["19.02, 72.85"]`, so they are treated as strings.

## Optional, nullable & defaults

```ruby
nickname?*: { string, anonymous }   # optional + nullable, default "anonymous"
---
~ {}      # ✓ → "anonymous" (omitted, default applies)
~ N       # ✓ → null
~ John    # ✓ → "John"
```

| Input                   | Result                                                         |
| ----------------------- | -------------------------------------------------------------- |
| valid text              | the string                                                     |
| fails a constraint      | `invalid-*` error (`invalid-min-length`, `invalid-pattern`, …) |
| `N`, nullable (`*`)     | `null`                                                         |
| `N`, not nullable       | `null-not-allowed` error                                       |
| omitted, `default` set  | the default                                                    |
| omitted, optional (`?`) | absent                                                         |
| omitted, required       | `value-required` error                                         |

> **Use the `*` suffix for nullability** — keyed `null:` is not yet honored (see below).

## Implementation status (beta)

* Keyed `optional:` works; keyed `null:` is not yet honored — use the `*` suffix.

## See Also

* [Strings (value syntax)](/structure-and-syntax/values/string)
* [Email](/schema-definition-language/data-types/string/email) · [URL](/schema-definition-language/data-types/string/url)
* [Date and Time](/schema-definition-language/data-types/date-and-time)
* [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef) · [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)


# Email

The email type — a string validated as an email address.

`email` is a `string` shortcut (see [String Types](/schema-definition-language/data-types/string)) whose value MUST be a valid email address. It shares the string [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef) (`choices`, `pattern`, `minLen`, …) and adds email-format validation.

```ruby
userEmail: email
---
~ test@example.com    # ✓
~ notanemail          # ✗ invalid-email
```

Restrict to a fixed set with `choices`:

```ruby
companyEmail: { email, choices: [info@acme.com, sales@acme.com] }
---
~ info@acme.com       # ✓
~ other@acme.com      # ✗ invalid-choice
```

## See Also

* [String Types](/schema-definition-language/data-types/string) · [URL](/schema-definition-language/data-types/string/url)
* [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)


# URL

The url type — a string validated as a URL.

`url` is a `string` shortcut (see [String Types](/schema-definition-language/data-types/string)) whose value MUST be a valid URL. It shares the string [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef) and adds URL-format validation.

> **Quote URL values.** A URL contains `:` and `/`, which end an unquoted (open) string, so URLs must be written as quoted strings.

```ruby
website: url
---
~ 'https://example.com'         # ✓
~ "https://example.com/p?q=1"   # ✓
~ 'not a url'                    # ✗ invalid-url
```

Restrict to a fixed set with `choices`:

```ruby
homepage: { url, choices: ['https://a.com', 'https://b.com'] }
---
~ 'https://a.com'    # ✓
```

## See Also

* [String Types](/schema-definition-language/data-types/string) · [Email](/schema-definition-language/data-types/string/email)
* [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)


# Numeric Types

The number type and its family of integer, unsigned, and float shortcuts.

The **`number`** type validates a numeric value. It is the base of a small family of **predefined shortcuts** — `int`, `uint`, `int8`, `byte`, and so on — where each shortcut is simply `number` with a fixed set of constraints baked in. For example, `int8` is `number` restricted to whole values in −128…127.

> Two related numeric types have their own pages: [BigInt](/schema-definition-language/data-types/bigint) (arbitrary-precision integers, suffix `n`) and [Decimal](/schema-definition-language/data-types/decimal) (fixed-precision decimals, suffix `m`). For how numbers are *written* (decimal, hex, octal, binary, scientific, `NaN`, `Inf`), see [Numeric Values](/structure-and-syntax/values/number).

## The number family

Each name below is the base `number` type plus preset constraints. A conformant validator MUST recognize all of these names.

| Type             | Whole number? | Range                          |
| ---------------- | ------------- | ------------------------------ |
| `number`         | no            | IEEE-754 double                |
| `float`          | no            | IEEE-754 double                |
| `int`            | yes           | unbounded integer              |
| `uint`           | yes           | integer ≥ 0                    |
| `int8`           | yes           | −128 … 127                     |
| `uint8` / `byte` | yes           | 0 … 255                        |
| `int16`          | yes           | −32 768 … 32 767               |
| `uint16`         | yes           | 0 … 65 535                     |
| `int32`          | yes           | −2 147 483 648 … 2 147 483 647 |
| `uint32`         | yes           | 0 … 4 294 967 295              |

`byte` is an alias for `uint8`. A value outside a type's range MUST be rejected with an `invalid-range` error:

```ruby
age: int8
---
~ 200      # ✗ invalid-range — int8 max is 127
```

> **Whole-number rule.** The `int`/`uint`/`int8…32`/`uint8…32` shortcuts MUST reject values with a fractional part (`int` accepts `42`, not `42.5`). `number` and `float` accept any finite double.

> **Reserved (not yet supported).** `int64`, `uint64`, `float32`, and `float64` are reserved for future use; the reference implementation currently rejects them.

## TypeDef

A `number` MemberDef accepts only the options below. Any other key is invalid.

| Option       | Type            | Description                                                                        |
| ------------ | --------------- | ---------------------------------------------------------------------------------- |
| `type`       | string          | Type name (`number` or any family member). First positional value.                 |
| `default`    | number          | Value used when the member is omitted. Second positional value.                    |
| `choices`    | array of number | Restricts the value to a fixed set. Third positional value.                        |
| `min`        | number          | Minimum allowed value (inclusive).                                                 |
| `max`        | number          | Maximum allowed value (inclusive).                                                 |
| `multipleOf` | number          | The value must be an exact multiple of this.                                       |
| `format`     | string          | Serialization format: `decimal` (default), `hex`, `octal`, `binary`, `scientific`. |
| `optional`   | bool            | If `true`, the member may be omitted. Shorthand: `?` suffix on the key.            |
| `null`       | bool            | If `true`, the member may be `null`. Shorthand: `*` suffix on the key.             |

## Constraints

### min / max

Inclusive bounds. A value outside the bounds is rejected with `invalid-range`.

```ruby
age: { number, min: 18, max: 25 }
---
~ 18     # ✓
~ 25     # ✓
~ 35     # ✗ invalid-range
```

> An explicit `min`/`max` **replaces** a shortcut's built-in bound rather than narrowing it — e.g. `{ int8, min: -200 }` currently accepts −200. See *Implementation status* below.

### multipleOf

The value must be an exact multiple of the given number.

```ruby
rollNo: { number, multipleOf: 5 }
---
~ 10     # ✓
~ -10    # ✓
~ 12     # ✗ — must be a multiple of 5
```

### choices

Restricts the value to a fixed set. As the third positional value it may omit the key.

```ruby
code: { number, choices: [234, 245, 456] }
---
~ 245    # ✓
~ 5      # ✗ invalid-choice
```

### format

Controls how the number is **written** on serialization; it does not restrict which input notations are accepted (any [notation](/structure-and-syntax/values/number) is read).

```ruby
flags: { uint16, format: hex }      # serialized as 0x…
mask:  { uint8,  format: binary }   # serialized as 0b…
```

## Special values

`NaN` and `Inf`/`-Inf` are valid only for `number`/`float`. When combined with a numeric bound, the reference implementation currently coerces them to `null` rather than validating them — see *Implementation status*.

## Optional, nullable & defaults

How a member resolves (verified behavior):

| Input                                         | Result                   |
| --------------------------------------------- | ------------------------ |
| value present, valid                          | the value                |
| value present, out of range                   | `invalid-range` error    |
| value is `N` (null), key is nullable (`*`)    | `null`                   |
| value is `N` (null), key is **not** nullable  | `null-not-allowed` error |
| value omitted, `default` set                  | the default              |
| value omitted, key optional (`?`), no default | absent                   |
| value omitted, required, no default           | `value-required` error   |

```ruby
a?: { number, 7 }    # optional with default 7  →  omitted yields 7
b*: number           # nullable  →  N yields null
c?*: number          # optional and nullable
```

> **Use the `*` suffix for nullability.** The keyed `null: T` option is part of the TypeDef but is **not currently honored** — only the `*` suffix enables null. (See below.)

## Implementation status (beta)

A few behaviors on this page describe the agreed target; the reference implementation is catching up:

* **Whole-number enforcement** for the `int` family is not yet applied (`int` currently accepts `3.14`).
* **`byte`** alias is being added (currently only `uint8` is recognized).
* **Explicit `min`/`max`** can widen a shortcut's range (under review).
* **`NaN`/`Inf`** under a bound resolve to `null` (under review).
* **Keyed `null:`** is not honored; use the `*` suffix.

## Examples

A schema mixing notations and family members (adapted from the playground):

```ruby
~ $row: { hex: uint8, oct: uint8, bin: uint8, dec: number, sci: { number, min: 999999999 } }
--- rows: $row
~ 0x11, 0o2, 0b11, 10, 4.329e+10
~ 0x22, 0o3, 0b100, 20, 2.329e+20
```

Any input notation works for any number type — `0x11`, `0o21`, `0b10001`, and `17` all denote the same value.

## See Also

* [Numeric Values](/structure-and-syntax/values/number) — how numbers are written
* [BigInt](/schema-definition-language/data-types/bigint) · [Decimal](/schema-definition-language/data-types/decimal) — related numeric types
* [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef) · [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef) — how type options work


# BigInt

The bigint type — arbitrary-precision integers.

The **`bigint`** type validates an arbitrary-precision integer — values too large for the double-based `number` family (see [Numeric Types](/schema-definition-language/data-types/number)). In data it is written with an `n` suffix: `123n`, `0xFFn`.

> For the literal syntax, see [BigInt values](/structure-and-syntax/values/number/bigint).

## TypeDef

A `bigint` MemberDef accepts only the options below. Any other key is invalid.

| Option       | Type            | Description                                                        |
| ------------ | --------------- | ------------------------------------------------------------------ |
| `type`       | string          | The type name `bigint`. First positional value.                    |
| `default`    | bigint          | Value used when the member is omitted. Second positional value.    |
| `choices`    | array of bigint | Restricts the value to a fixed set.                                |
| `min`        | bigint          | Minimum allowed value (inclusive).                                 |
| `max`        | bigint          | Maximum allowed value (inclusive).                                 |
| `multipleOf` | bigint          | The value must be an exact multiple of this.                       |
| `format`     | string          | Serialization base: `decimal` (default), `hex`, `octal`, `binary`. |
| `optional`   | bool            | If `true`, the member may be omitted. Shorthand: `?` suffix.       |
| `null`       | bool            | If `true`, the member may be `null`. Shorthand: `*` suffix.        |

## Examples

```ruby
id: bigint
---
~ 123n                  # ✓
~ 0xFFn                 # ✓ (255)
~ 99999999999999999999999999999n   # ✓
```

```ruby
big: { bigint, min: 100n }
---
~ 50n     # ✗ invalid-range
```

## Optional, nullable & defaults

Resolution follows the [common rules](/schema-definition-language/data-types/number#optional-nullable--defaults):

```ruby
id?*: bigint    # optional + nullable
---
~ {}     # ✓ omitted → absent
~ N      # ✓ null
~ 7n     # ✓
```

## Implementation status (beta)

* Keyed `null:` is not yet honored — use the `*` suffix.

## See Also

* [BigInt values](/structure-and-syntax/values/number/bigint)
* [Numeric Types](/schema-definition-language/data-types/number) · [Decimal](/schema-definition-language/data-types/decimal)
* [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef) · [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)


# Decimal

The decimal type — fixed-precision decimal numbers.

The **`decimal`** type validates an exact, fixed-precision decimal — for money and other values where binary floating point would lose accuracy. In data it is written with an `m` suffix: `123.45m`.

> For the literal syntax, see [Decimal values](/structure-and-syntax/values/number/decimal).

## TypeDef

A `decimal` MemberDef accepts only the options below. Any other key is invalid.

| Option       | Type             | Description                                                     |
| ------------ | ---------------- | --------------------------------------------------------------- |
| `type`       | string           | The type name `decimal`. First positional value.                |
| `default`    | decimal          | Value used when the member is omitted. Second positional value. |
| `choices`    | array of decimal | Restricts the value to a fixed set.                             |
| `precision`  | int              | Maximum total number of significant digits.                     |
| `scale`      | int              | Exact number of digits after the decimal point.                 |
| `min`        | decimal          | Minimum allowed value (inclusive).                              |
| `max`        | decimal          | Maximum allowed value (inclusive).                              |
| `multipleOf` | decimal          | The value must be an exact multiple of this.                    |
| `optional`   | bool             | If `true`, the member may be omitted. Shorthand: `?` suffix.    |
| `null`       | bool             | If `true`, the member may be `null`. Shorthand: `*` suffix.     |

## Precision & scale

`precision` and `scale` together give SQL-style `DECIMAL(precision, scale)` validation:

* **scale** — the number of fractional digits MUST equal `scale`.
* **precision** — the total significant digits MUST NOT exceed `precision`.

```ruby
price: { decimal, precision: 5, scale: 2 }
---
~ 123.45m    # ✓  (5 digits, 2 after the point)
```

```ruby
rate: { decimal, scale: 2 }
---
~ 1.5m       # ✗ invalid-scale  (1 fractional digit, scale requires 2)
```

> With neither `precision` nor `scale`, a `decimal` is compared by its exact value.

## Optional, nullable & defaults

Resolution follows the [common rules](/schema-definition-language/data-types/number#optional-nullable--defaults):

```ruby
amount?*: decimal
---
~ {}      # ✓ omitted → absent
~ N       # ✓ null
~ 9.99m   # ✓
```

## Implementation status (beta)

* Keyed `null:` is not yet honored — use the `*` suffix.

## See Also

* [Decimal values](/structure-and-syntax/values/number/decimal)
* [Numeric Types](/schema-definition-language/data-types/number) · [BigInt](/schema-definition-language/data-types/bigint)
* [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef) · [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)


# Date and Time

The datetime, date, and time types.

Internet Object has three temporal types, each with its own literal value:

| Type       | Literal                    | Captures               |
| ---------- | -------------------------- | ---------------------- |
| `date`     | `d'2024-03-20'`            | calendar date          |
| `time`     | `t'14:30:45'`              | time of day            |
| `datetime` | `dt'2024-03-20T14:30:45Z'` | date + time (ISO 8601) |

> For the literal value syntax in detail, see [Date and Time](/structure-and-syntax/values/date-and-time).

```ruby
created: datetime, birthday: date, opensAt: time
---
~ dt'2024-03-20T14:30:00Z', d'1990-05-01', t'09:00:00'   # ✓
```

## TypeDef

These types share one TypeDef. A MemberDef accepts only the options below.

| Option     | Type     | Description                                                  |
| ---------- | -------- | ------------------------------------------------------------ |
| `type`     | string   | `datetime`, `date`, or `time`. First positional value.       |
| `default`  | datetime | Value used when the member is omitted.                       |
| `choices`  | array    | Restricts the value to a fixed set.                          |
| `min`      | datetime | Earliest allowed value (inclusive).                          |
| `max`      | datetime | Latest allowed value (inclusive).                            |
| `optional` | bool     | If `true`, the member may be omitted. Shorthand: `?` suffix. |
| `null`     | bool     | If `true`, the member may be `null`. Shorthand: `*` suffix.  |

## Constraints

### min / max

```ruby
when: { datetime, min: dt'2024-01-01T00:00:00Z', max: dt'2024-12-31T00:00:00Z' }
---
~ dt'2024-06-01T00:00:00Z'    # ✓
```

## Optional, nullable & defaults

```ruby
deletedAt?*: datetime   # optional + nullable
---
~ {}    # ✓ omitted → absent
~ N     # ✓ null
```

| Input                   | Result                   |
| ----------------------- | ------------------------ |
| valid temporal literal  | the value                |
| out of `min`/`max`      | `invalid-range` error    |
| `N`, nullable (`*`)     | `null`                   |
| `N`, not nullable       | `null-not-allowed` error |
| omitted, optional (`?`) | absent                   |
| omitted, required       | `value-required` error   |

## Implementation status (beta)

* **`datetime` currently drops the seconds component** on parse (`dt'…T14:30:45Z'` is read as `…14:30:00`). Under review.
* Keyed `null:` is not yet honored — use the `*` suffix.

## See Also

* [Date and Time (value syntax)](/structure-and-syntax/values/date-and-time)
* [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef) · [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)


# Binary

The binary type — byte data written as base64.

The **`binary`** type validates a sequence of bytes. In data, binary is written as a base64 literal with a `b` prefix: `b'SGVsbG8='`. (Base64 is the *encoding*; `binary` is the *type*.)

> For the literal value syntax, see [Binary values](/structure-and-syntax/values/binary).

## TypeDef

A `binary` MemberDef accepts the options below.

| Option     | Type            | Description                                                  |
| ---------- | --------------- | ------------------------------------------------------------ |
| `type`     | string          | The type name `binary`. First positional value.              |
| `default`  | binary          | Value used when the member is omitted.                       |
| `choices`  | array of binary | Restricts the value to a fixed set.                          |
| `len`      | int ≥ 0         | Exact length in bytes.                                       |
| `minLen`   | int ≥ 0         | Minimum length in bytes.                                     |
| `maxLen`   | int ≥ 0         | Maximum length in bytes.                                     |
| `optional` | bool            | If `true`, the member may be omitted. Shorthand: `?` suffix. |
| `null`     | bool            | If `true`, the member may be `null`. Shorthand: `*` suffix.  |

## Example

```ruby
avatar: { binary, maxLen: 65536 }
---
~ b'SGVsbG8='
```

## Implementation status (beta)

> **Binary is under development.** The `binary` schema type is being added to the reference implementation (it is not yet registered), and the `b'…'` value literal is not yet accepted by the parser. The design above is the agreed target; examples are not yet executable and are excluded from the example verifier.

## See Also

* [Binary values](/structure-and-syntax/values/binary)
* [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef) · [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)


# Object (SchemaDef)

The object type — structured key/value data described by a SchemaDef.

The **`object`** type validates structured key/value data. Like an array, it is a **container**: you declare its **shape** — the set of fields and their types. That shape is called a **SchemaDef**.

> For the object *value* syntax (`{ … }`), see [Objects](/structure-and-syntax/values/object).

## Declaring the shape (SchemaDef)

```ruby
addr: { street: string, city: string }   # inline SchemaDef
meta: {}                                  # any object (no fixed shape)
meta: object                              # same as {}
home: $address                            # a referenced SchemaDef
```

A field may itself be any type, nested object, array, or [reference](/definitions/schema-references).

```ruby
name: string, location: { x: int, y: int }
---
~ John, { 1, 2 }            # ✓ location = { x: 1, y: 2 }
~ John, { 1, two }          # ✗ invalid-type (y is not an int)
```

> In a record with several fields, write nested objects in the open positional form (`~ John, { 1, 2 }`). A record written wholly as `{ … }` maps its values to the *record's* fields, not to one field's object.

## TypeDef

An `object` MemberDef accepts only the options below.

| Option     | Type      | Description                                                   |
| ---------- | --------- | ------------------------------------------------------------- |
| `type`     | string    | The type name `object`.                                       |
| `default`  | object    | Value used when the member is omitted.                        |
| `schema`   | SchemaDef | The object's shape. Usually written inline (`{ … }`) instead. |
| `optional` | bool      | If `true`, the member may be omitted. Shorthand: `?` suffix.  |
| `null`     | bool      | If `true`, the member may be `null`. Shorthand: `*` suffix.   |

## Nesting

Objects nest to any depth:

```ruby
~ $address: { street: string, city: string }
~ $schema: { name: string, home: $address }
---
~ John, { Main St, NYC }     # ✓
```

## Open and dynamic objects

An empty SchemaDef (`{}` or `object`) accepts any object. To allow **extra** fields beyond those declared, add `*` to the shape — see [Open & Dynamic Schemas](/schema-definition-language/advanced-schema-concepts/dynamic-schema):

```ruby
~ $schema: { name: string, * }
---
~ John, extra1, extra2       # ✓ extra fields accepted
```

## Optional, nullable & defaults

```ruby
~ $address: { street: string, city: string }
~ $schema: { name: string, home?*: $address }
---
~ John, { Main St, NYC }     # ✓
~ Jane, N                    # ✓ home is null
```

| Input                   | Result                                  |
| ----------------------- | --------------------------------------- |
| valid object            | the object                              |
| field fails its type    | the field's error (e.g. `invalid-type`) |
| `N`, nullable (`*`)     | `null`                                  |
| `N`, not nullable       | `null-not-allowed` error                |
| omitted, optional (`?`) | absent                                  |
| omitted, required       | `value-required` error                  |

## Implementation status (beta)

* Keyed `null:` is not yet honored — use the `*` suffix.

## See Also

* [Objects (value syntax)](/structure-and-syntax/values/object)
* [Open & Dynamic Schemas](/schema-definition-language/advanced-schema-concepts/dynamic-schema) · [Schema References](/definitions/schema-references)
* [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef) · [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)


# Array

The array type — ordered, typed collections of values.

The **`array`** type validates an ordered list of values. Unlike a scalar type, an array is a **container**: besides constraining the list itself (its length), you declare the type of its **elements**. An array with no element type accepts items of any type.

> For the array *value* syntax (`[a, b, c]`), see [Arrays](/structure-and-syntax/values/array). This page covers the array *schema* type.

## Declaring the element type

There are two equivalent ways to declare what an array holds: the **`[ … ]` shorthand** and the keyed **`of:`** form.

```ruby
tags: array                  # any array (elements unconstrained)
tags: []                     # same — any array
tags: [string]               # array of strings (shorthand)
tags: { array, of: string }  # array of strings (keyed form)
```

The element type may be any type, an inline object shape, or a [reference](/definitions/schema-references):

```ruby
people:  [{ name, age, role }]    # array of objects (inline shape)
authors: [$person]                # array of a referenced schema
```

> **The shorthand holds a&#x20;*****type*****, not constraints.** Use `[ … ]` for a bare element type (`[string]`, `[[int]]`, `[{ name: string }]`). To add **list-level** constraints such as `len`, switch to the keyed form: `{ array, of: string, len: 3 }`. Writing constraints inside the brackets — `{ [string], len: 3 }` — is **not valid** and raises a syntax error.

## TypeDef

An `array` MemberDef accepts only the options below. Any other key is invalid.

| Option     | Type             | Description                                                             |
| ---------- | ---------------- | ----------------------------------------------------------------------- |
| `type`     | string           | The type name `array`.                                                  |
| `default`  | array            | Value used when the member is omitted.                                  |
| `of`       | type / MemberDef | The element type. Equivalent to the `[ … ]` shorthand.                  |
| `len`      | int ≥ 0          | Exact number of elements required.                                      |
| `minLen`   | int ≥ 0          | Minimum number of elements.                                             |
| `maxLen`   | int ≥ 0          | Maximum number of elements.                                             |
| `optional` | bool             | If `true`, the member may be omitted. Shorthand: `?` suffix on the key. |
| `null`     | bool             | If `true`, the member may be `null`. Shorthand: `*` suffix on the key.  |

> **`len` precedence.** When `len` is set, `minLen` and `maxLen` are ignored.

## Constraints

### Element type (`of`)

Each element MUST satisfy the element type, or validation fails per item:

```ruby
scores: [int]
---
~ [1, 2, 3]      # ✓
~ [1, two, 3]    # ✗ invalid-type on the second item
```

### len / minLen / maxLen

```ruby
top3: { array, of: string, len: 3 }
---
~ [a, b, c]      # ✓
~ [a, b]         # ✗ invalid-length — must have exactly 3 items
```

```ruby
tags: { array, of: string, minLen: 1 }
---
~ []             # ✗ out-of-range — must have at least 1 item
~ [a]            # ✓
```

### default

Applies only when the member is omitted; must be a valid array.

```ruby
tags?: { array, of: string, default: [] }
```

## Nested and multidimensional arrays

An element type may itself be an array, giving nested or fixed-size multidimensional arrays. Both forms below are valid:

```ruby
# Array of arrays of integers (shorthand)
matrix: [[int]]
---
~ [[1, 2], [3, 4]]
```

```ruby
# Fixed 3×3 integer matrix (keyed form, with len)
grid: { array, of: { array, of: int, len: 3 }, len: 3 }
---
~ [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
```

## Optional, nullable & defaults

```ruby
tags?: [string]    # optional (key may be missing)
tags*: [string]    # nullable (value may be N)
tags?*: [string]   # optional and nullable
```

| Input                                         | Result                   |
| --------------------------------------------- | ------------------------ |
| value present, valid                          | the array                |
| value is `N` (null), key is nullable (`*`)    | `null`                   |
| value is `N` (null), key is **not** nullable  | `null-not-allowed` error |
| value omitted, `default` set                  | the default              |
| value omitted, key optional (`?`), no default | absent                   |
| value omitted, required, no default           | `value-required` error   |

## Implementation status (beta)

* **Keyed `optional:`** works; **keyed `null:`** is not yet honored — use the `*` suffix for nullability.
* The `{ [type], …constraints }` combined form is not supported (use `{ array, of: type, … }`).

## Examples

Adapted from the playground (all forms below parse):

```ruby
anything:    array,                              # any array
strings:     [string],                           # array of strings
min2strings: { array, of: { string, minLen: 2 }, minLen: 2 },
objects:     { array, of: { name, age, role } }
---
[1, two], [aa, bbb], [aaa, bbbb], [{ John Doe, 25, Student }, { Jane Doe, 30, Teacher }]
```

## See Also

* [Arrays (value syntax)](/structure-and-syntax/values/array)
* [Object (SchemaDef)](/schema-definition-language/data-types/object) — for object element shapes
* [Schema References](/definitions/schema-references) — reusable element types
* [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef) · [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)


# Bool

The bool type — true/false values.

The **`bool`** type validates a boolean value. In data it is written compactly as `T`/`F` or verbosely as `true`/`false`.

> For the boolean *value* syntax, see [Booleans](/structure-and-syntax/values/booleans).

## TypeDef

A `bool` MemberDef accepts only the options below. Any other key is invalid.

| Option     | Type   | Description                                                             |
| ---------- | ------ | ----------------------------------------------------------------------- |
| `type`     | string | The type name `bool`. First positional value.                           |
| `default`  | bool   | Value used when the member is omitted. Second positional value.         |
| `optional` | bool   | If `true`, the member may be omitted. Shorthand: `?` suffix on the key. |
| `null`     | bool   | If `true`, the member may be `null`. Shorthand: `*` suffix on the key.  |

## Examples

```ruby
active: bool
---
~ T        # ✓ true
~ false    # ✓
~ yes      # ✗ not-a-bool
```

Optional with a default:

```ruby
verified?: { bool, T }    # optional; defaults to true when omitted
---
~ {}        # ✓ verified resolves to T
~ F         # ✓ verified is F
```

Nullable:

```ruby
flag*: bool               # nullable (value may be N)
---
~ N         # ✓ null
~ T         # ✓
```

## Optional, nullable & defaults

| Input                               | Result                   |
| ----------------------------------- | ------------------------ |
| `T`/`true`/`F`/`false`              | the boolean              |
| any other token                     | `not-a-bool` error       |
| `N`, key nullable (`*`)             | `null`                   |
| `N`, not nullable                   | `null-not-allowed` error |
| omitted, `default` set              | the default              |
| omitted, optional (`?`), no default | absent                   |
| omitted, required                   | `value-required` error   |

> **Use the `*` suffix for nullability.** The keyed `null:` option is part of the TypeDef but is not currently honored — see *Implementation status*.

## Implementation status (beta)

* Keyed `optional:` works; keyed `null:` is not yet honored — use the `*` suffix.

## See Also

* [Booleans (value syntax)](/structure-and-syntax/values/booleans)
* [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef) · [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)


# Advanced Schema Concepts


# TypeDef

TypeDef — the fixed option contract that every MemberDef of a type is validated against.

A **TypeDef** is the fixed option contract for a built-in type. It defines exactly which options a [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef) of that type may use, the order of its positional values, and the type of each option. A TypeDef is itself written in Internet Object object syntax, so the same rules that apply to objects apply to it.

> **TypeDef vs. MemberDef.** A *TypeDef* is the spec-defined contract for a type; a *MemberDef* is what a schema author writes for one member. Every MemberDef is validated against the TypeDef of its declared type.

## Positional and keyed options

A TypeDef fixes the meaning of each positional value. For every type the order is:

1. **type** — the type name (`number`, `int16`, `string`, …)
2. **default** — the value used when the member is omitted
3. **choices** — the allowed set of values

Any further options are written as keyed entries (`min: 0`, `pattern: …`) in any order after the positional ones. So all of these are valid `number` MemberDefs:

```ruby
~ $schema: {
    a: { number, 20 },                        # type + default
    b: { int16, 1, [1, 2, 3] },               # type + default + choices
    c: { number, 50, min: 10, max: 99 },      # default + keyed options
    d: { number, 10, [5, 10, 15], min: 5 }    # default + choices + keyed option
}
---
~                       # all omitted → defaults a 20, b 1, c 50, d 10
~ 25, 3, 60, 15         # a 25, b 3, c 60, d 15
```

A TypeDef written out looks like an ordinary object whose members are the options (most of them optional). Illustratively, the `number` TypeDef is shaped like this:

```ruby
type?       : string,        # a number-family name (see Numeric Types)
default?    : number,
choices?    : [number],
min?        : number,
max?        : number,
multipleOf? : number,
format?     : { string, choices: [decimal, hex, octal, binary, scientific] },
optional?   : { bool, F },
null?       : { bool, F }
```

Each type page lists its own TypeDef in full; that table is the authoritative source of the options a type accepts.

## Validation against the TypeDef

A MemberDef may use **only** the options its type's TypeDef defines. An unknown option is rejected with `unknown-member`:

```ruby
age: { number, minimum: 10 }
---
42                       # ✗ unknown-member — number has no option "minimum" (use min)
```

This is what makes options portable: because the contract is fixed, the same MemberDef validates identically in every conformant implementation.

## TypeDefs are fixed

A schema author cannot change or extend a built-in type's TypeDef — the option set is defined by this specification. To build a reusable, document-local type (a base type plus preset constraints), define a **type reference** in the header instead; see [Schema References](/definitions/schema-references).

## See Also

* [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef) — how authors write a member's type and constraints
* [Schema Data Types](/schema-definition-language/data-types) — every type's TypeDef table
* [Numeric Types](/schema-definition-language/data-types/number) · [String Types](/schema-definition-language/data-types/string) — example TypeDefs


# MemberDef

MemberDef — defining one member's type, constraints, and optional/nullable/default behavior.

A **MemberDef** (member definition) defines a single member of a schema: its type and the constraints on its value. It is an Internet Object object whose first value is the type, optionally followed by a default and choices, then keyed options. Every MemberDef is validated against its type's [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef), so only the options that type defines are allowed.

## Writing a MemberDef

The first value is the type; a default and `choices` may follow positionally; all other options are keyed:

```ruby
~ $schema: {
    age:    { number, min: 0, max: 120 },        # type + keyed options
    level:  { int16, 1, [1, 2, 3] },             # type + default + choices
    name:   { string, pattern: "^[A-Za-z]+$" },  # type + keyed option
    tags:   { array, of: string, minLen: 1 }     # container type + options
}
---
~ 30, 2, John, [a, b]
```

A member can also be just a type (`age: int`) or just a name (`age`, which defaults to `any`). The positions and options each type accepts are listed in its [Schema Data Types](/schema-definition-language/data-types) page.

## Validation against the TypeDef

A MemberDef may use only the options its type defines. An unknown option is rejected with `unknown-member`:

```ruby
age: { number, minimum: 10 }
---
42                       # ✗ unknown-member — number has no option "minimum" (use min)
```

## Optional, nullable, and default

* **Optional** (`?` on the key) — the member may be omitted: `age?: { number, min: 0 }`.
* **Nullable** (`*` on the key) — the value may be `null`: `age*: number`.
* **Both** (`?*`) — `score?*: { number, min: 0 }`.
* **Default** — the second positional value (or keyed `default:`) supplies a value when the member is omitted: `role?: { string, guest }`.

```ruby
~ $schema: { name: string, role?: { string, guest }, nickname?*: string }
---
~ John                       # role → "guest"; nickname omitted
~ Mary, admin, N             # role "admin"; nickname null
```

> **Use the `*` suffix for nullability.** The keyed `null:` option is part of each TypeDef but is not yet honored by the reference implementation — see *Implementation status*.

## MemberDef vs. SchemaDef

Both use `{ … }`, which makes them easy to confuse, but they serve different purposes:

* A **MemberDef** validates one value — it has a *type* and constraints.
* A **SchemaDef** describes an object's *shape* — a map of field names to types or MemberDefs.

The parser tells them apart by the first entry:

1. **Is the first value a known type?** → it is a **MemberDef**.
2. **Otherwise** (the first entry is a `key: …` pair, or a bare field name) → it is a **SchemaDef**.

```ruby
~ $schema: {
    address: { street: string, city: string },   # SchemaDef — a nested object shape
    age:     { int, min: 0, max: 120 }            # MemberDef — a type with constraints
}
---
~ { Main St, NYC }, 30
```

Here `address` declares fields (`street`, `city`), while `age` declares a type with a range.

## Nested shapes

For a nested object, write the shape inline (`{ … }`). The explicit `{ object, schema: { … } }` form is equivalent and documented on [Object (SchemaDef)](/schema-definition-language/data-types/object):

```ruby
~ $schema: { name: string, meta: { author: string, version: { int, min: 1 } } }
---
~ John, { Jane, 2 }
```

## Implementation status (beta)

* Keyed `optional:` works; keyed `null:` is not yet honored — use the `*` suffix for nullability.

## See Also

* [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef) — the allowed options for each type
* [Overview](/schema-definition-language/internet-object-schema) — how schemas are built
* [Object (SchemaDef)](/schema-definition-language/data-types/object) — declaring object shapes
* [Schema References](/definitions/schema-references) — reusing shapes with `$`


# Open & Dynamic Schemas

Open and dynamic schemas — accepting extra fields with the \* marker.

By default a schema is **closed**: a record may contain only the declared fields. Adding a `*` marker makes the schema **open**, allowing extra fields beyond those declared.

## Closed by default

Extra values in a record are rejected unless the schema opts in with `*`:

```ruby
~ $schema: { name: string, age: int }
---
~ John, 30                # ✓
~ Alex, 25, extra1        # ✗ additional-values-not-allowed
```

## Allowing extra fields with `*`

Place `*` after the declared fields to accept extras. Positional extras are keyed by index; keyed extras keep their names:

```ruby
~ $schema: { name: string, age: int, * }
---
~ John, 30                       # ✓
~ Alex, 25, Male, cool           # ✓ extras at index 2 and 3
~ { Mia, 28, role: dev }         # ✓ extra keyed field "role"
```

## Typing the extra fields

`*: <type>` constrains every extra field; `*: { <type>, …constraints }` adds constraints:

```ruby
~ $schema: { name: string, *: string }
---
~ { John, role: dev }     # ✓
~ { Alex, code: 123 }     # ✗ not-a-string — extra value must be a string
```

```ruby
~ $schema: { name: string, *: { string, minLen: 4 } }
---
~ { John, dept: Sales }   # ✓
~ { Mia, id: "12" }       # ✗ invalid-min-length — extra is shorter than 4
```

## Dynamic types with anyOf

When a single field must accept more than one type, use `anyOf` (see [Any](/schema-definition-language/data-types/any)):

```ruby
test: { any, anyOf: [string, number] }
---
~ One     # ✓
~ 1       # ✓
~ Two     # ✓
```

## When to use curly braces

A top-level schema needs braces only when wrapping a nested object. A nested field with more than one member MUST be enclosed in `{ … }`:

```ruby
# 'address' captures only 'street'; city/state become separate fields — usually not intended
name, age, address: street, city, state, isActive
```

```ruby
# 'address' is a nested object with three members
name, age, address: { street, city, state }, isActive
```

## See Also

* [Internet Object Schema](/schema-definition-language/internet-object-schema) · [Any](/schema-definition-language/data-types/any)
* [Object (SchemaDef)](/schema-definition-language/data-types/object)


# Union Types (anyOf)

Members that accept more than one type, via anyOf.

When a field must accept values of **more than one type**, use the `anyOf` constraint on the `any` type (see [Any](/schema-definition-language/data-types/any)). A value is valid if it matches **any one** of the listed alternatives.

```ruby
id: { any, anyOf: [string, int] }
---
~ 42      # ✓ matches int
~ abc     # ✓ matches string
```

A value matching none of the alternatives is rejected:

```ruby
flag: { any, anyOf: [bool, int] }
---
~ T       # ✓
~ hello   # ✗ matches neither
```

## Constrained and structured alternatives

Each alternative may be a full MemberDef (with constraints) or an object shape, not just a bare type name:

```ruby
value: { any, anyOf: [ { int, multipleOf: 5 }, { int, multipleOf: 3 } ] }
---
~ 10      # ✓ multiple of 5
~ 9       # ✓ multiple of 3
```

## Guidance

* Order alternatives from most specific to least specific.
* Prefer `anyOf` over a bare `any` when the set of acceptable types is known — it keeps validation meaningful.

## See Also

* [Any](/schema-definition-language/data-types/any) · [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)
* [Schema References](/definitions/schema-references)


# Composition & Reuse

Composing and reusing schemas through references.

Large schemas are built by **composing** smaller, named pieces. Define a shape once in the header as a `$` reference and reuse it wherever it's needed — across fields, arrays, and other schemas.

## Reuse a shape across fields

```ruby
~ $address: { street, city }
~ $schema: { name: string, home: $address, office?: $address }
---
~ John, { Main St, NYC }, { 5th Ave, NYC }
~ Jane, { Oak Ave, LA }
```

## Compose schemas from other schemas

A reference can be used inside another reference, building larger shapes from smaller ones:

```ruby
~ $address: { street, city }
~ $person: { name: string, address: $address }
~ $schema: { lead: $person, members: [$person] }
---
~ { Ann, { Main St, NYC } }, [{ Bob, { Oak Ave, LA } }, { Cy, { 5th Ave, NYC } }]
```

Here `members` is an array whose element type is the `$person` schema.

## Set the default schema by reference

`$schema` may itself be a reference:

```ruby
~ $address: { street, city }
~ $person: { name: string, home: $address }
~ $schema: $person
---
~ John, { Main St, NYC }
```

## Guidance

* For readability, define a shape **before** you reference it. Order within the header is not significant — references resolve after the whole header is read (see [Schema References](/definitions/schema-references)).
* Reuse keeps documents consistent and small; change a shape once, everywhere updates.

## See Also

* [Schema References](/definitions/schema-references)
* [Object (SchemaDef)](/schema-definition-language/data-types/object) · [Array](/schema-definition-language/data-types/array)


# Overview

Streaming — an incremental, record-oriented transport over the Internet Object data model.

**Streaming** is Internet Object consumed incrementally. A producer frames records onto a byte or text stream, and a consumer reads them back one logical record at a time as the bytes arrive — without waiting for the whole document. It is not a separate format or a second parser: it is the *same* data model, the *same* grammar, and the *same* validation, delivered over time.

> **Streaming is part of the format, not an add-on.** It adds only three things — framing, transport coordination, and an emission envelope around each record. It defines no new type, no new validation rule, and no new serialization behavior. Those all come from the core specification, unchanged.

This chapter is the **language-neutral, normative** contract for Internet Object streaming. It governs every implementation in every language and on every platform. It uses the requirement keywords MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY as defined in RFC 2119 and RFC 8174. A conformant implementation satisfies every MUST and MUST NOT.

## What streaming is

Streaming is an **incremental, record-oriented transport**. A producer frames Internet Object records onto a stream; a consumer reads them back one logical record at a time as bytes arrive.

The governing requirement: streaming MUST behave like a **record protocol**, not a raw chunk parser. Transport chunk boundaries carry no meaning — splitting or coalescing the bytes MUST NOT change the records a consumer sees. What the consumer observes is a sequence of *records*, each either a successfully parsed value or a recoverable error, in wire order.

The protocol defines two roles:

* the **reader** — consumes a stream and emits one item per logical data record;
* the **writer** — frames records onto a stream.

It also places obligations on **adapters** (transport bridges) and **transports**. All of these are abstract roles, not the API of any one library — see [Readers & Writers](/streaming/readers-and-writers).

## Relationship to the core specification

Internet Object **core** is the single authority for data semantics. The streaming protocol is subordinate to it and inclusive of it.

* **Subordinate on semantics.** Streaming MUST NOT redefine, reinterpret, or override any Internet Object semantics — what a type means, how values coerce, whether a value validates, how `default`, optional, `null`, and `choices` resolve, how open and closed schemas behave, how values serialize, or what an error's identity is.
* **Inclusive, not bolt-on.** Streaming MUST reuse core. It MUST NOT fork, shadow, or partially re-implement core parsing, schema resolution, validation, or serialization.

Everything streaming adds is *around* the core result: framing before it, transport beneath it, and an envelope after it.

### The equivalence rule

A single test enforces the relationship above. It is the heart of the protocol:

> For the same record text and the same definitions state, a streamed record MUST produce the **same parsed record value, and the same error identity**, that the non-streaming core path (parse → schema processing → validation) produces for an equivalent one-record document.

If a behavior cannot be derived from "run core over this record's text," it is out of scope for streaming. The protocol references the core type, schema, validation, and serialization rules; it never restates them. When you need those rules, follow the links to the relevant chapter — for example [Validation Model](/conformance/validation-model), [Internet Object Schema](/schema-definition-language/internet-object-schema), and [Error Model](/parsing-and-errors/error-model).

## Terminology

These terms have precise meanings throughout this chapter:

| Term                       | Meaning                                                                                                                |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **Logical record**         | One Internet Object collection record, introduced on the wire by `~`. The unit the reader emits.                       |
| **Header**                 | The definitions block at the start of a stream, before the first `---`.                                                |
| **Section**                | A run of records sharing one schema context, introduced by a `---` control frame.                                      |
| **Control frame**          | A header-definition block or a section marker (`---` / `--- $Schema`). Control frames are never emitted as data items. |
| **Stream item**            | The envelope the reader emits per logical record (see [Stream Items](/streaming/stream-items)).                        |
| **Frame**                  | A contiguous span of stream text the reader buffers and resolves as a unit — one record, or the whole header.          |
| **Default schema context** | The active schema used to validate records that carry no explicit schema selector.                                     |

## How this chapter is organized

| Page                                                | Covers                                                                                       |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| [Wire Format & Framing](/streaming/wire-format)     | The on-the-wire grammar, the mandatory `---` terminator, control frames, and UTF-8 encoding. |
| [Stream Items](/streaming/stream-items)             | The two-kind item model, record indexing, and degenerate inputs.                             |
| [Schema & State](/streaming/schema-and-state)       | Atomic header resolution, preloaded definitions, precedence, and schema switching.           |
| [Streaming Error Model](/streaming/error-model)     | Error categories, recoverable-versus-fatal disposition, and stream-absolute positions.       |
| [Readers & Writers](/streaming/readers-and-writers) | Reader and writer obligations, lifecycle, adapters, backpressure, and conformance.           |

## Versioning

This is **Streaming Protocol v1**. The two-kind item model, the framing rules, and the error model are frozen for v1. Additive, optional metadata MAY be introduced without a version bump; breaking changes require a new major version. The protocol is versioned independently of any implementation, and an implementation declares which protocol version it implements. See the [Version History](/versioning-and-stability/version-history).

## See Also

* [Collection](/collections/collection) — the record sequence streaming transports
* [Data Streaming](/collections/data-streaming) — how collections motivate streaming
* [Validation Model](/conformance/validation-model) — the parse → validate → load pipeline streaming reuses
* [Error Model](/parsing-and-errors/error-model) — the core error identities streaming preserves


# Wire Format & Framing

The on-the-wire framing of a streamed document — the mandatory terminator, control frames, and encoding.

The streaming wire format **is** the existing Internet Object document grammar, consumed incrementally. The markers `~`, `---`, and the header-definition grammar are core constructs; streaming reuses them as its framing layer and does not define them. This page specifies the *framing obligations* of a streamed document — what a producer must put on the wire and what a consumer may rely on — not the grammar itself. For what the markers mean, see [Internet Object Document](/structure-and-syntax/introduction) and [Collection](/collections/collection).

> **Chunk boundaries are not semantic.** Transport may split or coalesce the byte stream however it likes. Splitting or coalescing chunks MUST NOT change the records a reader emits. Framing is determined by the markers below, never by where a packet happens to end.

## Records and the data marker

`~` is the only normative data-record marker. A writer MUST frame every logical data record with `~`, and the reader emits exactly one item per `~`-introduced record. Quoted multiline values remain part of the same logical record — a newline inside a quoted string does not start a new record.

```ruby
~ $schema: { name: string, role: string }
---
~ Alice, admin
~ Bob, guest
```

This stream carries two logical records. Each `~` line is one record; the reader emits one item for each.

## The mandatory header terminator

A stream MAY begin with header definitions, written in the core header-definition grammar. To make the boundary between header and data unambiguous as bytes arrive, the terminator is mandatory:

* A conforming writer **MUST emit an explicit `---` (or `--- $Schema`) before the first data record, even when the header is empty.** An empty header serializes to exactly `---`.
* This terminator is what opens the data section. It makes the first token unambiguous to the reader:
  * a stream beginning with `---` has no header (or an empty one), and its data MAY stream immediately;
  * a stream beginning with `~` is a header (definitions block) that the reader MUST buffer until the terminating `---`.

Only the **first** `---` is load-bearing for header-versus-data separation. Within the data section, records use `~` alone; any later `---` is an ordinary schema switch, not a second header boundary.

The smallest possible conforming stream with data is therefore a bare terminator followed by records:

```ruby
---
~ Alice
~ Bob
```

## Control frames

A **control frame** is structural input that is not a data record: a header-definition block, or a section marker. Control frames are never emitted as data items.

* `--- $Schema` selects the schema context for the records that follow.
* A bare `---` resets the active section to the default schema context.
* A header-definition block (everything before the first `---`) establishes shared stream state.

Because control frames carry state rather than data, the reader applies their effect but emits nothing for them. The detailed rules for schema selection and definition state are in [Schema & State](/streaming/schema-and-state).

## The legacy headerless form

A document that begins directly with `~` data and contains no `---` is the ordinary non-streaming collection form. A reader MAY accept it, so that a non-streaming document stays equivalent under the reader. However:

* This form **cannot be emitted incrementally.** The reader must buffer it to end of stream to determine that it was data and not an unterminated header.
* A writer MUST NOT emit this form. A writer always emits the `---` terminator (the section above), which removes the ambiguity.

In short: readers tolerate the legacy form for compatibility; writers never produce it.

## Encoding

Streaming decodes the wire the same way the core format does, with the additional obligation that decoding state survives chunk boundaries.

* Byte sources MUST be decoded as UTF-8, preserving multibyte decoder state across chunk boundaries so that a code point split across two chunks decodes correctly.
* A leading UTF-8 byte-order mark (`EF BB BF`) at the very start of the stream MUST be stripped. A BOM-like sequence anywhere else is ordinary content and MUST NOT be stripped.
* Newlines MUST be normalized for framing: `\r\n` and a lone `\r` are treated as `\n`. Record framing MUST NOT depend on the producer's newline convention.
* Text sources are already-decoded text and MUST NOT be re-decoded as bytes.
* Newline normalization is a framing concern only. It MUST NOT alter how core interprets bytes inside a quoted value.

For the full character-encoding rules of the format, see [Encoding](/structure-and-syntax/encoding).

## See Also

* [Stream Items](/streaming/stream-items) — what the reader emits for each `~` record
* [Schema & State](/streaming/schema-and-state) — how `---` and `--- $Schema` select schema context
* [Internet Object Document](/structure-and-syntax/introduction) — the header and data structure streaming reuses
* [Encoding](/structure-and-syntax/encoding) — the format's character-encoding rules


# Stream Items

The two-kind stream-item model the reader emits, record indexing rules, and degenerate inputs.

A reader emits a sequence of **stream items**, in wire order. Each item corresponds to exactly one logical data record and is exactly one of two kinds: a **record** (success) or a **record-error** (recoverable failure). This page defines that abstract model. The concrete representation — object shape, field names, a sum type, an iterator of results — is chosen by each platform, but every implementation MUST preserve the model described here.

> **Two kinds, never more.** Implementations MUST NOT introduce additional item kinds in v1. The discriminant names the *protocol event* — record versus record-error — not the type of the payload. New metadata MAY be added only as optional, additive fields that do not change the meaning of the fields below.

## The record item

A **record item** represents one successfully parsed and validated record. It carries:

| Field         | Meaning                                                                                                                         |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `kind`        | `record` (success).                                                                                                             |
| `recordIndex` | The zero-based position of this record in the stream (see below).                                                               |
| `schemaName`  | Present **only** when an explicit schema selector applied; otherwise absent. See [Schema & State](/streaming/schema-and-state). |
| `value`       | The complete parsed record value — identical to what the non-streaming core path produces for that record.                      |

A record item MUST carry a complete record value. It MUST NOT carry an error.

## The record-error item

A **record-error item** represents one record that failed recoverably. Iteration continues to the next record. It carries:

| Field         | Meaning                                                                                                                 |
| ------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `kind`        | `record-error` (recoverable failure).                                                                                   |
| `recordIndex` | The zero-based position of this record — counted exactly as a successful one.                                           |
| `schemaName`  | Same rule as the record item: present only when an explicit selector applied.                                           |
| `error`       | The failure, preserving the core error identity (category + code). See [Streaming Error Model](/streaming/error-model). |

A record-error item MUST carry no value. An item MUST NOT carry both a value and a recoverable error — the two kinds are mutually exclusive.

## Record indexing

`recordIndex` is the stable identity of a record's position in the stream. Its rules are strict:

* It is **zero-based** and counts **logical data records** — not chunks, lines, or sections.
* It MUST increment for **both** kinds. A failed record consumes an index exactly as a successful one does, so indices are **dense and gap-free**.
* It is **stream-global**. It MUST NOT reset on a schema switch or on a bare `---`.
* Items MUST be emitted **in wire order**: the item with index *n* before the item with index *n+1*.

Consider a stream whose second record is malformed:

```
---
~ { id: 1 }
~ { BROKEN
~ { id: 2 }
```

The reader emits three items in order: a `record` at index `0`, a `record-error` at index `1`, and a `record` at index `2`. The bad record still consumes index `1`; the third record is `2`, not `1`. The reader MUST NOT leak partial fragments of the broken record before its error item.

## Degenerate and empty inputs

The reader's behavior on trivial inputs follows directly from the framing rules and the equivalence rule — these cases are specified so every implementation agrees:

* An **empty source** (zero bytes) MUST emit zero items and complete normally.
* A **whitespace-only or blank-line-only** source MUST emit zero items and complete normally.
* A **header-only stream** (definitions, then `---`, then end of stream with no records) MUST emit zero items and complete normally.
* A **trailing bare `---`** with no following records MUST NOT emit an item and MUST NOT error.
* A **bare `~`** with no payload (or `~` followed only by whitespace) is delegated to core like any other record. Under an active schema it yields whatever core produces — typically a validation failure, hence one `record-error` — and with no active schema it yields core's empty record. Streaming MUST NOT special-case it into a stream-only error, because that would diverge from the non-streaming parse of the same input.

## See Also

* [Schema & State](/streaming/schema-and-state) — when `schemaName` is present and how selectors work
* [Streaming Error Model](/streaming/error-model) — what a record-error item's `error` preserves
* [Wire Format & Framing](/streaming/wire-format) — how `~` records and `---` frames are read off the wire
* [Collection](/collections/collection) — the record sequence each item maps to


# Schema & State

How a stream resolves definitions atomically, selects schemas, and applies precedence with preloaded state.

A stream carries state: header definitions, a default schema context, and the explicit schema in effect for the current section. This page specifies how that state is established, how it is selected per record, and how it composes with definitions supplied before the stream begins. All of the *semantics* — what a definition means, how a schema resolves, how `default`, optional, `null`, and `choices` behave — belong to core; streaming only governs *when* state is resolved and *which* schema applies. See [Definitions](/definitions/definitions) and [Schema References](/definitions/schema-references).

## The header is resolved atomically

The header is the definitions block before the first `---`. Definition references inside it are **position-independent**: a definition at any position may reference another regardless of order. Because of this:

> **The header MUST be buffered and resolved as a single atomic frame before any data record is processed.** The header MUST NOT be resolved piecemeal. A reader buffers from the start of the stream up to the terminating `---`, resolves the whole block at once, and only then begins emitting records.

Definitions are **header-only** in v1. After the first logical data record begins, the header phase is over. There is no normative midstream definition-mutation syntax — a stream cannot add or change definitions once data has started.

## Selecting a schema per record

A record is validated under exactly one schema context:

* `--- $Name` selects a named schema for the records that follow it.
* A bare `---` resets the active section to the **default schema context**.
* A record that carries no explicit selector is validated through the active default schema context.

The `schemaName` reported on a stream item reflects the **explicit** selector declared for that record's section, and when present MUST include the leading `$` sigil:

* If an explicit selector applied (for example `$User`), `schemaName` is that name.
* If the record was validated only through the active default schema context with no explicit selector, `schemaName` MUST be **absent**. An implementation MUST NOT synthesize a name (such as `$schema`) merely because a default schema was active.

A schema switch changes the parsing and serialization context; it does not by itself produce a stream item.

```ruby
~ $User: { name: string }
~ $Order: { id: int }
--- $User
~ Alice
--- $Order
~ 1001
```

Here the reader emits two record items: the first reports `schemaName` `$User`, the second reports `$Order`. The two `---` control frames are applied but never emitted as items.

## Unknown schema switches are fatal

A `--- $Name` selector MUST reference an already-defined schema. A switch to an unknown or invalid schema is **not** a recoverable per-record error — it is a **fatal** stream error, because the section's records can no longer be validated against a known shape. The error preserves its core identity (the `schema-not-defined` validation error); see [Streaming Error Model](/streaming/error-model).

If no default schema exists, a bare `---` selects the schemaless default context rather than inventing a stream-only schema.

## Preloaded definitions and precedence

A reader MAY be constructed with preloaded definitions and an optional fallback default schema context **before any stream bytes are read**. This is how out-of-band schemas — agreed once between a publisher and a subscriber — are supplied to the reader. If none are provided, the initial definitions state is empty.

Precedence MUST match core's external-definitions behavior:

* In-stream header definitions **override** matching preloaded keys.
* An in-stream `$schema` **overrides** the fallback default schema context.
* If the stream defines no `$schema`, the fallback default schema context **remains active**.

This is what makes the shared, out-of-band deployment mode work: the wire can carry only data sections (or a header with metadata but no schema), while the reader validates against the schema it already holds. For the two deployment modes — embedded versus shared — see [Schema-First Design](/core-concepts/schema-first).

## State reuse

Header-defined definitions become shared stream state and apply to later records. A reader MUST reuse accepted definitions and resolved schema objects across records: an unchanged schema context MUST NOT trigger repeated schema compilation. All definition lookup, schema resolution, default handling, and member validation MUST be delegated to core — a reader MUST NOT embed its own copy of the rules for `default`, optional, `null`, `choices`, or open-schema handling.

## See Also

* [Definitions](/definitions/definitions) — the header definition block streaming resolves atomically
* [Schema References](/definitions/schema-references) — how `$name` references resolve
* [Schema-First Design](/core-concepts/schema-first) — embedded versus shared (out-of-band) schemas
* [Streaming Error Model](/streaming/error-model) — why an unknown schema switch is fatal


# Streaming Error Model

The streaming error model — error categories, recoverable-versus-fatal disposition, and stream-absolute positions.

Streaming preserves the core error identity of everything semantic and defines its own errors only for the transport and lifecycle it owns. This page specifies the **categories** an error can have, the **disposition** that decides whether iteration continues or stops, and the positioning rules that make a streamed error indistinguishable from the non-streaming one. The core errors themselves are defined in [Error Model](/parsing-and-errors/error-model); streaming references them and does not restate them.

> **Match on category and code, never on message.** Every error carries a stable category and a stable string code. Message text is non-contractual and MAY be localized. Tooling MUST branch on the category and code, not on the human-readable message.

## Error categories

Every error carries a **category** and a stable string **code**. The category is one of four values, and it MUST be derived from the originating error's **class**, not from any code-grouping (a code may sit in one core grouping yet be raised as a different class — the class is authoritative):

| Category     | Raised by                                                       |
| ------------ | --------------------------------------------------------------- |
| `syntax`     | A tokenization or parsing failure.                              |
| `validation` | A schema validation failure.                                    |
| `general`    | Any other core error.                                           |
| `stream`     | A transport or lifecycle failure raised by the streaming layer. |

The `syntax`, `validation`, and `general` categories and their codes are defined by Internet Object **core**; streaming preserves them unchanged. The `stream` category and its codes are defined here, because transport and lifecycle are streaming's own domain.

## Disposition: recoverable versus fatal

Disposition — what happens to iteration — is distinct from category — what the error is. The same category can be recoverable in one place and fatal in another.

### Recoverable record errors

A recoverable error is localized to one logical record. The default and normative behavior is to emit one **record-error item** and continue to the next record. These carry a core category (`syntax`, `validation`, or `general`). The rules:

* **Parse failures are boundary-based.** When parsing fails for one record and recovery advances to the next `~`, the next `---`, or end of stream, that record produces **one** record-error item, using the primary parse error for that boundary.
* **Validation may find several problems, but the item carries one.** Validation runs after a successful parse and MAY collect multiple errors for one record. The reader MUST still emit exactly **one** record-error item for that record; its public `error` is the **first** collected validation error in v1.
* **Core identity is preserved.** The emitted error MUST carry the same category (derived from the core error class) and the same code that the non-streaming path produces. Streaming MUST NOT remap codes, collapse the category distinction, or invent a stream-local taxonomy for core errors.
* **Truncated input is a syntax error.** If the source closes cleanly while a logical record is incomplete — a `~` frame began but end of stream arrived before the record could be fully parsed — the reader MUST emit one record-error item for the incomplete record, using the core parse error for truncated input (category `syntax`).
* **No partial fragments.** The reader MUST NOT emit partial record fragments before or instead of an error.
* **Warnings are not errors.** Non-fatal core warnings MUST NOT be promoted to record-error items in v1.

### Fatal stream errors

A fatal error terminates iteration. The conditions are invalid control state (an invalid control frame, invalid header definitions, or an unknown schema switch), a source or transport failure, cancellation, or a buffer-limit overflow. A fatal error:

* MUST terminate iteration. The platform signals this in its own idiom — an exception, a rejected promise, an error result — but iteration does not continue.
* MUST NOT be emitted as a record-error item.
* MAY carry a **core** category (for example, an unknown schema switch is fatal but preserves the core `validation` / `schema-not-defined` identity) **or** the `stream` category.

## Streaming fatal codes

The streaming layer defines exactly these fatal codes in v1, all category `stream`:

| Code                     | Raised when                                                                                                                                                                                        |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `stream-buffer-exceeded` | A single pending frame (one record, or the header) exceeds the implementation's buffer limit. The limit bounds one frame; crossing it means a correct record boundary can no longer be guaranteed. |
| `stream-source-error`    | The underlying source or transport fails or errors.                                                                                                                                                |
| `stream-aborted`         | Iteration is cancelled cooperatively (for example, via an abort signal).                                                                                                                           |

These are the only `stream`-category codes in v1. Every other fatal error preserves a **core** category and code:

* an unknown schema switch → `validation` / `schema-not-defined`;
* invalid header definitions → `syntax`;
* a partial frame at end of stream → `syntax`.

This preserves the governing principle: streaming defines codes only for its own transport and lifecycle domain; everything semantic stays core's.

## Stream-absolute positions

Error positions — row, column, and offset — MUST be **stream-absolute**: measured from the start of the stream, identical to what the non-streaming parser reports for the equivalent whole document. The reader rebases each frame's local positions onto a running stream base across chunk and frame boundaries. It MUST NOT report record-relative positions.

This is a direct consequence of the [equivalence rule](/streaming/streaming#the-equivalence-rule): an error from a streamed record must be indistinguishable — in category, code, and position — from the same error produced by parsing the whole document at once.

## See Also

* [Error Model](/parsing-and-errors/error-model) — the core error categories and codes streaming preserves
* [Error Accumulation](/parsing-and-errors/error-accumulation) — why validation can collect several errors per record
* [Stream Items](/streaming/stream-items) — how a recoverable error becomes a record-error item
* [Schema & State](/streaming/schema-and-state) — why an unknown schema switch is fatal, not recoverable


# Readers & Writers

Obligations of the reader and writer roles, plus adapters, transports, backpressure, and conformance.

The protocol defines two roles — the **writer** that frames records onto a stream, and the **reader** that consumes a stream and emits one item per record — together with the obligations of the **adapters** and **transports** that connect them. These are abstract roles. A concrete implementation may expose them under any names and any platform idioms, but it MUST honor the duties below.

## Writer obligations

A writer is responsible for canonical framing. It MUST:

* **Serialize through core.** A writer MUST serialize record values using core's serializer. It MUST NOT introduce stream-only formatting for strings, defaults, nulls, arrays, or objects.
* **Emit the header at most once.** The header (if any) is emitted before the first data record, and the writer MUST emit the `---` terminator per [Wire Format & Framing](/streaming/wire-format) — even for an empty header.
* **Switch schemas only when needed.** A writer SHOULD emit a schema switch only when the effective schema actually changes.
* **Never emit invalid control frames.** A writer MUST NOT emit midstream definition-mutation control frames in v1, nor unresolved or undefined schema switches.
* **Never emit the legacy headerless form.** A writer always emits the `---` terminator, so it never produces the buffer-to-end-of-stream legacy form described in [Wire Format & Framing](/streaming/wire-format).
* **Write sequentially.** Writer calls MUST be issued sequentially; the protocol does not define concurrent-write framing in v1.

### Raw forwarding

A writer MAY forward pre-framed Internet Object text verbatim — a "raw forward" capability. When it does, the caller is responsible for correct framing, and the writer's automatic schema-switch tracking is no longer reliable for subsequent structured writes. After a raw forward, the next structured write MUST carry an explicit schema selector if the active schema may have changed.

## Reader obligations and lifecycle

A reader consumes the stream incrementally and emits items in wire order. It MUST:

* **Process incrementally.** A reader MUST process records incrementally and MUST NOT re-parse or re-materialize already-emitted records.
* **Stay bounded in memory.** A reader's memory growth MUST be bounded by pending undecoded bytes, the current incomplete frame (one record, or the header), and minimal lookahead — not by total stream history.
* **Reuse compiled state.** A reader MUST reuse accepted definitions and resolved schema objects across records; an unchanged schema context MUST NOT trigger repeated schema compilation.
* **Be single-consumption.** A reader is single-consumption in v1. Consuming it more than once has undefined behavior.
* **Be lazy.** A reader MUST advance the source only as the consumer requests the next item. This laziness is what provides read-side backpressure for pull-based sources.
* **Release on early termination.** When the consumer stops early, the reader MUST release the underlying source (for example, release a stream lock) and discard buffered-but-unemitted bytes.
* **Support cancellation.** A reader SHOULD support cooperative cancellation. When cancelled, iteration terminates fatally and the source is released; cancellation MUST NOT emit a record-error item.

## Adapters and transports

An **adapter** is a transport bridge — it moves bytes between a transport and the reader or writer. A **transport** is the underlying channel. Their obligations keep the record protocol intact end to end:

* Adapters are transport bridges **only**. They MUST NOT bypass, replace, or fork core parsing, validation, or serialization.
* Adapters MUST preserve record order and MUST NOT invent, merge, or discard logical records.
* Adapters that consume bytes MUST preserve correctness across chunk boundaries (see the encoding rules in [Wire Format & Framing](/streaming/wire-format)).
* Adapters MUST NOT downgrade a fatal control-state error into a record-error item.
* A flow-controlled transport's write operation SHOULD resolve only after the transport has accepted the frame per that transport's backpressure model, and a writer MUST honor that backpressure.

## Performance and backpressure

* A large same-schema stream MUST keep parsing incrementally — without recompiling the schema per record and without retaining emitted records.
* **Read-side backpressure** is provided naturally by pull-based sources: a lazy reader only advances the source when the consumer asks for the next item.
* **Push-based sources** may lack producer backpressure. An implementation that offers a push source MUST document this, and producers MUST apply their own flow control.

## Conformance

An implementation is **conformant** if it satisfies every MUST and MUST NOT in this chapter and passes the shared, language-neutral conformance corpus that accompanies the protocol.

Implementations SHOULD also verify the [equivalence rule](/streaming/streaming#the-equivalence-rule) directly against their own core: streamed output MUST equal non-streamed core output for the same input and definitions, **including across arbitrary chunk boundaries**. The strongest form of this test feeds the same input split every possible way — whole, per line, per byte, and at random boundaries including mid-multibyte and mid-marker — and asserts that every split produces identical items. This single property catches the overwhelming majority of streaming defects, because it forces the framing layer to be invisible to the result.

## See Also

* [Wire Format & Framing](/streaming/wire-format) — the framing the writer produces and the reader consumes
* [Stream Items](/streaming/stream-items) — the items a reader emits
* [Streaming Error Model](/streaming/error-model) — recoverable versus fatal disposition for the reader
* [Conformance Requirements](/conformance/requirements) — the format's broader conformance duties


# Overview

Overview of the parsing pipeline and the two error classes.

Turning Internet Object text into validated data happens in stages:

1. **Tokenize** — split the text into tokens (values, separators, structural characters).
2. **Parse** — assemble tokens into a document tree (header, sections, records, values).
3. **Validate** — check the data against the schema.
4. **Load** — produce the final in-memory values.

Errors fall into **two classes**, matching the stages that produce them:

| Class                | Stage            | Example                                            |
| -------------------- | ---------------- | -------------------------------------------------- |
| **Syntax error**     | tokenize / parse | unbalanced `{`, missing comma, unterminated string |
| **Validation error** | validate         | wrong type, out of range, missing required field   |

The distinction matters because the two classes recover differently:

* **Syntax errors** are bounded by **structure** — the parser skips to the next boundary (`~` or `---`) and continues.
* **Validation errors** are bounded by the **object** — each record is validated on its own and may report zero, one, or many errors, without affecting other records.

## In this section

* [Error Model](/parsing-and-errors/error-model) — the two classes, categories, and error conditions
* [Parser Behavior & Recovery](/parsing-and-errors/parser-behavior) — how recovery works; processing options
* [Error Accumulation](/parsing-and-errors/error-accumulation) — collecting many errors and partial output

## See Also

* [Syntax Errors](/structure-and-syntax/syntax-errors)
* [Collection Rules](/collections/collection-rules)


# Error Model

Syntax vs validation errors; categories, codes, and conditions.

Internet Object defines two classes of error. Each reported error carries a stable **error code** (a hyphenated identifier), a human-readable message, and the **position** in the source where it occurred.

## Syntax errors

Raised while tokenizing or parsing, before any schema is applied. They describe malformed *text*. Representative codes:

| Code                           | Condition                                                          |
| ------------------------------ | ------------------------------------------------------------------ |
| `expecting-bracket`            | a `{`, `}`, `[`, or `]` is missing                                 |
| `unexpected-token`             | a token appears where the grammar does not allow it                |
| `unexpected-positional-member` | a positional value follows a keyed one in an object                |
| `unknown-member`               | a MemberDef uses an option the declared type does not define       |
| `string-not-closed`            | a quoted string has no closing quote                               |
| `value-required`               | a value was expected (e.g. a key with no value) but none was found |
| `invalid-datetime`             | a date/time literal is malformed                                   |

> A conformant parser SHOULD report a malformed **numeric** literal (such as `0o89` or `0xGH`) as a coded syntax error. The reference implementation currently raises an uncoded internal error for some of these — a known gap.

## Validation errors

Raised while validating data against a schema. They describe values that do not satisfy the schema. Representative codes:

| Code                                  | Condition                                           |
| ------------------------------------- | --------------------------------------------------- |
| `invalid-type`                        | value is not of the declared type                   |
| `not-a-bool` / `not-a-string`         | value is not a boolean / string for that field      |
| `invalid-range`                       | number/date outside `min`/`max`                     |
| `out-of-range` / `invalid-length`     | array/string length outside `minLen`/`maxLen`/`len` |
| `invalid-min-length`                  | string/array shorter than the minimum               |
| `invalid-choice`                      | value is not one of `choices`                       |
| `invalid-pattern`                     | string does not match `pattern`                     |
| `invalid-email` / `invalid-url`       | malformed email/URL                                 |
| `invalid-scale` / `invalid-precision` | decimal scale/precision violated                    |
| `value-required`                      | a required field is missing                         |
| `null-not-allowed`                    | `null` given for a non-nullable field               |
| `additional-values-not-allowed`       | extra values without an open (`*`) schema           |

> Error **codes are stable**; messages and exact positions may vary between implementation versions. Tooling should branch on the code, not the message.

## Definition errors

A third small group arises from header references: `schema-not-defined`, `variable-not-defined` (see [Error Handling in Definitions](/definitions/error-handling)).

## See Also

* [Parser Behavior & Recovery](/parsing-and-errors/parser-behavior) · [Error Accumulation](/parsing-and-errors/error-accumulation)
* [Conformance Requirements](/conformance/requirements)


# Parser Behavior & Recovery

Boundary-bounded syntax-error recovery and processing options.

A conformant processor SHOULD recover from errors and continue, so that a single bad record does not discard the rest of a document.

## Syntax-error recovery is bounded by structure

On a syntax error, the parser **skips tokens until the next boundary** and resumes there. The boundaries are:

* the record separator `~` (start of the next collection item), and
* the section separator `---` (start of the next section),
* or end of input.

```ruby
~ $schema: { name: string, age: int }
---
~ John, 28              # parsed
~ Bad, { unclosed      # syntax error here; parser skips to the next ~
~ Bob, 35              # parsed — recovery resumed at this record
```

The malformed middle record is reported as an error; the records before and after it are still parsed.

## Validation recovery is bounded by the object

Each record is validated independently. A validation error in one record does not stop validation of the others (see [Collection Rules](/collections/collection-rules)).

## Processing options

A processor typically offers options that control recovery and output. Common ones:

* **continue-on-error** — collect errors and keep going (recommended), versus failing on the first error.
* **skip-errors** — omit error entries from the loaded result, returning only the records that succeeded.

> Option names and exact semantics are implementation-defined; this section describes the behaviors a conformant processor is expected to provide.

## Error nodes

When continuing past an error, a processor marks the failed record with an error placeholder in the result so consumers can tell which records succeeded and which did not. See [Error Accumulation](/parsing-and-errors/error-accumulation).

## See Also

* [Error Model](/parsing-and-errors/error-model) · [Error Accumulation](/parsing-and-errors/error-accumulation)
* [Syntax Errors](/structure-and-syntax/syntax-errors)


# Error Accumulation

Accumulating per-object validation errors and per-region syntax errors.

Rather than stopping at the first problem, a conformant processor **accumulates** errors and returns them together, alongside whatever data parsed successfully. This gives authors a full picture in one pass.

## Per-object validation errors

Each record is validated independently and may contribute **zero, one, or many** errors. A failing record is marked as an error; the others are unaffected:

```ruby
~ $schema: { name: string, age: { int, max: 25 } }
---
~ James, 20    # ✓
~ Alex, 30     # ✗ invalid-range
~ Bob, 22      # ✓
```

The result contains two valid records and one error entry — not a single fatal failure.

## Per-region syntax errors

Syntax errors are accumulated per recovered region (between boundaries). One unparsable record yields one error, and parsing resumes at the next `~` or `---`.

## Partial output

Because errors are accumulated rather than thrown, the loaded result includes the records that succeeded. Consumers can render valid data and surface the error list side by side (for example, editor markers at each error's position).

## Duplicate section names

When two sections share a name, the duplicate is **automatically renamed** (`users` → `users_2` → `users_3`), so the rest of the document still loads.

## See Also

* [Error Model](/parsing-and-errors/error-model) · [Parser Behavior & Recovery](/parsing-and-errors/parser-behavior)
* [Collection Rules](/collections/collection-rules)


# Validation Model

The parse, validate, load, and stringify pipeline.

Processing an Internet Object document is defined as a pipeline of four stages. Each stage has a clear input and output, so implementations behave consistently.

```
text ──parse──▶ document tree ──validate──▶ checked tree ──load──▶ values
                                                              ◀─stringify── values
```

## Parse

**Input:** UTF-8 text. **Output:** a document tree (header, sections, records, values).

Parsing checks only *syntax* — that the text is well-formed. It does not consult any schema. Syntax errors are produced here (see [Error Model](/parsing-and-errors/error-model)).

## Validate

**Input:** the document tree + a schema. **Output:** the same tree, with each value checked.

Validation applies the schema: types, constraints (`min`, `maxLen`, `pattern`, `choices`, …), optionality, and nullability. Validation errors are produced here. With no schema, data is accepted structurally and mapped to positional keys.

## Load

**Input:** the validated tree. **Output:** in-memory values.

Loading converts checked values into their final representations (numbers, booleans, dates, byte data, nested objects/arrays), applying defaults for omitted fields.

## Stringify

The inverse of the pipeline: in-memory values are serialized back to Internet Object text, honoring schema hints such as a number's `format` or a string's quote style. A value that is loaded and then stringified SHOULD round-trip to an equivalent document.

## See Also

* [Conformance Requirements](/conformance/requirements)
* [Parsing & Errors](/parsing-and-errors/parsing-and-errors)


# Conformance Requirements

MUST/SHOULD/MAY duties of parsers, validators, and serializers.

The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, and **MAY** are used as in RFC 2119. This section states the duties of a conformant implementation. Internet Object is **language-independent**; these requirements describe behavior, not any particular API.

## All implementations

* MUST accept input encoded as UTF-8.
* MUST treat the format as case-sensitive (keys, keywords, type names).
* MUST recognize the structural characters and keywords exactly as defined.

## A conformant parser

* MUST build a document tree according to the [grammar](/appendices/grammar).
* MUST report syntax errors with a stable error **code** and a source **position**.
* SHOULD recover from a syntax error by skipping to the next boundary (`~` or `---`) and continuing, rather than aborting the whole document.

## A conformant validator

* MUST validate data against the schema: types, constraints, optionality, nullability.
* MUST recognize the closed set of built-in types and their allowed options (each type's [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef)).
* MUST reject a value that violates its type or constraints, with the appropriate error code.
* MUST validate each record independently; one invalid record MUST NOT invalidate others.
* MUST NOT invent new built-in type names; document-local types are declared with `$` references.

## A conformant serializer

* MUST produce output that re-parses to equivalent data (round-trip).
* SHOULD honor schema serialization hints (e.g. number `format`, string quote style).

## Versioning

* The **specification** carries its own version (currently `1.0 Draft`).
* **Implementations** carry their own versions independently and SHOULD declare which specification version they conform to (e.g. "implements Internet Object 1.0").

## Reference implementation

The official TypeScript/JavaScript implementation, [`internet-object`](https://github.com/maniartech/InternetObject-js), serves as a reference implementation. Where this specification and an implementation disagree during the draft period, the discrepancy is tracked and resolved case by case; the specification is the intended source of truth as it stabilizes.

## See Also

* [Validation Model](/conformance/validation-model) · [Formal Grammar](/appendices/grammar)


# Versioning Policy

How the Internet Object specification is versioned, and the stability tiers that govern each feature.

This page defines how the **Internet Object specification** is versioned and how the stability of each feature is governed, so the specification can be published and evolve continuously without being a perpetual draft. It follows the model used by mature standards (CSS module levels, TC39 stages, Kubernetes alpha/beta/GA): a written policy plus a per-page status index, with stability tracked per page rather than as one global label. For where each page currently stands, see [Feature Status](/versioning-and-stability/feature-status).

## What is versioned

* **The format specification** carries a major version (for example, "Internet Object 1.0"). A backward-incompatible change to the format requires a new major version (2.0).
* **Sub-protocols** built on the format — for example [Streaming](/streaming/streaming) — carry their own version (the Streaming Protocol is at v1) and advance on their own clock.
* **Implementations** are not versioned by this document. They follow their own Semantic Versioning and declare which specification version they implement. The specification's clock and an implementation's release clock are independent.

## Stability tiers

Every feature carries exactly one tier. Tiers are tracked per feature, not per specification release.

| Tier        | Meaning                                                                                            | May change                              |
| ----------- | -------------------------------------------------------------------------------------------------- | --------------------------------------- |
| Stable      | Part of the frozen specification contract.                                                         | Only in a new specification major       |
| Candidate   | Feature-complete and under review; intended to become Stable. Not yet part of the frozen contract. | With notice, before it graduates        |
| Draft       | Provisional; still evolving. Use at your own risk.                                                 | At any time                             |
| Deprecated  | Still specified; scheduled for removal.                                                            | Removed in the next specification major |
| Reserved    | Syntax or semantics reserved for future definition; not yet specified.                             | May be defined at any time              |
| Informative | A non-normative page (guides, rationale, appendices); carries no maturity guarantee.               | n/a                                     |

As analogues: Draft is close to TC39 Stage 1–2 or Kubernetes alpha; Candidate to Stage 3 (candidate) or beta; Stable to Stage 4 or GA.

## Core rules

* A **Stable** feature MUST NOT change incompatibly except in a new specification major.
* A **Candidate** feature is feature-complete and SHOULD be treated as near-final, but MAY still change — with a changelog notice — before graduating to Stable.
* A **Draft** feature MAY change or be removed at any time and MUST be clearly marked.
* A feature MUST be **Deprecated** for at least one major cycle before removal.
* Each page declares its tier in a `status:` field in its front matter; the [Feature Status](/versioning-and-stability/feature-status) page is generated from those fields, so the dashboard never drifts from the pages. To change a status, edit the page's `status:` and regenerate.

## Graduation and deprecation lifecycle

```
(proposed) → Draft → Candidate → Stable → Deprecated → Removed
                │         │
                └─────────┴── may still change while Draft or Candidate
```

* **Draft → Candidate:** the design is complete and reviewed.
* **Candidate → Stable:** behavior is final and consistent across implementations; graduation is announced in the changelog.
* **Stable → Deprecated → Removed:** with a replacement and a target major.

## Relationship to implementations

* An implementation MAY implement Candidate or Draft features but SHOULD mark them as such in its own API (for example, SemVer-exempt or `@experimental`).
* A feature is normally promoted to **Stable** only once it is interoperably implemented and verified — for example, by a shared conformance suite. Until then it stays Candidate.
* Implementations declare the specification version and which Candidate or Draft features they include.

## 1.0 readiness checklist

Declare a specification major (such as 1.0) final when:

* [ ] The Stable surface (structure, core values and types, collections, core schema and validation) is one the specification will commit to keeping until 2.0.
* [ ] Each Stable feature is interoperably implemented and verified.
* [ ] All not-yet-final features are explicitly marked Draft or Candidate, not silently shipped as Stable.
* [ ] [Feature Status](/versioning-and-stability/feature-status) is complete and reviewed.
* [ ] The deprecation and breaking-change process here is adopted.

## Changelog

Specification changes — especially anything affecting a Stable feature, and every feature graduation — MUST be recorded in the [Version History](/versioning-and-stability/version-history). Day-to-day finalization work is tracked in the [Roadmap](/appendices/roadmap).

## See Also

* [Feature Status](/versioning-and-stability/feature-status) — the current tier of each feature
* [Roadmap](/appendices/roadmap) — the finalization plan
* [Version History](/versioning-and-stability/version-history) — the specification changelog
* [Conformance Requirements](/conformance/requirements) — what a conformant implementation must do


# Version History

How specification changes are recorded; the change log begins at the first 1.0 release.

This page records notable changes to the **specification**, newest first, once versioned releases begin. Implementations (such as `io-js2`) version independently under their own SemVer and declare which specification version they conform to.

## 1.0 Draft — in progress

The specification is in its **1.0 Draft**: it is still being authored, most features are at **Candidate** maturity, and it may change without a version bump until 1.0 is finalized.

There are **no released versions yet** — the first entry here will be **1.0**, recorded when the draft is finalized. Until then:

* for the current maturity of each feature, see [Feature Status](/versioning-and-stability/feature-status);
* for how versions and stability work, see the [Versioning Policy](/versioning-and-stability/versioning);
* for planned direction, see the [Roadmap](/appendices/roadmap).

## See Also

* [Versioning Policy](/versioning-and-stability/versioning) · [Feature Status](/versioning-and-stability/feature-status) · [Roadmap](/appendices/roadmap)


# Feature Status

Generated overview of specification features and their maturity status.

> **Generated file — do not edit by hand.** Produced by `tools/status-report.ts` from each page's `status:` front matter. To change a page's status, edit that page's `status:` field and regenerate with `npm run status:write`.

**Maturity levels:** `Stable` (frozen contract) · `Candidate` (feature-complete, under review) · `Draft` (still evolving) · `Deprecated` · `Reserved`. Defined in the [Versioning Policy](/versioning-and-stability/versioning). Non-normative pages are marked `Informative` and are not graded here.

**Totals:** 72 Candidate.

## Structure and Syntax

| Page                                                                                                             | Status    |
| ---------------------------------------------------------------------------------------------------------------- | --------- |
| [Internet Object Document](/structure-and-syntax/introduction)                                                   | Candidate |
| [Header](/structure-and-syntax/introduction/header)                                                              | Candidate |
| [Data Sections](/structure-and-syntax/introduction/data)                                                         | Candidate |
| [Structural Elements](/structure-and-syntax/structural-elements)                                                 | Candidate |
| [Structural Characters & Separators](/structure-and-syntax/structural-elements/structural-characters-n-keywords) | Candidate |
| [Literals](/structure-and-syntax/structural-elements/literals)                                                   | Candidate |
| [Other Special Characters](/structure-and-syntax/structural-elements/other-special-characters)                   | Candidate |
| [Whitespace & Indentation](/structure-and-syntax/structural-elements/whitespaces)                                | Candidate |
| [Value Representations](/structure-and-syntax/values)                                                            | Candidate |
| [Objects](/structure-and-syntax/values/object)                                                                   | Candidate |
| [Arrays](/structure-and-syntax/values/array)                                                                     | Candidate |
| [Strings](/structure-and-syntax/values/string)                                                                   | Candidate |
| [Open Strings](/structure-and-syntax/values/string/open-strings)                                                 | Candidate |
| [Regular Strings](/structure-and-syntax/values/string/regular-strings)                                           | Candidate |
| [Raw Strings](/structure-and-syntax/values/string/raw-strings)                                                   | Candidate |
| [Numeric Values](/structure-and-syntax/values/number)                                                            | Candidate |
| [Number](/structure-and-syntax/values/number/number)                                                             | Candidate |
| [BigInt](/structure-and-syntax/values/number/bigint)                                                             | Candidate |
| [Decimal](/structure-and-syntax/values/number/decimal)                                                           | Candidate |
| [Special Numeric Formats](/structure-and-syntax/values/number/special-formats)                                   | Candidate |
| [NaN and Infinity](/structure-and-syntax/values/number/nan-and-infinity)                                         | Candidate |
| [Binary](/structure-and-syntax/values/binary)                                                                    | Candidate |
| [Date and Time](/structure-and-syntax/values/date-and-time)                                                      | Candidate |
| [Booleans](/structure-and-syntax/values/booleans)                                                                | Candidate |
| [Nulls](/structure-and-syntax/values/null)                                                                       | Candidate |
| [Case Sensitivity Rules](/structure-and-syntax/case-sensitivity)                                                 | Candidate |
| [Comments](/structure-and-syntax/comments)                                                                       | Candidate |
| [Encoding](/structure-and-syntax/encoding)                                                                       | Candidate |
| [Syntax Errors](/structure-and-syntax/syntax-errors)                                                             | Candidate |

## Definitions

| Page                                                         | Status    |
| ------------------------------------------------------------ | --------- |
| [Definitions](/definitions/definitions)                      | Candidate |
| [Variables](/definitions/variables)                          | Candidate |
| [Schema References](/definitions/schema-references)          | Candidate |
| [Error Handling in Definitions](/definitions/error-handling) | Candidate |

## Collections

| Page                                                     | Status    |
| -------------------------------------------------------- | --------- |
| [Collection](/collections/collection)                    | Candidate |
| [Creating Collections](/collections/creating-collection) | Candidate |
| [Collection Rules](/collections/collection-rules)        | Candidate |
| [Data Streaming](/collections/data-streaming)            | Candidate |

## Schema Definition Language

| Page                                                                                          | Status    |
| --------------------------------------------------------------------------------------------- | --------- |
| [Overview](/schema-definition-language/internet-object-schema)                                | Candidate |
| [Schema Representation](/schema-definition-language/schema-representation)                    | Candidate |
| [Schema Data Types](/schema-definition-language/data-types)                                   | Candidate |
| [Any](/schema-definition-language/data-types/any)                                             | Candidate |
| [String Types](/schema-definition-language/data-types/string)                                 | Candidate |
| [Email](/schema-definition-language/data-types/string/email)                                  | Candidate |
| [URL](/schema-definition-language/data-types/string/url)                                      | Candidate |
| [Numeric Types](/schema-definition-language/data-types/number)                                | Candidate |
| [BigInt](/schema-definition-language/data-types/bigint)                                       | Candidate |
| [Decimal](/schema-definition-language/data-types/decimal)                                     | Candidate |
| [Date and Time](/schema-definition-language/data-types/date-and-time)                         | Candidate |
| [Binary](/schema-definition-language/data-types/binary)                                       | Candidate |
| [Object (SchemaDef)](/schema-definition-language/data-types/object)                           | Candidate |
| [Array](/schema-definition-language/data-types/array)                                         | Candidate |
| [Bool](/schema-definition-language/data-types/bool)                                           | Candidate |
| [TypeDef](/schema-definition-language/advanced-schema-concepts/typedef)                       | Candidate |
| [MemberDef](/schema-definition-language/advanced-schema-concepts/memberdef)                   | Candidate |
| [Open & Dynamic Schemas](/schema-definition-language/advanced-schema-concepts/dynamic-schema) | Candidate |
| [Union Types (anyOf)](/schema-definition-language/advanced-schema-concepts/union-types)       | Candidate |
| [Composition & Reuse](/schema-definition-language/advanced-schema-concepts/composition)       | Candidate |

## Streaming

| Page                                                | Status    |
| --------------------------------------------------- | --------- |
| [Overview](/streaming/streaming)                    | Candidate |
| [Wire Format & Framing](/streaming/wire-format)     | Candidate |
| [Stream Items](/streaming/stream-items)             | Candidate |
| [Schema & State](/streaming/schema-and-state)       | Candidate |
| [Streaming Error Model](/streaming/error-model)     | Candidate |
| [Readers & Writers](/streaming/readers-and-writers) | Candidate |

## Parsing & Errors

| Page                                                              | Status    |
| ----------------------------------------------------------------- | --------- |
| [Overview](/parsing-and-errors/parsing-and-errors)                | Candidate |
| [Error Model](/parsing-and-errors/error-model)                    | Candidate |
| [Parser Behavior & Recovery](/parsing-and-errors/parser-behavior) | Candidate |
| [Error Accumulation](/parsing-and-errors/error-accumulation)      | Candidate |

## Conformance

| Page                                                  | Status    |
| ----------------------------------------------------- | --------- |
| [Validation Model](/conformance/validation-model)     | Candidate |
| [Conformance Requirements](/conformance/requirements) | Candidate |

## Interoperability

| Page                                                              | Status    |
| ----------------------------------------------------------------- | --------- |
| [JSON Compatibility](/interoperability/json-compatibility)        | Candidate |
| [Converting To/From Other Formats](/interoperability/conversions) | Candidate |

## Appendices

| Page                                         | Status    |
| -------------------------------------------- | --------- |
| [Formal Grammar (EBNF)](/appendices/grammar) | Candidate |

*18 informative (non-normative) pages — guides, rationale, appendices, and these versioning pages — are not graded for maturity and are omitted above.*

## See Also

* [Versioning Policy](/versioning-and-stability/versioning) — the maturity levels and rules behind this table
* [Roadmap](/appendices/roadmap) · [Version History](/versioning-and-stability/version-history)


# JSON Compatibility

JSON compatibility in Internet Object.

Internet Object is designed to be friendly to JSON. A large subset of JSON values parses directly, and most Internet Object data converts cleanly to JSON.

## JSON parses as Internet Object

Quoted-key object syntax and quoted strings, arrays, numbers, booleans, and `null` are valid Internet Object. A JSON object can be used as a record as-is:

```ruby
---
{"name": "John", "age": 30, "active": true}
```

The idiomatic Internet Object form drops the quotes and repeated keys by moving them into the schema:

```ruby
name, age, active: bool
---
John, 30, true
```

## Differences

* **Keys** — Internet Object keys may be unquoted; JSON requires quotes. Both are accepted.
* **Booleans / null** — Internet Object also allows the compact forms `T`, `F`, `N`.
* **Comments** — Internet Object allows `#` comments; JSON does not. Comments are dropped when converting to JSON.
* **Schema** — Internet Object carries an optional schema/header that JSON has no equivalent for; on conversion it becomes external (or is dropped).

## Lossy cases (IO → JSON)

Some Internet Object types have no exact JSON counterpart and convert approximately:

| Internet Object              | JSON representation                                              |
| ---------------------------- | ---------------------------------------------------------------- |
| `decimal` (`1.50m`)          | number or string (precision/scale not preserved by JSON numbers) |
| `bigint` (`123n`)            | number (may lose precision) or string                            |
| `datetime` / `date` / `time` | ISO-8601 string                                                  |
| `binary` (`b'…'`)            | base64 string                                                    |
| `NaN`, `Inf`, `-Inf`         | not representable in JSON (often `null` or a string)             |

For exact round-trips, keep the data in Internet Object form.

## See Also

* [Converting To/From Other Formats](/interoperability/conversions)
* [Why Internet Object?](/internet-object/why-internet-object)


# Converting To/From Other Formats

Converting Internet Object to and from JSON and other formats.

Internet Object interoperates with common data formats. This page summarizes how data maps in each direction. For the JSON specifics, see [JSON Compatibility](/interoperability/json-compatibility).

## JSON

* **JSON → IO** — direct: a JSON object/array is valid Internet Object data. Optionally lift repeated keys into a schema to shrink collections.
* **IO → JSON** — direct for the common types; lossy for `decimal`, `bigint`, `datetime`, `binary`, and `NaN`/`Inf` (see [JSON Compatibility](/interoperability/json-compatibility)).

## CSV

* **CSV → IO** — a header row maps to a schema; each data row becomes a collection record:

  ```ruby
  # CSV:  name,age,active
  #       John,30,true
  name: string, age: int, active: bool
  ---
  ~ John, 30, true
  ```
* **IO → CSV** — flat collections export cleanly; nested objects/arrays must be flattened or encoded, since CSV has no nesting.

## YAML

YAML and Internet Object both support nesting and comments. Conversion is straightforward for scalars, maps, and sequences; Internet Object adds schema and validation that YAML lacks, and its richer scalar types (`decimal`, `bigint`, `datetime`, `binary`) map to YAML scalars.

## General guidance

* Converting **into** Internet Object is a good time to add a schema and tighten types.
* Converting **out** may be lossy for the precise types above — keep the canonical copy in Internet Object when exactness matters.

## See Also

* [JSON Compatibility](/interoperability/json-compatibility) · [Why Internet Object?](/internet-object/why-internet-object)


# Best Practices & Guidelines

Practical guidance for authoring Internet Object documents and schemas.

## Schemas

* **Type your fields in production.** Prefer `name: string, age: int` over bare `name, age`; types catch bad data and document intent.
* **Use keyed schemas beyond trivial records.** Positional (CSV-like) data is great for flat, fully-required records; reach for keyed fields once shapes get richer.
* **Put optional fields last in positional schemas.** A leading optional field can swallow the next value (the first value provided fills the optional slot).
* **Reuse with references.** Define a shape once as `$address` and reference it; change it in one place. See [Composition & Reuse](/schema-definition-language/advanced-schema-concepts/composition).

## Values

* **Quote ambiguous values.** Quote strings that look like numbers, booleans, or null (`"123"`, `"true"`, `"N"`), contain commas, or contain `:` / `/` (URLs, times).
* **Pick the right number type.** Use `int`/`uint`/`intN` for whole numbers, `decimal` (`m`) for money, `bigint` (`n`) for very large integers — not `number` for everything.
* **Mark nullability explicitly** with the `*` suffix; mark optionality with `?`.

## Documents

* **Keep the schema in the header**, data below `---`. Use metadata keys for paging/status.
* **Use collections for many similar records**; one schema validates them all.
* **Comment the why.** Use `#` to explain intent, not to restate the obvious.

## Interoperability

* **Stay in Internet Object when exactness matters.** `decimal`, `bigint`, `datetime`, and `binary` can be lossy when converted to JSON — see [JSON Compatibility](/interoperability/json-compatibility).

## See Also

* [Schema-First Design](/core-concepts/schema-first) · [Getting Started](/internet-object/getting-started)


# Formal Grammar (EBNF)

The grammar of Internet Object in EBNF.

This appendix gives the grammar of Internet Object in EBNF. It is the normative reference for document structure; the prose chapters explain semantics. Lexical rules (whitespace, comments, literals) are listed at the end.

> Notation: `=` defines a rule, `|` alternation, `[ ]` optional, `{ }` zero-or-more, `( )` grouping, `"…"` a literal. Whitespace and comments may appear between tokens unless a rule states otherwise.

## Document

```ebnf
document        = [ header "---" ] section { sectionBreak section }
                | value ;                         (* a bare, header-less value/object *)

header          = { definition } ;
section         = [ sectionTag ] ( collection | object ) ;
sectionBreak    = "---" [ sectionTag ] ;
sectionTag      = name [ ":" schemaRef ]          (* e.g.  employee : $employee *)
                | schemaRef ;                      (* e.g.  $employee *)
```

## Header definitions

```ebnf
definition      = "~" key ":" defValue ;
key             = metaKey | variableKey | refKey ;
metaKey         = name ;                            (* metadata *)
variableKey     = "@" name ;                        (* value variable *)
refKey          = "$" name ;                        (* schema/type reference *)
defValue        = value | schema ;
schemaRef       = "$" name ;
```

## Collections and records

```ebnf
collection      = record { record } ;
record          = "~" [ recordBody ] ;             (* "~" alone = empty object {} *)
recordBody      = object | openObject | value ;    (* a bare value is promoted to an object *)
```

## Objects and arrays

```ebnf
object          = "{" [ memberList ] "}" ;
openObject      = memberList ;                      (* unbraced, top-level only *)
memberList      = member { "," member } [ "," ] ;
member          = [ key ":" ] value ;              (* keyed or positional *)

array           = "[" [ valueList ] "]" ;
valueList       = value { "," value } ;
```

## Values

```ebnf
value           = object | array | scalar | variableRef ;
variableRef     = "@" name | "$" name ;
scalar          = string | number | bigint | decimal
                | datetime | binary | boolean | null ;
```

## Schema (MemberDef and SchemaDef)

```ebnf
schema          = schemaDef | memberDef | refKey ;
schemaDef       = "{" memberDefList "}" | memberDefList ;   (* object shape *)
memberDefList   = schemaMember { "," schemaMember } [ "," "*" [ ":" memberDef ] ] ;
schemaMember    = name [ "?" ] [ "*" ] [ ":" ( type | memberDef | array | schemaRef ) ] ;
memberDef       = "{" type { "," option } "}" ;            (* type + constraints *)
option          = positionalValue | ( name ":" value ) ;   (* e.g. min: 0 *)
type            = "string" | "int" | "uint8" | "bool" | "datetime" | "decimal" | … ;
```

## Lexical

```ebnf
string          = openString | regularString | rawString ;
regularString   = '"' { char | escape } '"' | "'" { char | escape } "'" ;
rawString       = "r" ( '"' { rawChar } '"' | "'" { rawChar } "'" ) ;
openString      = unquotedChar { unquotedChar } ;          (* ends at a structural char *)

number          = [ sign ] ( decInt [ frac ] [ exp ]    (* frac/exp: base-10 only *)
                           | frac [ exp ]               (* leading-dot form, e.g. .5 *)
                           | hex | octal | binaryNum ) ;
hex             = ( "0x" | "0X" ) hexDigit { hexDigit } ;
octal           = ( "0o" | "0O" ) octDigit { octDigit } ;
binaryNum       = ( "0b" | "0B" ) ( "0" | "1" ) { "0" | "1" } ;
bigint          = [ sign ] ( decInt | hex | octal | binaryNum ) "n" ;
decimal         = [ sign ] ( decInt [ frac ] | frac ) "m" ;

datetime        = "dt" quoted | "d" quoted | "t" quoted ;  (* ISO-8601 inside quotes *)
binary          = "b" quoted ;                              (* base64 inside quotes *)

boolean         = "T" | "F" | "true" | "false" ;
null            = "N" | "null" ;
specialNumber   = "NaN" | "Inf" | "+Inf" | "-Inf" ;

comment         = "#" { anyCharExceptNewline } ;
ws              = ? Unicode whitespace ? ;
name            = nameStart { nameChar } ;
```

> This grammar is a working draft for the 1.0 specification. Edge cases (precise open-string termination, escape sequences, and datetime sub-formats) are described in their respective chapters and will be folded in as the grammar is finalized.

## See Also

* [Structural Elements](/structure-and-syntax/structural-elements)
* [Value Representations](/structure-and-syntax/values)
* [Internet Object Schema](/schema-definition-language/internet-object-schema)


# Glossary

Definitions of key Internet Object terms.

**Document** — A complete Internet Object unit: an optional header, then data, optionally split into sections.

**Header** — The part of a document before `---`, holding the schema, definitions, and metadata.

**Data section** — The part after `---` holding the actual values: a single object or a collection. A document may have multiple named sections.

**Section** — A named (or default) block of data with an associated schema, introduced by a `---` separator.

**Collection** — An ordered sequence of records, each beginning with `~`.

**Record** — One item of a collection (an object).

**Object** — An ordered set of key/value members, written `{ … }` or, at top level, unbraced.

**Member** — One key/value pair (keyed) or value (positional) inside an object.

**Value** — A scalar (string, number, bigint, decimal, datetime, binary, boolean, null) or a structured value (object, array).

**Schema** — A description of the shape and constraints of data, written in object syntax.

**SchemaDef** — A schema that describes an object's *shape* (its fields and their types).

**MemberDef** — A definition of a single field: its type plus constraints (`{ string, maxLen: 40 }`).

**TypeDef** — The fixed contract for a built-in type: the options a MemberDef of that type may use. Defined by the specification, not by authors.

**Type** — A named kind of value (`string`, `int`, `decimal`, …). Built-in types form a closed set; some are shortcuts (e.g. `uint8`, `email`) over a base type plus constraints.

**Shortcut** — A built-in name that stands for a base type plus preset constraints (`int8` is `int` with an 8-bit range; `email` is `string` with an email `pattern`). A true 1:1 rename (e.g. `byte` for `uint8`) is an **alias**.

**Definition** — A header entry `~ key: value` — metadata, a variable, or a reference.

**Variable** — A reusable value defined with `@name` and used as `@name`.

**Reference (ref)** — A reusable schema or type defined with `$name` and used as `$name`.

**`$schema`** — The special reference naming a document's default schema.

**Open string / Regular string / Raw string** — The three ways to write text: unquoted, quoted with escapes, and `r'…'` literal.

**Open schema** — A schema marked with `*` that accepts fields beyond those declared.

**Optional / Nullable** — A field marked `?` may be omitted; a field marked `*` may be `null`.

## See Also

* [Getting Started](/internet-object/getting-started) · [Formal Grammar](/appendices/grammar)


# FAQs

Frequently asked questions about Internet Object — what it is, its features, interoperability, size and performance, status, and how to take part.

## Getting oriented

### Why do we need another data-interchange format?

Internet Object was created to address long-standing limitations of JSON — today's most common interchange format — including keys repeated on every record, the absence of a built-in schema, no comments, and no native streaming model. It keeps JSON's readability while removing that redundancy and adding schema-driven validation, precise data types, comments, and streaming. For the full background, [read the story](https://internetobject.org/the-story/).

### Why is it called "Internet Object"?

The name reflects its purpose: a compact, readable way to exchange **objects** — structured data — across the **internet**. A document is treated as data plus the schema that gives it shape, so the same objects travel efficiently between services, languages, and platforms.

### When should I use Internet Object, and when not?

Internet Object suits data that has a stable shape and is exchanged or stored repeatedly: APIs, records and collections, logs, configuration, and high-volume or streamed data where size and validation matter. It is less compelling for one-off, entirely ad-hoc data with no recurring shape, where JSON's ubiquity may outweigh the savings. See [Best Practices & Guidelines](/best-practices/best-practices) for guidance.

### Is Internet Object a replacement for JSON, or a complement?

Either, depending on your needs. It can replace JSON where its schema, compactness, comments, precise types, or streaming are valuable, and it interoperates with JSON where you need to keep using it — a wide subset of JSON parses directly as Internet Object. You can adopt it incrementally rather than all at once.

## Features

### Is a schema required, or can I use it schemaless?

A schema is recommended but not required. Internet Object is **schema-first, not schema-required**: a document with no schema is still valid, and its values map to positional keys. Declare a schema once the data has a stable shape, leaves your control, or needs validation. See [Schema-First Design](/core-concepts/schema-first).

### Does it support comments, dates, and precise numbers?

Yes. Internet Object supports comments, native date and time values, and precise numeric types — including exact decimals and big integers — that JSON either omits or represents loosely as strings or floats. See [Comments](/structure-and-syntax/comments), [Date and Time](/structure-and-syntax/values/date-and-time), [Decimal](/structure-and-syntax/values/number/decimal), and [BigInt](/structure-and-syntax/values/number/bigint).

### Does Internet Object support binary data?

Yes — as encoded binary. The wire form is text-based and human-readable, so it does not embed raw bytes directly; instead it provides a `binary` type that carries binary data as base64 text (`b'…'`), which a parser decodes to bytes. This keeps documents safe to transmit and read as plain text while still representing binary payloads. See [Binary](/structure-and-syntax/values/binary).

### Does it support streaming?

Yes. Because a collection is a sequence of independent records, Internet Object can be produced and consumed incrementally — one record at a time, each validated on its own — without waiting for the whole document. The framing, record model, and error handling are defined in the [Streaming](/streaming/streaming) chapter.

## Working with JSON and other formats

### Can an Internet Object parser read JSON, and can a schema validate it?

Largely, yes. JSON compatibility was not an original goal, but a wide subset of JSON parses directly as Internet Object data; and because it parses as data, the same schema validates both an Internet Object document and an equivalent JSON object. See [JSON Compatibility](/interoperability/json-compatibility) for the exact rules and the lossy cases.

### Can I use Internet Object to validate my existing JSON API?

Yes. Because a wide subset of JSON parses directly as Internet Object data, you can write an Internet Object schema and validate the JSON your API already sends and receives — without changing the API itself. This makes adoption low-risk: keep your existing JSON on the wire and gain schema-driven validation now, then move to the more compact Internet Object encoding later if you choose. See [JSON Compatibility](/interoperability/json-compatibility).

### How do I convert to and from JSON or other formats?

Internet Object maps cleanly to and from JSON and other common formats. The [Converting To/From Other Formats](/interoperability/conversions) chapter covers the mapping rules, and [JSON Compatibility](/interoperability/json-compatibility) covers the JSON specifics, including where a conversion can lose precision.

## Size and performance

### Is an Internet Object document smaller than JSON?

Usually, and often by a wide margin. For record-heavy data an uncompressed Internet Object document is commonly **40–60% smaller** than the equivalent JSON — and sometimes more — because field names are declared once in the schema rather than repeated on every record. Once both are gzipped the difference narrows and depends on the data.

### Is Internet Object faster to build and parse than JSON?

Building is very fast. The format is simple and straightforward, and a document can be produced by plain string concatenation or interpolation — no special serializer is required.

Parsing is not a like-for-like comparison. A JSON parser only checks syntax, whereas an Internet Object parser, in a single pass, also validates the data against the schema — types, constraints, defaults, and required fields. You receive validated, structured data, not just parsed text. As with any format, raw parsing speed depends more on the quality of the parser and the workload than on the format itself.

## Status, ecosystem, and licensing

### Is Internet Object ready for production, and what is the current status?

The specification is at **1.0 Draft**, published alongside the **public beta** of the reference implementation. The format is ready for evaluation and early adoption; the specification and the implementation are still converging, so check the status notes on individual pages before relying on a feature in production. Implementations version independently and declare the specification version they conform to.

### Which languages have libraries?

A JavaScript/TypeScript reference implementation is available in public beta. Libraries for other languages are planned, and the specification is deliberately language-agnostic so anyone can implement it. See the [Roadmap](/appendices/roadmap), and the contribution options below to help build one.

### How is Internet Object licensed?

The specification is licensed under **CC BY-ND 4.0** — free to share with attribution, with no modified versions. Example snippets in the specification are dedicated to the public domain under **CC0**, and the reference libraries are licensed under **Apache-2.0**. Full details are on the [License](/appendices/license) page.

## Community

### How can I contribute?

Contributions are welcome in many forms:

1. Join a team building an Internet Object library in your favorite language.
2. Write a blog post or article about Internet Object.
3. Help friends and colleagues get started with the format.
4. Help develop the technical documentation.
5. Proofread and help correct the specification and its language.
6. Translate the documentation into other languages.
7. Spread the word about Internet Object.

### How do I report an issue or propose a change?

Issues, corrections, and proposals are handled in the specification's repository at [github.com/maniartech/InternetObject-specs](https://github.com/maniartech/InternetObject-specs). Open an issue describing the problem or suggestion; substantive changes to the format are discussed there before they enter the specification.

## See Also

* [Why Internet Object?](/internet-object/why-internet-object) · [Getting Started](/internet-object/getting-started)
* [JSON Compatibility](/interoperability/json-compatibility) · [Roadmap](/appendices/roadmap)
* [License](/appendices/license) · [Acknowledgments](/appendices/contributors)


# Roadmap

Planned directions for the Internet Object specification and ecosystem.

This page outlines the direction of the Internet Object specification and its surrounding ecosystem. It is **informative**, not normative: it describes intended work, not guaranteed features or dates. Items may change as the format and its reference implementation converge during the 1.0 Draft period.

## Specification

* **Converge the spec and the reference implementation.** Where the specification currently describes behavior ahead of the implementation (noted on the affected pages), close the gap in one direction or the other and remove the qualifier.
* **Finalize the formal grammar.** Fold the remaining lexical edge cases — precise open-string termination, the full escape-sequence set, and date/time sub-formats — into the [EBNF grammar](/appendices/grammar) so it is complete and self-contained.
* **Complete the error catalogue.** Give every malformed literal and structural failure a stable, documented [error code](/parsing-and-errors/error-model), so tooling can branch on codes for all failures rather than most.
* **Settle the numeric type set.** Confirm the final list of numeric shortcuts and their ranges, and mark any that remain reserved.
* **Finalize streaming.** Specify the framing and collection-merge rules for [streaming collections](/collections/data-streaming) once the model stabilizes.

## Conformance

* **Publish a conformance test suite.** A language-independent set of documents and expected outcomes (valid data, error codes, positions) that any implementation can run to demonstrate conformance with the [requirements](/conformance/requirements).

## Implementations and tooling

* **Reference implementation to 1.0.** Track the TypeScript/JavaScript implementation to a stable 1.0 aligned with the finalized specification.
* **Implementations in more languages.** Support community libraries for additional languages, each declaring the specification version it conforms to.
* **Authoring tooling.** Encourage formatters, linters, editor support (syntax highlighting, schema-aware completion), and converters to and from JSON, CSV, and YAML.

## See Also

* [Version History](/versioning-and-stability/version-history) · [Conformance Requirements](/conformance/requirements)
* [Acknowledgments](/appendices/contributors) · [FAQs](/appendices/faqs-1)


# Acknowledgments

The author of the Internet Object specification and the ManiarTech team who helped produce it.

Internet Object was researched, created, and authored by **Mohamed Aamir Maniar** (ManiarTech®), who maintains the specification.

Its development has been supported by the **ManiarTech® team**:

| Team member         | Contribution                   |
| ------------------- | ------------------------------ |
| Kabir Maniar        | Document drafting and diagrams |
| Ujwala Mhashakhetri | Document drafting              |

Internet Object also welcomes contributions from the wider community — see the ways to help in the [FAQs](/appendices/faqs-1).

## See Also

* [FAQs](/appendices/faqs-1) · [Roadmap](/appendices/roadmap)


# License

Licensing terms for the Internet Object Specification, its examples, and its implementations.

The **Internet Object Specification** — the documentation text in this repository — is licensed under the **Creative Commons Attribution-NoDerivatives 4.0 International License (CC BY-ND 4.0)**.

© 2020–2026 Mohamed Aamir Maniar / ManiarTech®. **Internet Object™** is a trademark of ManiarTech®. The official specification is published at [internetobject.org](https://internetobject.org).

## What this means

You are free to **share** the specification — copy and redistribute it in any medium or format, for any purpose, including commercially — under the following terms:

* **Attribution.** You must give appropriate credit, link to the license, and indicate the official version. You may do so in any reasonable manner, but not in any way that suggests the author endorses you or your use.
* **No derivatives.** If you remix, transform, or build upon the specification, you may **not** distribute the modified material. The canonical text is maintained solely by the author so the format stays consistent across every implementation.

There are **no additional restrictions** beyond these — you may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.

## Preferred attribution

When crediting the specification, use:

> "Internet Object Specification" by Mohamed Aamir Maniar (ManiarTech®), licensed under [CC BY-ND 4.0](https://creativecommons.org/licenses/by-nd/4.0/). Official version: <https://internetobject.org>.

## Implementations are not derivatives

Building software that implements the format described here — a parser, validator, serializer, or any other tool — **does not create a derivative work of the specification** and is **not** restricted by CC BY-ND. The license covers the specification *text*, not the *format* it describes.

The reference implementations and libraries are licensed separately under the **Apache License 2.0**.

## Examples are public domain

All example code and Internet Object snippets embedded in the specification are dedicated to the public domain under [**CC0 1.0**](https://creativecommons.org/publicdomain/zero/1.0/). You may copy them into your own work freely, without restriction or attribution.

## Translations

A **translation is a derivative work** (an adaptation) of the original English specification. Because the specification is licensed under CC BY-ND 4.0, derivative works are not permitted by the license itself; the permission below is a **separate, limited grant** by the author.

Faithful translations are permitted, provided they:

* are clearly marked as **unofficial**;
* reproduce the specification faithfully, without adding to, removing from, or altering its meaning;
* prominently link to the official English version above and identify it as **authoritative**; and
* are not presented as the specification itself.

The **original English text is the sole authoritative and normative version.** In case of any discrepancy or conflict between a translation and the English original, the **English version governs**; translations are provided for convenience only and carry no normative force. The author reserves the right to designate official translations and to withdraw this permission for any translation that misrepresents the specification.

## Full text

The complete legal text is in the [`LICENSE`](https://github.com/maniartech/InternetObject-specs/blob/main/LICENSE) file at the root of the repository, and the canonical license is available from Creative Commons at [creativecommons.org/licenses/by-nd/4.0](https://creativecommons.org/licenses/by-nd/4.0/).

## Summary

| Material                      | License      | You may                                        |
| ----------------------------- | ------------ | ---------------------------------------------- |
| Specification text            | CC BY-ND 4.0 | Share with attribution; no modified copies     |
| Example snippets              | CC0 1.0      | Reuse freely, no attribution required          |
| Implementations and libraries | Apache-2.0   | Use, modify, and distribute under Apache terms |


