Documentation/Reference

Shared serialization and custom format metadata

On this page

Shared rules and format overrides

TYPE Customer
    Id AS INTEGER
    FullName AS STRING SERIALIZE NAME "full_name" JSON NAME "name"
    Password AS STRING = "" SERIALIZE WRITEONLY
    Internal AS STRING = "private" SERIALIZE EXCLUDE
    Nick AS OPTIONAL OF STRING SERIALIZE OMITNONE
END TYPE

SERIALIZE accepts NAME, READONLY, WRITEONLY, EXCLUDE, OMITNONE, ENCODE WITH and DECODE WITH. Their direction/default/initialization semantics match the existing JSON clauses . They are schema mapping rules, not changes to BASIL mutability. Defaults are ordinary TYPE field defaults. NAME accepts a string literal or a parenthesized constant STRING expression; parentheses keep a following TYPE field default unambiguous. This also preserves multiline wire names through compiled metadata. TYPE headers accept shared ENCODE WITH / DECODE WITH defaults, including on choices. Case payload declarations do not accept field annotations.

ToValue/FromValue apply shared rules and converters; they ignore JSON-specific rules/profiles. EncodeJSON/DecodeJSON and ToJSON/FromJSON use the effective JSON mapping. Existing programs using only JSON clauses retain their previous behavior.

JSON clauses override the same shared property regardless of source order: NAME replaces NAME; READONLY/WRITEONLY/EXCLUDE replace the shared access group; each converter direction replaces that direction. OMITNONE is additive. There is currently no INCLUDE or explicit undo-OMITNONE clause. Contradictions within one scope, duplicate effective names in either scope and OMITNONE on a nonoptional field are compile errors, even without an adapter call.

For JSON converters, precedence independently by direction is JSON field override, shared field override, supplied JsonCodec rule, JSON type default, shared type default, generated standard handling. A selected encode/decode pair must have matching representation types. Shared converters can accept a second readonly SerializationLocation { Path, Offset }; the existing JsonLocation form also remains accepted. ToValue/FromValue have no parsed text locations, so Offset is -1; typed DecodeJSON supplies the parsed byte offset. Checked errors and partial cleanup use the ordinary call contracts. Allocation failure remains fatal.

Typed format options

TYPE PacketField
    Number AS INTEGER
    Label AS STRING = ""
END TYPE
TYPE Customer FORMAT PacketField { Number=100 }
    Id AS INTEGER FORMAT PacketField { Number=1 }
    Name AS STRING FORMAT PacketField { Number=2,Label="customer" }
END TYPE

FORMAT attaches an independent typed constant record. It can occur on a TYPE header or ordinary TYPE field, together with SERIALIZE/JSON and other FORMAT annotations. A given options type can occur once per declaration. Identity follows normal imports and nominal types; two modules’ identically named options are distinct. The compiler checks the literal, not an open dictionary of unchecked attributes.

Supported option values: checked scalar/string/enum constant expressions, NONE, SOME(constant), nested ordinary record literals, and matching empty lists/sets/dicts. Omitted options need an ordinary declared field default. Record nesting is bounded at 32. Arbitrary calls, nonempty collection literals, callable values, resources, choice literals and runtime variables are not constant annotation values. Checked integer overflow and constant/default type errors are rejected. No annotation code runs at build time. Type names, field names, private constants and imports use ordinary resolution/access rules.

Custom formats define their own option types and interpret them in normal library code. For example, an XML library could define XmlField with Name/Attribute options; a binary codec could define numeric field identifiers. Merely naming an options type XmlField does not give it compiler privileges. No built-in XmlField, YamlField, TomlField or JsonField metadata type is supplied in this milestone. Existing JSON clauses remain the JSON-specific mapping interface.

Public schema and metadata queries

For recursive field/element references, enums/choices, bound generic arguments and converter representations, use TypeSchema / SerializationTypeSchema / JsonTypeSchema . The older flat queries below remain supported.

DIM witness AS OPTIONAL OF Customer=NONE
schema=SerializationSchema(witness)
options=FormatMetadata(witness,"Id") AS OPTIONAL OF PacketField
IF LET field=options THEN
    PRINT field.Number
END IF
API Result
SerializationSchema(witness) Record name and ordered LIST OF SerializationField
FormatMetadata(witness[,field]) AS OPTIONAL OF M Owned M or NONE; M inferred from AS or typed destination
ToValue(value) Checked shared mapping into the existing JsonValue-compatible tree
FromValue(tree) AS T Checked shared construction of T with defaults and initialization

SerializationField contains Name (physical BASIL spelling), WireName (shared name), TypeName, IsOptional, HasDefault, ReadOnly, WriteOnly, Excluded and OmitNone. Query fields by physical BASIL name, case-insensitively; omitted/empty field means the TYPE header. An unannotated field/type or unrelated metadata type returns NONE. Header metadata is separate from field metadata; codecs can apply their own explicit inheritance/default policies. Each successful query constructs a fresh independent value. Mutating a returned options collection cannot change subsequent queries. The optional type witness never reads or constructs an uninitialized record.

Legacy ValueSchema/ValueField remain available: JsonName and their access flags report the effective JSON mapping. Schema queries expose no pointers, mutable views, erased addresses or general runtime reflection. The compiler generates ordinary typed BASIL construction/access functions; metadata errors use source locations. The defining record’s metadata is not automatically executed.

The codec is responsible for interpreting its own options, rejecting unsupported representations and checking format rules such as unique positive field numbers. Recoverable codec errors must be caught or declared normally. Unrelated annotations have no effect on a codec that does not use them.

Boundaries and examples

The runnable application uses a fully user-written numbered-field codec , with strings, exact-number text, Boolean and absent-optional payload tags. It validates field numbers and wire tags; the intentionally small example rejects nested payloads, tabs/newlines in strings and unknown/duplicate fields. It uses no JSON text parser/writer for its wire format. This demonstrates a public extension point, not general binary-format conformance.

The existing ToValue/FromValue transport is still JsonValue and reports JsonError; it is a bounded JSON-compatible value tree, not a lossless universal YAML/TOML/XML document model. Codecs can use their own typed representations and normal checked construction; richer common transport/field visitors remain a checklist item before claiming native temporal/tagged values, arbitrary mapping keys or mixed XML content. Typed metadata does not force custom options or codec documents into JsonValue. Generic user codec functions must declare the custom converter errors they allow to escape, alongside their own failures. Automatic per-specialization generic error-set inference is not implemented; a missing declaration is rejected. Format-specific profiles are ordinary codec-owned configuration; no global registry or arbitrary attribute grammar is introduced.

From the BASIL documentation. View source ↗

Find your way around BASIL

Type to search the manual.