Tapper
A Tool Transpiling C# Type into TypeScript Type. (Support JSON & MessagePack Serialization)
Install / Use
/learn @nenoNaninu/TapperREADME
Tapper
Tapper is a library/CLI tool to transpile C# type (class, struct, record, enum) into TypeScript type (type, enum). Using this tool can reduce serialization bugs (type mismatch, typos, etc.) and make TypeScript code easily follow changes in C# code.

Table of Contents
Packages
- Tapper.Attributes
- Tapper.Analyzer
- Tapper.Generator (.NET Tool)
Install Using .NET Tool
Use Tapper.Generator(CLI Tool) to generate TypeScript type from C# type.
Tapper.Generator can be easily installed using .NET Global Tools.
You can use the installed tools with the command tapper.
# install
# Tapper CLI (dotnet tool) requires .NET 7 or .NET 8, but your app TFM can use .NET 6, etc.
$ dotnet tool install --global Tapper.Generator
$ tapper help
# update
$ dotnet tool update --global Tapper.Generator
Getting Started
First, add the following packages to your project. Tapper.Analyzer is optional, but recommended.
$ dotnet add package Tapper.Attributes
$ dotnet add package Tapper.Analyzer (optional, but recommended.)
Next, apply the [TranspilationSource] Attribute to the C# type definition.
using Tapper;
namespace SampleNamespace;
[TranspilationSource] // <- Add attribute!
public class SampleType
{
public List<int>? List { get; }
public int Value { get; }
public Guid Id { get; }
public DateTime DateTime { get; }
}
Then execute the command as follows.
$ tapper --project path/to/XXX.csproj --output outdir
TypeScript source code is generated in the directory specified by --output.
In this example, TypeScript source code named outdir/SampleNamespace.ts is generated.
The contents of the generated code is as follows.
/* eslint-disable */
/** Transpiled from SampleNamespace.SampleType */
export type SampleType = {
/** Transpiled from System.Collections.Generic.List<int>? */
list?: number[];
/** Transpiled from int */
value: number;
/** Transpiled from System.Guid */
id: string;
/** Transpiled from System.DateTime */
dateTime: (Date | string);
}
Transpilation Rules
Tapper transpile C# types (class, struct, record, enum) to TypeScript types (type, enum).
When transpiling class, struct, and record, only public fields and properties are transpiled.
Built-in Supported Types
| C# | TypeScript | Description |
| ---- | ---- | ---- |
| bool | boolean |
| byte | number |
| sbyte | number |
| char | string or number | JSON: string, MessagePack number. |
| decimal | number |
| double | number |
| float | number |
| int | number |
| uint | number |
| long | number |
| ulong | number |
| short | number |
| ushort | number |
| object | any |
| string | string |
| Uri | string |
| Guid | string | Compatible with TypeScript's crypto.randomUUID(). |
| DateTime | (Date \| string) or Date | Json: (Date \| string), MessagePack: Date. |
| DateTimeOffset | (Date \| string) or [Date, number] | Json: (Date \| string), MessagePack: [Date, number]. note #41 |
| TimeSpan | string or number | Json: string, MessagePack: number. |
| System.Nullable<T>| (T | undefined) |
| byte[] | string or Uint8Array | JSON: string (base64), MessagePack Uint8Array. |
| T[] | T[] |
| System.Array | any[] | ❌ System.Text.Json |
| ArraySegment<T> | T[] | ❌ System.Text.Json |
| List<T> | T[] |
| LinkedList<T> | T[] |
| Queue<T> | T[] |
| Stack<T> | T[] |
| HashSet<T> | T[] |
| IEnumerable<T> | T[] |
| IReadOnlyCollection<T> | T[] |
| ICollection<T> | T[] |
| IList<T> | T[] |
| ISet<T> | T[] |
| Dictionary<TKey, TValue> | Partial<Record<TKey, TValue>> |
| IDictionary<TKey, TValue> | Partial<Record<TKey, TValue>> |
| IReadOnlyDictionary<TKey, TValue> | Partial<Record<TKey, TValue>> |
| Tuple | [T1, T2, ...] | ❌ System.Text.Json |
C# Namespace
C# namespace is mapped to the filename of the generated TypeScript code.
namespace SampleNamespace;
[TranspilationSource]
record Xxx();
For example, given the above C# code, TypeScript code with the file name SampleNamespace.ts is generated.
Nesting Types
It doesn't matter if the user-defined types are nested.
For example, consider the following C# code.
Apply [TranspilationSource] Attribute to all types to be transpiled.
If you add an analyzer package, you can avoid forgetting to apply [TranspilationSource].
#nullable enable
using System.Text.Json.Serialization;
using Tapper;
namespace Space1
{
[TranspilationSource]
public class CustomType1
{
public int Value;
public Guid Id;
[JsonIgnore]
public string Foo;
}
namespace Sub
{
[TranspilationSource]
public enum MyEnum
{
Zero = 0,
One = 1,
Two = 1 << 1,
Four = 1 << 2,
}
}
}
namespace Space2
{
[TranspilationSource]
public record CustomType3(float Value, DateTime ReleaseDate);
}
namespace Space3
{
using Space1;
using Space1.Sub;
using Space2;
[TranspilationSource]
public class NastingNamespaceType
{
public CustomType1? Value { get; set; }
public MyEnum MyEnumValue { get; set; }
[JsonPropertyName("list")]
public List<CustomType3> MyList { get; set; } = new();
}
}
The following TypeScript code is generated.
- Space1.ts
/** Transpiled from Space1.CustomType1 */
export type CustomType1 = {
/** Transpiled from int */
value: number;
/** Transpiled from System.Guid */
id: string;
}
- Space1.Sub.ts
/** Transpiled from Space1.Sub.MyEnum */
export enum MyEnum {
Zero = 0,
One = 1,
Two = 2,
Four = 4,
}
- Space2.ts
/** Transpiled from Space2.CustomType3 */
export type CustomType3 = {
/** Transpiled from float */
value: number;
/** Transpiled from System.DateTime */
name: (Date | string);
}
- Space3.ts
import { CustomType1 } from './Space1';
import { MyEnum } from './Space1.Sub';
import { CustomType3 } from './Space2';
/** Transpiled from Space3.NastingNamespaceType */
export type NastingNamespaceType = {
/** Transpiled from Space1.CustomType1? */
value?: CustomType1;
/** Transpiled from Space1.Sub.MyEnum */
myEnumValue: MyEnum;
/** Transpiled from System.Collections.Generic.List<Space2.CustomType3> */
list: CustomType3[];
}
Options
Naming Style
You can select camelCase, PascalCase, or none for the property name of the generated TypeScript type.
For none, the property name in C# is used.
The default is the standard naming style for TypeScript.
$ tapper --project path/to/Xxx.csproj --output outdir --naming-style camelCase
Enum Style
There are options for enum transpiling.
You can select Value (default), Name, NameCamel, NamePascal, Union, UnionCamel, or UnionPascal.
If you use this option, be careful with the serializer options.
For example, System.Text.Json serializes an enum as a integer by default (not string).
To serialize an enum as a string, you must pass JsonStringEnumConverter as an option to JsonSerializer.
$ tapper --project path/to/Xxx.csproj --output outdir --enum value
$ tapper --project path/to/Xxx.csproj --output outdir --enum name
$ tapper --project path/to/Xxx.csproj --output outdir --enum union
// C# source
[TranspilationSource]
public enum MyEnum
{
Zero = 0,
One = 1,
Two = 1 << 1,
Four = 1 << 2,
}
// Generated TypeScript
// --enum value (default)
export enum MyEnum {
Zero = 0,
One = 1,
Two = 2,
Four = 4,
}
// --enum name
export enum MyEnum {
Zero = "Zero",
One = "One",
Two = "Two",
Four = "Four",
}
// --enum union
export type MyEnum = "Zero" | "One" | "Two" | "Four";
// --enum unionCamel
export type MyEnum = "zero" | "one" | "two" | "four";
Serializer
The TypeScript code generated by Tapper is supposed to be serialized/deserialized with json or MessagePack.
And the appropriate type is slightly different depending on the serializer.
You can specify which one to use by passing the --serializer option.
The default is json.
$ tapper --project path/to/Xxx.csproj --output outdir --serializer MessagePack --naming-style none
Also, it is supposed that the following serializers are used.
-
Json
- C# : System.Text.Json
- TypeScript : JSON.stringify()
-
MessagePack
- C# : MessagePack-CSharp
- TypeScript : [msgpack-javascript](https://github.com/m
