message() function
undefined
export namespace protobuf {
export function message<T>(): string;
}typia.protobuf.message() function returns a Protocol Buffer message (structure) as a string value.
With this message() function, you can share *.proto files with other languages. If you want to customize byte order or define specific type (that is not supported in the TypeScript) like uint32, use comment tags by following comment tags section.
TypeScript Source Code
import typia, { tags } from "typia";
typia.protobuf.message<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;
}Type Tags
By using type tags, you can use special numeric types that are not supported in the TypeScript.
Just import Type (or typia.tags.Type) type, and combine it with number or bigint type through intersection symbol number & typia.tagsType<"float"> case. If you want to declare an union numeric type, combine | and bracket (()) symbols properly like below.
When you take a mistake that choosing different target type, TypeScript compiler would block it with compilation error message. Therefore, have a confidence when using the Type tag. For such type safety reason, I recommend to use Type tag instead of using comment tags as much as possible.
number & (Type<"uint32"> | Type<"double">)numbertype can be bothuint32anddouble
(number & Type<"int32">) | (bigint & Type<"uint64">)numberisint32bigintisuint64
(number & (Type<"int32">)| Type<"float">) | (bigint & Type<"uint64">)numbercan be bothint32andfloatbigintisuint64
TypeScript Source Code
import typia, { tags } from "typia";
interface TypeTagExample {
// ATOMIC TYPES
int32: number & tags.Type<"int32">;
uint32: number & tags.Type<"uint32">;
uint64: bigint & tags.Type<"uint64">;
int64: number & tags.Type<"int64">;
float: number & tags.Type<"float">;
double: number | undefined;
string: string | null;
// UNION TYPES
uint32_or_double: number & (tags.Type<"uint32"> | tags.Type<"double">);
int32_or_uint64:
| (number & tags.Type<"int32">)
| (bigint & tags.Type<"uint64">);
int32_or_float_or_uint64:
| (number & (tags.Type<"int32"> | tags.Type<"float">))
| (bigint & tags.Type<"uint64">);
// ARRAY AND MAP
uint64_array: Array<bigint & tags.Type<"uint64">>;
int32_map?: Map<number & tags.Type<"int32">, string> | null;
}
//----
// PROTOBUF MESSAGE SCHEMA
//----
typia.protobuf.message<TypeTagExample>();
//----
// DECODE FUNCTION
//----
typia.protobuf.createDecode<TypeTagExample>();
//----
// ENCODE FUNCTION
//----
typia.protobuf.createEncode<TypeTagExample>();Comment Tags
By using @type {target} comment tag, you also can use special numeric types.
However, this way is not recommended, because it can’t perform union numeric types, and cannot be used in Array and Map types. When you declare @type int32 statement, target number type be fixed as int32 type, and never can have another numeric type by declaring union statements.
Also, those comment tags are not type safe. If you take a mistake when writing a comment tag, it will not be detected by the compiler, and will cause an error at runtime. For example, if you write a miss-spelled keyword like @type unit32, the target number type would be double type, and you can identify it just by running the program (or visiting playground website).
TypeScript Source Code
import typia from "typia";
export interface CommentTagExample {
/**
* @type int32
*/
int32: number;
/**
* @type uint32
*/
uint32?: number | null;
/**
* @type uint64
*/
uint64?: number;
/**
* @type int64
*/
int64: number;
/**
* @type float
*/
float: number | null;
double: number;
string: string;
}
//----
// PROTOBUF MESSAGE SCHEMA
//----
typia.protobuf.message<CommentTagExample>();
//----
// DECODE FUNCTION
//----
typia.protobuf.createDecode<CommentTagExample>();
//----
// ENCODE FUNCTION
//----
typia.protobuf.createEncode<CommentTagExample>();Restrictions
You know what? Expression power of Protocol Buffer is extremely narrower than type system of TypeScript. For example, Protocol Buffer can’t express complicate union type containing array. Also, Protocol Buffer can’t express multi dimensional array type, either.
In such reason, when converting TypeScript type to Protocol buffer message schema, lots of restrictions are exist. Let’s study which types of TypeScript are not supported in Protocol Buffer. For reference, if you try to call typia.protobuf.message<T>() function with unsupported type, typia will generate compile errors like below example cases.
At first, top level type must be a sole and static object.
If you try to use number or Array<T> type as a top level type, typia will generate compile error like below. Dynamic object types like Record<string, T>, or Map<string, T> types are not allowed either. For reference, the sole object means that, union of object types is not allowed, either.
TypeScript Source Code
import typia from "typia";
interface Cat {
type: "cat";
name: string;
ribbon: boolean;
}
interface Dog {
type: "dog";
name: string;
hunt: boolean;
}
typia.protobuf.message<bigint>();
typia.protobuf.createDecode<Record<string, number>>();
typia.protobuf.createDecode<Map<number & typia.tags.Type<"float">, Dog>>();
typia.protobuf.createEncode<boolean[]>();
typia.protobuf.createEncode<Cat | Dog>();At next, in Protocol Buffer, those types are categorized as container types.
Array<T>Map<Key, T>Record<string, T>(dynamic object)
Also, those container types does not allow over two-dimensional stacking. Therefore, it is not possible to declaring two dimensional array like number[][], or Array type in Map like Map<string, number[]>. Besides, value type of those container also do not support union type either.
Additionally, about Map<Key, T> type, key type must be an atomic type. It means that, only boolean, number, bigint and string types are allowed. Also, key type cannot be union type, either.
TypeScript Source Code
import typia from "typia";
interface IPointer<T> {
value: T;
}
interface Cat {
type: "cat";
name: string;
ribbon: boolean;
}
interface Dog {
type: "dog";
name: string;
hunt: boolean;
}
typia.protobuf.message<IPointer<number[][]>>();
typia.protobuf.createEncode<IPointer<Record<string, string[]>>>();
typia.protobuf.createDecode<IPointer<Map<string, Cat | Dog>>>();
typia.protobuf.message<IPointer<Map<Cat, string>>>();
typia.protobuf.message<IPointer<Map<number | string, Dog>>>();At last, those types are all not allowed.
anyfunctional typeSet<T>,WeakSet<T>andWeakMap<T>Date,Boolean,BigInt,Number,String- Binary classes except
Uint8ArrayUint8ClampedArray,Uint16Array,Uint32Array,BigUint64ArrayInt8Array,Int16Array,Int32Array,BigInt64ArrayArrayBuffer,SharedArrayBufferandDataView
TypeScript Source Code
import typia from "typia";
interface Something {
any: any;
unknown: unknown;
closure: () => void;
dict: Set<string> | WeakSet<Something> | WeakMap<Something, string>;
date: Date;
classic: String;
buffer: ArrayBuffer;
}
typia.protobuf.message<Something>();