Documentation/Reference

JSON

On this page

Everyday typed values

TYPE Customer
    Id AS INTEGER
    Name AS STRING = "Unknown"
    Nick AS OPTIONAL OF STRING = NONE JSON OMITNONE
END TYPE

customer = DecodeJSON("{""Id"":42}") AS Customer
PRINT EncodeJSON(customer)
DIM other AS Customer
other = DecodeJSON("{""Id"":7,""Name"":""Ada""}")
PRINT EncodeJSON(other, JsonCodec(Pretty=TRUE, Indent=2))

A complete call’s AS annotation or known destination supplies its result type. ToJSON(value[, codec]) returns JsonValue; FromJSON(value[, codec]) AS T converts an existing JsonValue. EncodeJSON writes text; DecodeJSON accepts STRING or packed BYTE list. Failures use the normal checked FAILS/CATCH channel. Inline TRY is optional; allocation failure remains fatal.

Supported standard mappings:

BASIL value JSON representation
BOOLEAN / STRING Boolean / UTF-8 string
INTEGER, BYTE, UINT32, UINT64 Exact integer number
DOUBLE Finite JSON number; NaN/infinity fail
OPTIONAL OF T null for NONE; otherwise T’s representation
LIST OF T / SET OF T Array, preserving iteration order; decoding a set applies ordinary duplicate elimination
Ordinary record / ERROR record Object of fields
ENUM Its integer value; decoding checks declared membership
Closed choice Exactly one member, e.g. {"Text":"hi"} or {"Empty":null}
DICT OF K TO V Ordered object; key rules below
ARRAY OF T Zero-based rank-one array, or a bounds-preserving object below
JsonValue Its represented JSON tree, with no original application type identity

Resources cannot be serialized or copied into formatter transport. A function value has no standard JSON representation; an explicitly registered formatter may give it a checked representation. Unsupported active fields fail rather than exposing handles or addresses. JSON EXCLUDE can hide such copyable fields during encoding; decoding still requires valid initialization.

Dictionary STRING keys are ordinary member names. Other keys use their encoded JSON text as the member name, unless their selected converter’s representation is STRING, in which case that string is the name. Decoding reverses this rule using the statically known key type and codec. Distinct BASIL keys producing the same name, or distinct names decoding to an equal key, fail with code 3. Thus {"1":2,"1.0":3} cannot silently overwrite a DICT OF INTEGER TO INTEGER entry. These non-string-key conventions extend JSON’s string-only object names.

A nonzero-based or multidimensional array encodes as {"$bounds":[lower,upper,...],"$values":[flat row-major values...]}. Decoding validates signed intervals, rank, shape arithmetic and cell count before constructing an array. A plain JSON array decodes to a zero-based rank-one array. Shape metadata uses standard integers independently of data formatters.

Dynamic construction and edits

JsonObject() / JsonArray() create independent empty containers. For nested objects, use JsonObject([JsonField("name",value), ...]). JsonField returns an ordinary owned JsonMember (Name, Value); typed leaves use ToJSON and retain its checked converter failures. A JsonValue argument is adopted/copied with ordinary BYVAL semantics. Duplicate names replace the value in their first insertion slot. The existing dictionary-taking JsonObject and list-taking JsonArray overloads remain.

JsonPut(BYREF object,key,value) inserts/replaces a member and returns TRUE for insertion, FALSE for replacement. Typed inputs use ToJSON. A wrong-kind target raises JsonError code4; conversion failures preserve their declared type and leave the destination unchanged. Explicit BYREF works for named objects, fields and checked indexed locations; root loans exclude invalidation through aliases/tasks. JsonValue arguments are independent BYVAL snapshots, so inserting an object into itself stores its previous value, without a cycle. Mutations already committed before cancellation follow ordinary BYREF rules; there is no rollback transaction.

Defaults and field clauses

Shared SERIALIZE rules also apply; JSON clauses override the corresponding shared property. See shared precedence .

TYPE Customer
    Id AS INTEGER
    Name AS STRING = "Unknown" JSON NAME "full_name"
    Password AS STRING = "" JSON WRITEONLY
    ServerValue AS INTEGER = 0 JSON READONLY
    Internal AS INTEGER = 0 JSON EXCLUDE
    Nick AS OPTIONAL OF STRING JSON OMITNONE
    Tags AS LIST OF STRING = []
END TYPE
Clause Effect
NAME “name” Wire name in both directions; default is original field spelling
READONLY Encode, exclude from decoding; no change to BASIL mutability
WRITEONLY Decode, exclude from encoding
EXCLUDE Exclude both directions
OMITNONE Omit an absent optional on output; otherwise NONE writes null
ENCODE WITH F / DECODE WITH G Explicit converter for that field

JSON member names are case sensitive. Conflicting/repeated clauses, duplicate wire names and OMITNONE on a nonoptional field are compilation errors, including on unused types. READONLY and WRITEONLY cannot be combined; EXCLUDE cannot be combined with either. A supplied READONLY/EXCLUDE member is rejected under the standard strict policy; JsonCodec(IgnoreUnknown=TRUE) ignores it like any unknown member. Duplicate input keys are rejected even under that policy.

Missing fields use an explicit default, otherwise NONE for OPTIONAL, otherwise a checked missing-field error. Excluded/READONLY fields follow these same initialization rules. Explicit null means NONE only for OPTIONAL. Invalid supplied values and nonoptional null fail; defaults do not hide bad data. OPTIONAL field converters accept/return the payload type; missing/default/null handling precedes them.

Field defaults also apply to ordinary record construction. Current forms are checked compile-time scalar/string/enum expressions, NONE, and empty [], LIST(), SET() or DICT() of the matching field type. Effectful factories and nonempty aggregate defaults are not supported. Explicit constructor arguments run in written order, then omitted defaults in declaration order. Each collection default is independent. Ordinary construction still requires an explicit value or default for every field; implicit NONE for a missing optional is a decoder rule. Failed decoding destroys partial owners and preserves an existing assignment target.

Type converters and codec profiles

TYPE Money JSON ENCODE WITH FormatMoney DECODE WITH ParseMoney
    Cents AS INTEGER
END TYPE
FUNCTION FormatMoney(value AS Money) AS INTEGER = value.Cents
FUNCTION ParseMoney(value AS INTEGER) AS Money
    RETURN Money { Cents=value }
END FUNCTION

codec = WithJsonFormat(JsonCodec(Pretty=TRUE, Indent=2), FormatMoney, ParseMoney)
PRINT EncodeJSON(Money { Cents=1234 }, codec)

A pair’s original type comes from encoder input/decoder result; encoder output must match decoder input. The representation is another checked BASIL value, not unchecked raw JSON. STRING results are quoted/escaped. A representation’s root uses structural handling, avoiding immediate re-entry into the same rule; its nested values use ordinary recursive selection.

Precedence independently in each direction: JSON field override, shared field override, supplied profile rule, JSON type default, shared type default, standard generated handling. Profiles may hold many original types using the same representation type. Replacing a rule changes only that type and leaves the original profile unchanged. There is no mutable global registry. Profiles example .

The compiler retains a codec’s admitted errors in its static type and through compiled libraries. Inferred empty codecs must be configured in their initializer or assigned to a new binding; explicit wider JsonCodec annotations admit a wider checked set. Wrapper functions declare FAILS JsonError, LIKE codec; factories can retain errors with AS JsonCodec LIKE Encoder(AS T), LIKE Decoder(AS R). Private rules cannot be mutated. Compiled profile contract .

Current registration accepts unambiguous named, nongeneric, noncapturing functions with one read-only argument, or a second read-only JsonLocation/SerializationLocation argument. Reserved __ names, callable variables, overloaded ambiguous names, lambdas and resource representations are not accepted here. General checked function values remain available elsewhere.

JsonError automatically carries the JSON pointer, byte offset and message. Other converter errors retain their declared type and fields without an implicit wrapper or mutation. A context-aware converter can retain the supplied location explicitly:

ERROR InvalidDate
    Text AS STRING
    Location AS JsonLocation
END ERROR

Its converter can declare (text AS STRING, location AS JsonLocation) and return ERROR InvalidDate { Text=text, Location=location }. Offsets refer to the start of the supplied value; encoding/FromJSON lack source offsets and use -1. Runnable typed failure . One-argument custom errors are unchanged; they do not acquire hidden location fields. JsonError raised by a converter is normalized to the containing value’s wire location. Converter FAILS sets are preserved. An adapter admits the supplied codec’s immutable checked set and the selected value schema’s converter closure. JSON/shared choices within that schema are conservatively combined because context selects them at runtime; unrelated registrations elsewhere in the source graph do not broaden its contract.

Dynamic data, validation and limits

JsonValue is a closed choice with NullValue, BooleanValue, NumberValue (STRING), StringValue, ArrayValue (LIST OF JsonValue), and ObjectValue (ordered DICT). Use JsonNull/JsonBoolean/JsonNumber/JsonString/JsonArray/JsonObject constructors. JsonNumber validates a string’s grammar; the writer revalidates manually constructed NumberValue payloads. Exact number spellings, including large exponents and -0, are retained. Numeric getters reject type/range/fraction mismatches, with integer conversion performed without rounding through DOUBLE.

Find, Get(value,key,fallback), Contains, Keys and Values operate on dynamic objects. JsonAt(value,index) checks an array position. JsonGetString/Boolean/Array/ Object/Integer/UInt64/Double, JsonNumberText and IsJsonNull provide checked access. JsonPointer appends an RFC 6901 escaped member/index segment. ParseJsonDocument additionally retains a Locations map; typed DecodeJSON uses it.

The parser enforces RFC 8259 : UTF-8, strict number grammar/whitespace, escaped controls, paired surrogates, no BOM, trailing content/commas or duplicate keys. It does not normalize Unicode. Compact/pretty output preserves values, exact numbers and member order and has no trailing newline. Indent is spaces per level, 0..16. Root depth is zero.

JsonCodec defaults: Pretty=FALSE, Indent=2, MaxDepth=32, MaxInputBytes=8388608, MaxOutputBytes=8388608, IgnoreUnknown=FALSE. Depth is 1..64; byte limits are nonnegative. Limits apply before input conversion/output growth and during recursive conversion. A guarded coroutine may report a stack-budget limit before the requested depth. Output budgets count encoded bytes, including escaping and pretty whitespace; they are not a total heap quota. Long BASIL loops have budgeted cancellation/scheduling checkpoints; native buffer transactions do not suspend.

JsonError fields are Code, Offset (zero-based byte, -1 unavailable), Path (JSON pointer; empty at root), Message. Codes: 1 syntax, 2 UTF-8, 3 duplicate/collision, 4 type/range/representation, 5 missing field, 6 unknown/disallowed field, 7 limit, 8 invalid configuration. Invalid BYTE input currently reports UTF-8 offset 0. Unhandled JsonError diagnostics include the path, offset and message after cleanup.

User-defined formats

Shared serialization defines SERIALIZE clauses, checked typed FORMAT options, SerializationSchema and FormatMetadata. ToValue/FromValue apply shared rules/converters and ignore JSON-specific rules/profiles. They retain the existing JsonValue-compatible transport and JsonError channel. Legacy ValueSchema continues to report the effective JSON mapping. See the runnable custom-codec example . Richer format document models, EMBED evaluation, YAML/TOML/XML and generic record declarations remain separate work.

Files and JSON Lines

API Behavior
ReadJSON(file[,codec]) AS T Bounded stream read to EOF, then typed decode
ReadJSONFile(path[,codec]) AS T Open/read with automatic resource cleanup
WriteJSON(file,value[,codec]) Complete encoding before stream write; I/O failure can leave partial output
WriteJSONFile(path,value[,codec]) Complete encoding, then existing atomic replacement API
ReadJSONLine(file,BYREF reader[,codec]) AS OPTIONAL OF T One record, NONE at clean EOF
WriteJSONLine(file,value[,codec]) Compact record plus LF, even for a pretty profile

Initialize reader with JsonLinesReader(). It buffers input and exposes Line and Offset. LF/CRLF and an unterminated final record work; blank lines are invalid records. A malformed consumed line can be caught and the next line read. JsonError reports the stream byte offset and record number; custom JsonLocation offsets are relative to that line’s JSON document. Input/output limits apply per record. Do not mix direct reads or different readers on a buffered stream. Independent copies of reader state do not duplicate the file position.

Read helpers inherit worker-backed File reads and cancellation acknowledgement. Writes inherit existing WriteFile/atomic-write behavior, including synchronous regular-file writes. Flush/finishing errors remain explicit operations; no new normal-cleanup guarantee is made for fatal allocation failure.

HTTP and HTTPS

ReadJSONBody(request[,codec]) AS T handles a buffered request. Its streaming overload also takes connection and BYREF exchange and reads within both HTTP and codec limits. It checks application/json or application/*+json and UTF-8 charset; missing/invalid media types return HTTPError 415, oversized bodies 413. Parsing and typed conversion retain JsonError/declared converter failures.

JSONResponse(value[,status]) constructs a response; the full overload takes (value,status,headers,codec). It installs application/json and rejects conflicting framing headers and body-forbidden statuses. ReadJSONResponse(response[,codec]) AS OPTIONAL OF T rejects non-2xx with owned HTTPStatusError {Status,Headers,Body}, returns NONE for 204, and decodes other 2xx bodies (empty 200 is an error). JSON null for OPTIONAL OF U is SOME(NONE), distinct from no response content.

HTTPGetJSON(url) AS OPTIONAL OF T and HTTPPostJSON(url,body) AS OPTIONAL OF T use existing HTTP/TLS clients. The full GET overload takes (url,BYREF response,codec,options); POST adds body after url. Initialize response with HTTPResponse(). It retains status, headers, body and trailers before typed decoding, including on decode/status failure. The codec input limit caps transport body buffering. The ordinary HTTPGet/RequestHTTP + ReadJSONResponse composition also keeps complete response metadata.

RouteJSON(BYREF router,method,pattern,Handler[,codec]) accepts a named one-argument function: decode its checked input type, call it, encode its return value with 200. A handler returning HTTPResponse controls status/headers directly. It uses existing buffered routes and stores an independent per-route profile; no global registry or unchecked captures. Mutable router slots explicitly admit Error; registration does not infer a narrower lifetime-wide union. ServeHTTPChecked propagates original typed failures. Convenience ServeHTTP/ServeHTTPS handle JsonError as 400 and report other converter/route errors by concrete type name with a 500 response when headers have not started. They are request error boundaries; retain a task running the checked server when the caller needs typed failure observation. HTTP API retains its existing limits. Small server example .

Opaque representations

Opaque types require a declared or profile formatter for encoding/decoding. Automatic JSON/shared record mapping raises JsonError code4 instead of exposing private state or constructing invalid representations. A formatter must use the defining module or public factories. For native collections, serialize a logical snapshot or register a concrete formatter; physical ring/heap/cache state is not a wire schema. Ordinary public records keep generated field mapping.

From the BASIL documentation. View source ↗

Find your way around BASIL

Type to search the manual.