đź“– Guide Documents
Protocol Buffer
Message Schema

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.

ICustomer.ts
import typia, { tags } from "typia";
 
interface ICustomer {
  id: number & tags.Type<"int32">;
  email: string & tags.Format<"email">;
  name: string;
  pet: null | ICat | IDog;
  memo: null | Map<string, string>;
  logins: Array<ICustomerLogin>;
}
interface ICat {
  type: "cat";
  name: string;
  ribbon: boolean;
}
interface IDog {
  type: "dog";
  name: string;
  hunt: boolean;
}
interface ICustomerLogin {
  success: boolean;
  href: string & tags.Format<"url">;
  referrer: string & tags.Format<"url">;
  ip: string & (tags.Format<"ipv4"> | tags.Format<"ipv6">);
  time: string & tags.Format<"date-time">;
}
 
typia.protobuf.message<ICustomer>();
ICustomer.proto
syntax = "proto3";
 
message ICustomer {
  required int32 id = 1;
  required string email = 2;
  required string name = 3;
  oneof pet {
    ICat v4 = 4;
    IDog v5 = 5;
  }
  map<string, string> memo = 6;
  repeated ICustomerLogin logins = 7;
}
 
message ICat {
  required string type = 1;
  required string name = 2;
  required bool ribbon = 3;
}
 
message IDog {
  required string type = 1;
  required string name = 2;
  required bool hunt = 3;
}
 
message ICustomerLogin {
  required bool success = 1;
  required string href = 2;
  required string referrer = 3;
  required string ip = 4;
  required string time = 5;
}

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 both uint32 and double
  • (number & Type<"int32">) | (bigint & Type<"uint64">)
    • number is int32
    • bigint is uint64
  • (number & (Type<"int32">)| Type<"float">) | (bigint & Type<"uint64">)
    • number can be both int32 and float
    • bigint is uint64
TypeTagExample.ts
import typia, { tags } from "typia";
 
export 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 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).


CommentTagExample.ts
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> and WeakMap<T>
  • Date, Boolean, BigInt, Number, String
  • Binary classes except Uint8Array
    • Uint8ClampedArray, Uint16Array, Uint32Array, BigUint64Array
    • Int8Array, Int16Array, Int32Array, BigInt64Array
    • ArrayBuffer, SharedArrayBuffer and DataView
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>();