Skip to content

Product message JSON V1

Since: 0.2.0

This document owns the optional, language-neutral product wire format. It is independent of the Conformance transport and of GESB. Encoding and decoding are explicit API operations; local Receive, Emit and Publish continue to forward immutable values directly without JSON, external projection or constructor calls.

Envelope and canonical writing

A message document contains exactly version: 1 and message. A value document contains exactly version: 1 and value. A message object contains exactly name, tags and args. Arguments are an ordered array of objects containing name and value; _ denotes an unlabeled argument. Labels and their order are part of the signature. Named labels must be unique; _ may repeat. Tags use bare lowercase tag names, normalized by the ordinary message API.

{"message":{"args":[{"name":"value","value":{"type":"Number","unit":"","value":"100.25"}}],"name":"Done","tags":["ready"]},"version":1}

Writers use compact JSON, scalar-ordinal sorted object properties, and retain array order. They escape quote and backslash with a backslash and U+0000–001F with lowercase four-digit \u escapes; other Unicode scalars remain literal. JSON strings use JSON escaping, not GES’s doubled-delimiter convention. Readers accept JSON whitespace, equivalent JSON escapes and arbitrary object property order. Duplicate object properties and unknown/missing properties are errors. No prefix, trailing content, comments or malformed surrogate escape is accepted. JSON numeric tokens in this schema are signed decimal integers; only the envelope version uses one. GES numeric payloads are strings, avoiding precision loss through an intermediary JSON implementation.

Value objects

Every value object contains type and exactly the fields listed below. There are no public Integer/Binary64 value tags.

type Additional fields
Nothing none
Number value: numeric Text; unit: "", "s", "m" or "°"
Percentage value: numeric Text containing the ratio, e.g. "0.1" for 10%
Boolean value: JSON Boolean
Text value: JSON string
Tag value: bare valid tag name
List items: ordered value objects
Map entries: ordered {key: Text, value: value-object} objects
Record name: PascalCase type name; entries: Map-style entries
Vector, Point x, y, z: numeric Text; unit: the Number unit strings
Dice rolls: positive Int32 decimal numeric strings, descending on output
Range from, to, step: unitless numeric Text
Series kind: "fibonacci" or "factorial"; offset: nonnegative Int64 numeric Text
Handler name: message name; labels: ordered argument labels
Message value: message object

Numeric decoding and normalization follow Numbers. Number payloads do not embed units or percent suffixes. NaN is not a Number value; its canonical value representation is Nothing. Number infinities use Infinity and -Infinity. Percentage and Range components must be finite. Map/Record writers order entries by Unicode scalar key order; duplicate decoded keys are rejected. Scalar-distinct Unicode keys remain distinct.

A Record contains immutable typed field data. Decoding never resolves a Record definition and never invokes its constructor. External values are exported as Records using their declared type name and readable field snapshot, recursively. There is no External wire type and decoding does not recreate native identity. Getter failures propagate to the encoding caller. Series transport supports only the two built-in data series and their offsets.

Ranges remain lazy. Integral representable endpoints and steps normalize to exact Int64 ranges as defined by Language; other finite ranges use the existing Binary64 term rule. Transport does not materialize terms.

Limits and failures

Both implementations enforce at most 64 data nesting levels (the root is level zero), 65,536 visited data values/message objects, and 4,194,304 UTF-16 code units of JSON. Syntax parsing additionally limits JSON nesting to 256 and visited JSON values to 524,288. Cyclic external graphs terminate with a resource-limit error. No partially decoded message is returned or enqueued. Limits do not consume a Host’s opcode budget; the codec operates independently of a Host.

Stable failure codes are message.invalidJson, message.invalidValue, message.unsupportedVersion, message.unsupportedSeries, and message.resourceLimit. In C# they are carried by GameEventScriptMessageFormatException.Code; in Swift by GameEventScriptMessageFormatError.code.