typia.protobuf.*Decode โ Protocol Buffer bytes โ TypeScript object
namespace protobuf {
function decode<T>(buffer: Uint8Array): Resolved<T>;
function isDecode<T>(buffer: Uint8Array): Resolved<T> | null;
function assertDecode<T>(buffer: Uint8Array): Resolved<T>;
function validateDecode<T>(buffer: Uint8Array): IValidation<Resolved<T>>;
}The decoder reads T at compile time and emits a hand-rolled binary reader for that shape โ same model as the encoder, in reverse.
First example
import typia from "typia";
interface User {
id: string;
age: number;
hobbies: string[];
}
const bytes: Uint8Array = await fetchBytesSomehow();
const user = typia.protobuf.assertDecode<User>(bytes);
// โ User, with type tag constraints validated. Throws on mismatch.TypeScript Source
import typia, { tags } from "typia";
const member: IMember = typia.random<IMember>();
const encoded: Uint8Array = typia.protobuf.encode<IMember>(member);
const decoded: IMember = typia.protobuf.decode<IMember>(encoded);
console.log(member, decoded);
interface IMember {
id:
| (string & tags.Sequence<11>)
| (number & tags.Type<"uint64"> & tags.Sequence<12>)
| (Uint8Array & tags.Sequence<13>);
name: (string & tags.Sequence<20>) | null;
children: Array<IMember> & tags.Sequence<30>;
keywords: Map<string, string> & tags.Sequence<40>;
thumbnail:
| (string & tags.Format<"uri"> & tags.ContentMediaType<"image/*">)
| Uint8Array;
email: string & tags.Format<"email">;
hobbies: Array<IHobby>;
}
interface IHobby {
id: string & tags.Format<"uuid">;
name: string;
valid: boolean;
}Compiled JavaScript
import typia from "typia";
import * as _ProtobufReader_1 from "typia/lib/internal/_ProtobufReader";
import * as _ProtobufSizer_1 from "typia/lib/internal/_ProtobufSizer";
import * as _ProtobufWriter_1 from "typia/lib/internal/_ProtobufWriter";
import * as _isFormatEmail_1 from "typia/lib/internal/_isFormatEmail";
import * as _isFormatUri_1 from "typia/lib/internal/_isFormatUri";
import * as _isFormatUuid_1 from "typia/lib/internal/_isFormatUuid";
import * as _isTypeUint64_1 from "typia/lib/internal/_isTypeUint64";
import * as _randomArray_1 from "typia/lib/internal/_randomArray";
import * as _randomBoolean_1 from "typia/lib/internal/_randomBoolean";
import * as _randomFormatEmail_1 from "typia/lib/internal/_randomFormatEmail";
import * as _randomFormatUri_1 from "typia/lib/internal/_randomFormatUri";
import * as _randomFormatUuid_1 from "typia/lib/internal/_randomFormatUuid";
import * as _randomInteger_1 from "typia/lib/internal/_randomInteger";
import * as _randomPick_1 from "typia/lib/internal/_randomPick";
import * as _randomString_1 from "typia/lib/internal/_randomString";
import * as _throwTypeGuardError_1 from "typia/lib/internal/_throwTypeGuardError";
const member = (() => {
const _ro0 = (_recursive = true, _depth = 0) => ({
id: (_generator?.pick ?? _randomPick_1._randomPick).call(_generator, [
() =>
(_generator?.string ?? _randomString_1._randomString)({
type: "string",
"x-protobuf-sequence": 11,
}),
() =>
(_generator?.integer ?? _randomInteger_1._randomInteger)({
type: "integer",
minimum: 0,
"x-protobuf-sequence": 12,
}),
() =>
new Uint8Array(
(_generator?.array ?? _randomArray_1._randomArray)({
type: "array",
element: () =>
(_generator?.integer ?? _randomInteger_1._randomInteger)({
type: "integer",
minimum: 0,
maximum: 255,
}),
}),
),
])(),
name: (_generator?.pick ?? _randomPick_1._randomPick).call(_generator, [
() => null,
() =>
(_generator?.string ?? _randomString_1._randomString)({
type: "string",
"x-protobuf-sequence": 20,
}),
])(),
children:
5 >= _depth
? (_generator?.array ?? _randomArray_1._randomArray)({
type: "array",
"x-protobuf-sequence": 30,
recursive: true,
element: () => _ro0(true, _recursive ? 1 + _depth : _depth),
})
: [],
keywords: new Map(
(_generator?.array ?? _randomArray_1._randomArray)({
type: "array",
element: () => [
(_generator?.string ?? _randomString_1._randomString)({
type: "string",
}),
(_generator?.string ?? _randomString_1._randomString)({
type: "string",
}),
],
}),
),
thumbnail: (_generator?.pick ?? _randomPick_1._randomPick).call(
_generator,
[
() => (_generator?.uri ?? _randomFormatUri_1._randomFormatUri)(),
() =>
new Uint8Array(
(_generator?.array ?? _randomArray_1._randomArray)({
type: "array",
element: () =>
(_generator?.integer ?? _randomInteger_1._randomInteger)({
type: "integer",
minimum: 0,
maximum: 255,
}),
}),
),
],
)(),
email: (_generator?.email ?? _randomFormatEmail_1._randomFormatEmail)(),
hobbies: (_generator?.array ?? _randomArray_1._randomArray)({
type: "array",
element: () => _ro1(true, _recursive ? 1 + _depth : _depth),
}),
});
const _ro1 = (_recursive = false, _depth = 0) => ({
id: (_generator?.uuid ?? _randomFormatUuid_1._randomFormatUuid)(),
name: (_generator?.string ?? _randomString_1._randomString)({
type: "string",
}),
valid: (_generator?.boolean ?? _randomBoolean_1._randomBoolean)(),
});
let _generator;
return (generator_1) => {
_generator = generator_1;
return _ro0();
};
})()();
const encoded = (() => {
const encoder = (writer, input) => {
const _vctx = {};
const _peo0 = (input) => {
if ((_vctx.peo0 || (_vctx.peo0 = new WeakSet())).has(input))
_throwTypeGuardError_1._throwTypeGuardError({
method: "typia.protobuf.encode",
expected: "non-circular reference",
value: input,
});
_vctx.peo0.add(input);
// property "id": ((Uint8Array & Sequence<13>) | (number & Type<"uint64"> & Sequence<12>) | (string & Sequence<11>));
if (input.id instanceof Uint8Array) {
writer.uint32(106);
writer.bytes(input.id);
} else if ("number" === typeof input.id) {
writer.uint32(96);
writer.uint64(input.id);
} else if ("string" === typeof input.id) {
writer.uint32(90);
writer.string(input.id);
} else
_throwTypeGuardError_1._throwTypeGuardError({
method: "typia.protobuf.encode",
expected:
'((Uint8Array & Sequence<13>) | (number & Type<"uint64"> & Sequence<12>) | (string & Sequence<11>))',
value: input.id,
});
// property "name": ((string & Sequence<20>) | null);
if (null !== input.name) {
writer.uint32(162);
writer.string(input.name);
}
// property "children": (Array<IMember> & Sequence<30>);
if (0 !== input.children.length) {
for (const elem of input.children) {
writer.uint32(242);
writer.fork();
_peo0(elem);
writer.ldelim();
}
}
// property "keywords": Map<string, string>;
for (const [key, value] of input.keywords) {
writer.uint32(322);
writer.fork();
writer.uint32(10);
writer.string(key);
writer.uint32(18);
writer.string(value);
writer.ldelim();
}
// property "thumbnail": ((string & Format<"uri"> & ContentMediaType<"image/*">) | Uint8Array);
if (input.thumbnail instanceof Uint8Array) {
writer.uint32(330);
writer.bytes(input.thumbnail);
} else if ("string" === typeof input.thumbnail) {
writer.uint32(338);
writer.string(input.thumbnail);
} else
_throwTypeGuardError_1._throwTypeGuardError({
method: "typia.protobuf.encode",
expected:
'((string & Format<"uri"> & ContentMediaType<"image/*">) | Uint8Array)',
value: input.thumbnail,
});
// property "email": (string & Format<"email">);
writer.uint32(346);
writer.string(input.email);
// property "hobbies": Array<IHobby>;
if (0 !== input.hobbies.length) {
for (const elem of input.hobbies) {
writer.uint32(354);
writer.fork();
_peo1(elem);
writer.ldelim();
}
}
_vctx.peo0.delete(input);
};
const _peo1 = (input) => {
// property "id": (string & Format<"uuid">);
writer.uint32(10);
writer.string(input.id);
// property "name": string;
writer.uint32(18);
writer.string(input.name);
// property "valid": boolean;
writer.uint32(24);
writer.bool(input.valid);
};
const _io0 = (input, _vctx = {}) =>
(_vctx.io0 || (_vctx.io0 = new WeakSet())).has(input)
? true
: (_vctx.io0.add(input),
(null !== input.id &&
undefined !== input.id &&
("string" === typeof input.id ||
("number" === typeof input.id &&
_isTypeUint64_1._isTypeUint64(input.id)) ||
input.id instanceof Uint8Array) &&
(null === input.name || "string" === typeof input.name) &&
Array.isArray(input.children) &&
input.children.every(
(elem) =>
"object" === typeof elem && null !== elem && _io0(elem, _vctx),
) &&
input.keywords instanceof Map &&
(() =>
[...input.keywords].every(
(elem) =>
Array.isArray(elem) &&
elem.length === 2 &&
"string" === typeof elem[0] &&
"string" === typeof elem[1],
))() &&
null !== input.thumbnail &&
undefined !== input.thumbnail &&
(("string" === typeof input.thumbnail &&
_isFormatUri_1._isFormatUri(input.thumbnail)) ||
input.thumbnail instanceof Uint8Array) &&
"string" === typeof input.email &&
_isFormatEmail_1._isFormatEmail(input.email) &&
Array.isArray(input.hobbies) &&
input.hobbies.every(
(elem) =>
"object" === typeof elem && null !== elem && _io1(elem, _vctx),
)) ||
(_vctx.io0.delete(input), false));
const _io1 = (input, _vctx = {}) =>
"string" === typeof input.id &&
_isFormatUuid_1._isFormatUuid(input.id) &&
"string" === typeof input.name &&
"boolean" === typeof input.valid;
_peo0(input);
return writer;
};
return (input) => {
const sizer = encoder(new _ProtobufSizer_1._ProtobufSizer(), input);
const writer = encoder(new _ProtobufWriter_1._ProtobufWriter(sizer), input);
return writer.buffer();
};
})()(member);
const decoded = (() => {
const _pdo0 = (reader, previous = -1) => {
const output = {
id: new Uint8Array([]),
name: null,
children: [],
keywords: new Map(),
thumbnail: new Uint8Array([]),
email: "",
hobbies: [],
};
while (reader.index() < reader.size()) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 13:
// bytes;
output.id = reader.bytes();
break;
case 12:
// uint64;
output.id = Number(reader.uint64());
break;
case 11:
// string;
output.id = reader.string();
break;
case 20:
// string;
output.name = reader.string();
break;
case 30:
// Array<IMember>;
output.children.push(_pdo0(reader, reader.fork()));
break;
case 40:
// Map<string, string>;
(() => {
output.keywords ??= new Map();
const piece = reader.fork();
const entry = {
key: "",
value: "",
};
while (reader.index() < reader.size()) {
const kind = reader.uint32();
switch (kind >>> 3) {
case 1:
// string;
entry.key = reader.string();
break;
case 2:
// string;
entry.value = reader.string();
break;
default:
reader.skipType(kind & 7);
break;
}
}
reader.close(piece);
output.keywords.set(entry.key, entry.value);
})();
break;
case 41:
// bytes;
output.thumbnail = reader.bytes();
break;
case 42:
// string;
output.thumbnail = reader.string();
break;
case 43:
// string;
output.email = reader.string();
break;
case 44:
// Array<IHobby>;
output.hobbies.push(_pdo1(reader, reader.fork()));
break;
default:
reader.skipType(tag & 7);
break;
}
}
if (-1 < previous) reader.close(previous);
return output;
};
const _pdo1 = (reader, previous = -1) => {
const output = {
id: "",
name: "",
valid: false,
};
while (reader.index() < reader.size()) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
// string;
output.id = reader.string();
break;
case 2:
// string;
output.name = reader.string();
break;
case 3:
// bool;
output.valid = reader.bool();
break;
default:
reader.skipType(tag & 7);
break;
}
}
if (-1 < previous) reader.close(previous);
return output;
};
return (input) => {
const reader = new _ProtobufReader_1._ProtobufReader(input);
return _pdo0(reader);
};
})()(encoded);
console.log(member, decoded);Variants
| Function | Validates result? | On failure |
|---|---|---|
protobuf.decode<T> | No | output is whatever fell out of the binary reader |
protobuf.isDecode<T> | Yes (is) | returns null |
protobuf.assertDecode<T> | Yes (assert) | throws TypeGuardError |
protobuf.validateDecode<T> | Yes (validate) | returns IValidation<Resolved<T>> |
What the *Decode validators actually verify.
They check custom tag constraints โ tags.Format<"uuid">, tags.Minimum<0>, etc. They do not re-verify that the bytes were structurally valid Protocol Buffer for T โ the decoder already did that decoding work. So you can rely on these to catch โthe producer encoded a UUID thatโs actually not a UUIDโ, but not to tell you that someone fed you random bytes that happen to parse.
The safe pattern is: producer uses protobuf.assertEncode (or validateEncode), consumer uses protobuf.assertDecode (or validateDecode). Then both ends of the wire are checked.
undefined
export namespace protobuf {
export function decode<T>(buffer: Uint8Array): Resolved<T>;
export function isDecode<T>(buffer: Uint8Array): Resolved<T> | null;
export function assertDecode<T>(buffer: Uint8Array): Resolved<T>;
export function validateDecode<T>(
buffer: Uint8Array,
): IValidation<Resolved<T>>;
}undefined
/**
* Validation result type with detailed error information.
*
* `IValidation<T>` is the return type of `typia.validate<T>()` and related
* validation functions. Unlike `typia.is<T>()` which returns a boolean, or
* `typia.assert<T>()` which throws exceptions, `typia.validate<T>()` returns
* this structured result with full error details.
*
* Check the {@link IValidation.success | success} discriminator:
*
* - `true` โ {@link IValidation.ISuccess} with validated
* {@link IValidation.ISuccess.data | data}
* - `false` โ {@link IValidation.IFailure} with
* {@link IValidation.IFailure.errors | errors} array
*
* This is the recommended validation function when you need to report
* validation errors to users or log them for debugging.
*
* @author Jeongho Nam - https://github.com/samchon
*
* @example
* const result = typia.validate<User>(input);
* if (result.success) {
* return result.data; // User type
* } else {
* result.errors.forEach((e) => console.log(e.path, e.expected));
* }
*
* @template T The expected type after successful validation
*
* @evidence contracts/common.md#principled-implementation A union of a success variant with data typed as T and a failure variant with unknown data and an error list, discriminated by `success`; failure data is unknown because it did not match.
* @evidence contracts/common.md#clear-and-simple-design One alias with its variants and the error record in the namespace.
* @evidence contracts/common.md#prohibited-implementation-shortcuts It reports validation as data and does not throw.
* @evidence contracts/common.md#meaningful-documentation The comment compares it with `is` and `assert`, explains the discriminator and shows an example.
*/
export type IValidation<T = unknown> =
| IValidation.ISuccess<T>
| IValidation.IFailure;
export namespace IValidation {
/**
* Successful validation result.
*
* Indicates the input matches the expected type. The validated data is
* available in {@link data} with full type information.
*
* @template T The validated type
*
* @evidence contracts/common.md#principled-implementation The literal true and the data typed T make a successful validation narrow to T.
* @evidence contracts/common.md#clear-and-simple-design Two fields.
* @evidence contracts/common.md#prohibited-implementation-shortcuts A plain data record.
* @evidence contracts/common.md#meaningful-documentation The comment says the data is the original input.
*/
export interface ISuccess<T = unknown> {
/**
* Success discriminator.
*
* Always `true` for successful validations. Use this to narrow the type
* before accessing {@link data}.
*/
success: true;
/**
* The validated data with correct type.
*
* The original input after successful validation. TypeScript will narrow
* this to type `T` when {@link success} is `true`.
*/
data: T;
}
/**
* Failed validation result with error details.
*
* Indicates the input did not match the expected type. Contains the original
* data and an array of all validation errors found.
*
* @evidence contracts/common.md#principled-implementation The literal false, the original input as unknown and an array of errors express a failed validation without claiming anything about the data's type.
* @evidence contracts/common.md#clear-and-simple-design Three fields.
* @evidence contracts/common.md#prohibited-implementation-shortcuts A plain data record.
* @evidence contracts/common.md#meaningful-documentation The comment says it carries the original data and every error found.
*/
export interface IFailure {
/**
* Success discriminator.
*
* Always `false` for failed validations. Use this to narrow the type before
* accessing {@link errors}.
*/
success: false;
/**
* The original input that failed validation.
*
* Preserved as `unknown` type since it didn't match the expected type.
* Useful for debugging or logging the actual value.
*/
data: unknown;
/**
* Array of validation errors.
*
* Contains one entry for each validation failure found. Multiple errors may
* exist if the input has multiple type mismatches.
*/
errors: IError[];
}
/**
* Detailed information about a single validation error.
*
* Describes exactly what went wrong during validation, including the
* location, expected type, and actual value.
*
* @evidence contracts/common.md#principled-implementation Each error records the path from `$input`, the expected type expression, the actual value and an optional description, which locates and explains one failure.
* @evidence contracts/common.md#clear-and-simple-design Four fields with the description optional.
* @evidence contracts/common.md#prohibited-implementation-shortcuts A plain data record.
* @evidence contracts/common.md#meaningful-documentation Every field has a comment with examples of path and expected formats.
*/
export interface IError {
/**
* Property path to the error location.
*
* A dot-notation path from the root input to the failing property. Uses
* `$input` as the root. Example: `"$input.user.email"` or
* `"$input.items[0].price"`.
*/
path: string;
/**
* Expected type expression.
*
* A human-readable description of what type was expected at this location.
* Examples: `"string"`, `"number & ExclusiveMinimum<0>"`, `"(\"active\" |
* \"inactive\")"`.
*/
expected: string;
/**
* The actual value that failed validation.
*
* The value found at the error path. May be `undefined` if the property was
* missing. Useful for debugging type mismatches.
*/
value: unknown;
/**
* Human-readable error description.
*
* Optional additional context about the validation failure, such as
* constraint violations or custom error messages.
*/
description?: string;
}
}undefined
/**
* Error thrown when type assertion fails.
*
* Thrown by {@link assert}, {@link assertGuard}, and other assert-family
* functions when input doesn't match expected type `T`. Contains detailed
* information about the first assertion failure:
*
* - `method`: Which typia function threw (e.g., `"typia.assert"`)
* - `path`: Property path where error occurred (e.g., `"input.user.age"`)
* - `expected`: Expected type string (e.g., `"number & ExclusiveMinimum<19>"`)
* - `value`: Actual value that failed validation
*
* @template T Expected type (for type safety)
*/
export class TypeGuardError<T = any> extends Error {
/**
* Name of the typia method that threw this error.
*
* E.g., `"typia.assert"`, `"typia.assertEquals"`, `"typia.assertGuard"`.
*/
public readonly method: string;
/**
* Property path where assertion failed.
*
* Uses dot notation for nested properties. Generated assertions start at
* `$input`; callers constructing the error may omit the path.
*
* E.g., `"input.age"`, `"input.profile.email"`, `"input[0].name"`.
*/
public readonly path: string | undefined;
/**
* String representation of expected type.
*
* E.g., `"string"`, `"number & ExclusiveMinimum<19>"`, `"{ name: string; age:
* number }"`.
*/
public readonly expected: string;
/**
* Actual value that failed assertion.
*
* The raw value at the error path, useful for debugging.
*/
public readonly value: unknown;
/**
* Optional human-readable error description.
*
* Primarily for AI agent libraries or custom validation scenarios needing
* additional context. Standard assertions rely on `path`, `expected`, and
* `value` for error reporting.
*/
public readonly description?: string | undefined;
/**
* Phantom property for TypeScript type safety.
*
* Not used at runtimeโexists only to preserve generic type `T` in the type
* system. Always `undefined`.
*
* @internal
*/
protected readonly fake_expected_typed_value_?: T | undefined;
/**
* Creates a new TypeGuardError instance.
*
* @param props Error properties
*/
public constructor(props: TypeGuardError.IProps) {
// MESSAGE CONSTRUCTION
// Use custom message if provided, otherwise generate default format
super(
props.message ||
`Error on ${props.method}(): invalid type${
props.path ? ` on ${props.path}` : ""
}, expect to be ${props.expected}`,
);
// INHERITANCE POLYFILL
// Set up prototype for compatibility across different JavaScript environments
const proto = new.target.prototype;
if (Object.setPrototypeOf) Object.setPrototypeOf(this, proto);
else (this as any).__proto__ = proto;
// ASSIGN MEMBERS
this.name = "TypeGuardError";
this.method = props.method;
this.path = props.path;
this.expected = props.expected;
this.value = props.value;
if (props.description || props.value === undefined)
this.description =
props.description ??
[
"The value at this path is `undefined`.",
"",
`Please fill the \`${props.expected}\` typed value next time.`,
].join("\n");
}
}
/**
* Properties of {@link TypeGuardError}.
*
* @evidence contracts/common.md#principled-implementation The error extends Error and carries the failing method, optional path, expected type text and actual value as readonly fields. A nonempty message overrides the generated text. A nonempty description is retained; when the value is undefined, a supplied description (including an empty one) is retained or a missing-value instruction is generated. The prototype assignment preserves the subclass prototype when a toolchain downlevels `extends Error`.
* @evidence contracts/common.md#clear-and-simple-design One class with a namespace holding its constructor properties; the generic `T` is carried by a protected phantom member that is marked internal and never assigned.
* @evidence contracts/common.md#prohibited-implementation-shortcuts The phantom member is a type-level device. The constructor changes only its own error instance's prototype to new.target.prototype for downlevel Error subclass compatibility; it does not replace a foreign method, global or prototype.
* @evidence contracts/common.md#meaningful-documentation The class comment lists the carried fields with examples, each field has its own comment and the phantom and constructor are explained.
*/
export namespace TypeGuardError {
/**
* Properties for constructing a TypeGuardError.
*
* @evidence contracts/common.md#principled-implementation The record lists exactly what the constructor reads: method, expected and value are required, while path, description and message are optional. A nonempty message overrides the generated text; absence or an empty message uses the default.
* @evidence contracts/common.md#clear-and-simple-design A flat record in the class namespace used only by the constructor and the factories.
* @evidence contracts/common.md#prohibited-implementation-shortcuts A data record.
* @evidence contracts/common.md#meaningful-documentation Each property has a comment with examples and the meaning of absence.
*/
export interface IProps {
/**
* Name of the typia method that threw the error.
*
* E.g., `"typia.assert"`, `"typia.assertEquals"`.
*/
method: string;
/**
* Property path where assertion failed (optional).
*
* E.g., `"input.age"`, `"input.profile.email"`.
*/
path?: undefined | string;
/**
* String representation of expected type.
*
* E.g., `"string"`, `"number & ExclusiveMinimum<19>"`.
*/
expected: string;
/** Actual value that failed assertion. */
value: unknown;
/**
* Optional human-readable error description.
*
* For AI agent libraries or custom validation needing additional context.
*/
description?: string;
/**
* Custom error message (optional).
*
* If omitted or empty, a default message is generated from other
* properties.
*/
message?: undefined | string;
}
}undefined
import { Equal } from "./internal/Equal";
import { IsTupleLike } from "./internal/IsTupleLike";
import { NativeClass } from "./internal/NativeClass";
import { ValueOf } from "./internal/ValueOf";
/**
* Converts a type to its resolved form by mapping callable values to never.
*
* `Resolved<T>` transforms classes to plain objects, extracts primitive values
* from boxed types (Booleanโboolean, Numberโnumber, Stringโstring), and
* recursively processes nested public properties. Arrays, tuples, Set and Map
* retain their container shape while their contents are resolved recursively,
* including readonly containers. Date and other supported native classes pass
* through unchanged; WeakSet and WeakMap become `never`.
*
* @author Jeongho Nam - https://github.com/samchon
* @author Kyungsu Kang - https://github.com/kakasoo
*
* @template T Target type to resolve
*
* @evidence contracts/common.md#principled-implementation Conditional types unwrap boxed primitives, map callable values to never and recursively resolve public properties and array, tuple, Set and Map contents. Supported native classes pass through after the container branches; weak collections become never. Broad unknown/object inputs remain unchanged. TupleStack preserves a revisited tuple-rest identity rather than imposing a depth limit, and arrays bypass the Equal comparison to avoid eager recursive-alias evaluation.
* @evidence contracts/common.md#clear-and-simple-design A thin alias chooses between the original and the resolved form and delegates structure-specific work to ResolvedMain, ResolvedObject and ResolvedArray.
* @evidence contracts/common.md#prohibited-implementation-shortcuts It is a structural mapping over the type with no consumer names, casts or runtime code.
* @evidence contracts/common.md#meaningful-documentation The comment states what is resolved and which categories are preserved; recursion and tuple guards are explained in line comments at their private declarations.
*/
export type Resolved<T> = unknown extends T
? T
: object extends T
? T
: T extends readonly unknown[]
? ResolvedMain<T> // avoid eagerly comparing recursive tuple rest aliases
: Equal<T, ResolvedMain<T>> extends true
? T
: ResolvedMain<T>;
// TupleStack closes recursive tuple rest cycles without limiting other nesting.
type ResolvedMain<T, TupleStack = never> = T extends [never]
? never // (special trick for jsonable | null) type
: ValueOf<T> extends boolean | number | bigint | string
? ValueOf<T>
: T extends Function
? never
: T extends object
? ResolvedObject<T, TupleStack>
: ValueOf<T>;
type ResolvedObject<T extends object, TupleStack> =
T extends Array<infer U>
? IsTupleLike<T> extends true
? T extends TupleStack
? T
: ResolvedArray<T, TupleStack | T>
: Array<ResolvedMain<U, TupleStack>>
: T extends ReadonlyArray<infer U>
? IsTupleLike<T> extends true
? T extends TupleStack
? T
: ResolvedArray<T, TupleStack | T>
: ReadonlyArray<ResolvedMain<U, TupleStack>>
: T extends Set<infer U>
? Set<ResolvedMain<U, TupleStack>>
: T extends Map<infer K, infer V>
? Map<ResolvedMain<K, TupleStack>, ResolvedMain<V, TupleStack>>
: T extends ReadonlyMap<infer K, infer V>
? ReadonlyMap<
ResolvedMain<K, TupleStack>,
ResolvedMain<V, TupleStack>
>
: T extends ReadonlySet<infer U>
? ReadonlySet<ResolvedMain<U, TupleStack>>
: T extends WeakSet<any> | WeakMap<any, any>
? never
: T extends NativeClass
? T
: {
[P in keyof T]: ResolvedMain<T[P], TupleStack>;
};
type ResolvedArray<T extends readonly unknown[], TupleStack> = {
[P in keyof T]: ResolvedMain<T[P], TupleStack>;
};Wire errors
The table above describes what happens when validation fails. A malformed payload is different: the binary reader raises an Error while reading the bytes, before any validator runs. That error propagates out of every variant, including isDecode and validateDecode, so it is not reported as a null or as an unsuccessful IValidation.
Wrap the call in try/catch when the bytes come from an untrusted producer.
try {
const user = typia.protobuf.assertDecode<User>(bytes);
} catch (error) {
// malformed wire bytes, or a failed tag constraint
}string fields are one source of these errors. proto3 requires a string to hold valid UTF-8, so typia rejects a payload containing a lone continuation byte, an overlong encoding, a truncated multi-byte sequence, an encoded surrogate, or an out-of-range code point, instead of substituting U+FFFD replacement characters and returning text the producer never sent.
Use Uint8Array when a field must carry arbitrary octets. It maps to proto3 bytes, which has no encoding requirement and accepts the same octets a string field rejects.
Reusable factories
const decodeUser = typia.protobuf.createAssertDecode<User>();TypeScript Source
import typia, { tags } from "typia";
export const decode = typia.protobuf.createDecode<IMember>();
interface IMember {
id:
| (string & tags.Sequence<11>)
| (number & tags.Type<"uint64"> & tags.Sequence<12>)
| (Uint8Array & tags.Sequence<13>);
name: (string & tags.Sequence<20>) | null;
children: Array<IMember> & tags.Sequence<30>;
keywords: Map<string, string> & tags.Sequence<40>;
thumbnail:
| (string & tags.Format<"uri"> & tags.ContentMediaType<"image/*">)
| Uint8Array;
email: string & tags.Format<"email">;
hobbies: Array<IHobby>;
}
interface IHobby {
id: string & tags.Format<"uuid">;
name: string;
valid: boolean;
}Compiled JavaScript
import typia from "typia";
import * as _ProtobufReader_1 from "typia/lib/internal/_ProtobufReader";
export const decode = (() => {
const _pdo0 = (reader, previous = -1) => {
const output = {
id: new Uint8Array([]),
name: null,
children: [],
keywords: new Map(),
thumbnail: new Uint8Array([]),
email: "",
hobbies: [],
};
while (reader.index() < reader.size()) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 13:
// bytes;
output.id = reader.bytes();
break;
case 12:
// uint64;
output.id = Number(reader.uint64());
break;
case 11:
// string;
output.id = reader.string();
break;
case 20:
// string;
output.name = reader.string();
break;
case 30:
// Array<IMember>;
output.children.push(_pdo0(reader, reader.fork()));
break;
case 40:
// Map<string, string>;
(() => {
output.keywords ??= new Map();
const piece = reader.fork();
const entry = {
key: "",
value: "",
};
while (reader.index() < reader.size()) {
const kind = reader.uint32();
switch (kind >>> 3) {
case 1:
// string;
entry.key = reader.string();
break;
case 2:
// string;
entry.value = reader.string();
break;
default:
reader.skipType(kind & 7);
break;
}
}
reader.close(piece);
output.keywords.set(entry.key, entry.value);
})();
break;
case 41:
// bytes;
output.thumbnail = reader.bytes();
break;
case 42:
// string;
output.thumbnail = reader.string();
break;
case 43:
// string;
output.email = reader.string();
break;
case 44:
// Array<IHobby>;
output.hobbies.push(_pdo1(reader, reader.fork()));
break;
default:
reader.skipType(tag & 7);
break;
}
}
if (-1 < previous) reader.close(previous);
return output;
};
const _pdo1 = (reader, previous = -1) => {
const output = {
id: "",
name: "",
valid: false,
};
while (reader.index() < reader.size()) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
// string;
output.id = reader.string();
break;
case 2:
// string;
output.name = reader.string();
break;
case 3:
// bool;
output.valid = reader.bool();
break;
default:
reader.skipType(tag & 7);
break;
}
}
if (-1 < previous) reader.close(previous);
return output;
};
return (input) => {
const reader = new _ProtobufReader_1._ProtobufReader(input);
return _pdo0(reader);
};
})();Restrictions
Same as the rest of the protobuf surface โ see protobuf.message.
Where to go next
- Encoding values โ
protobuf.encode - Sharing the schema with other services โ
protobuf.message - Numeric and string tags โ Special Tags