message()
function
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.
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 compliation 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">)
number
type can be bothuint32
anddouble
(number & Type<"int32">) | (bigint & Type<"uint64">)
number
isint32
bigint
isuint64
(number & (Type<"int32">)| Type<"float">) | (bigint & Type<"uint64">)
number
can be bothint32
andfloat
bigint
isuint64
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>();
By the way, how to specify the byte order of each properties?
Even though typia
automatically assigns byte order to each properties, but in sometimes, you may hope to determine the byte orders by yourself.
In that case, you can specify the byte order of each properties by utilizing the tags.Sequence<N>
type like below. If you have to define some union typed properties, combine the tags.Sequence<N>
type with intersection symbol &
to the target type.
import typia, { tags } from "typia";
interface TypeTagExample {
// ATOMIC TYPES
int32: number & tags.Type<"int32"> & tags.Sequence<10>;
uint32: number & tags.Type<"uint32"> & tags.Sequence<20>;
uint64: bigint & tags.Type<"uint64"> & tags.Sequence<30>;
int64: number & tags.Type<"int64"> & tags.Sequence<40>;
float: number & tags.Type<"float"> & tags.Sequence<50>;
double: (number & tags.Sequence<60>) | undefined;
string: (string & tags.Sequence<70>) | null;
// UNION TYPES
uint32_or_double: number &
(
| (tags.Type<"uint32"> & tags.Sequence<61>)
| (tags.Type<"double"> & tags.Sequence<62>)
);
int32_or_uint64:
| (number & tags.Type<"int32"> & tags.Sequence<71>)
| (bigint & tags.Type<"uint64"> & tags.Sequence<72>);
int32_or_float_or_uint64:
| (number &
(
| (tags.Type<"int32"> & tags.Sequence<81>)
| (tags.Type<"float"> & tags.Sequence<82>)
))
| (bigint & tags.Type<"uint64"> & tags.Sequence<83>);
// ARRAY AND MAP
uint64_array: Array<bigint & tags.Type<"uint64">> & tags.Sequence<90>;
int32_map?:
| (Map<number & tags.Type<"int32">, string> & tags.Sequence<100>)
| 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 mis-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).
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 TyeScript 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.
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.
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.
any
functional type
Set<T>
,WeakSet<T>
andWeakMap<T>
Date
,Boolean
,BigInt
,Number
,String
- Binary classes except
Uint8Array
Uint8ClampedArray
,Uint16Array
,Uint32Array
,BigUint64Array
Int8Array
,Int16Array
,Int32Array
,BigInt64Array
ArrayBuffer
,SharedArrayBuffer
andDataView
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>();