C# → TypeScript converter

Paste your C# DTOs — classes, records, structs, interfaces and enums — and get the TypeScript interfaces that describe the JSON System.Text.Json actually writes for them: camelCase names, T | null where the C# is nullable, arrays for every collection, Record<string, T> for dictionaries, strings for dates and GUIDs, and a note for every mapping. Nothing is guessed: a long gets a precision warning, a type you did not paste is flagged, a method is listed as skipped.

Your code is parsed on the server and forgotten. Nothing is stored, nothing is logged and nothing is compiled or executed: the converter reads the syntax tree of the text you paste, maps the types by name and discards the input.

Options go on the first line of the input as a comment, for example // ts: shape=type naming=asIs nullable=optional enum=string. The defaults are what ASP.NET Core sends with JsonSerializerDefaults.Web: interface, camelCase names, nullable members as T | null, enums as numbers, export on every type, dates as string. nullable=optional writes name?: T instead, nullable=both writes name?: T | null, enum=string writes a union of the member names, fields=include adds public fields for code that sets IncludeFields, and dates=Date emits Date with a warning, because the JSON still carries strings.

Free API — use it from your own tools

The same engine powers an open API (this page uses it too). No signup, no key — just a rate limit of 60 requests per minute per IP. Handy for a build step that keeps the front end's types in line with the API's DTOs.

curl

curl -X POST https://www.coder000.com/api/v1/csharp-to-typescript \
  -H "Content-Type: application/json" \
  -d '{"input": "public class Patient { public int Id { get; set; } public string? Email { get; set; } }"}'

C#

using var http = new HttpClient();
var source = await File.ReadAllTextAsync("Contracts/Patient.cs");
var response = await http.PostAsJsonAsync(
    "https://www.coder000.com/api/v1/csharp-to-typescript",
    new { input = source });
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
await File.WriteAllTextAsync("ClientApp/src/patient.ts", result.GetProperty("output").GetString());

Uniform response shape: { "output": "…", "notes": [...], "warnings": [...] } — output is the TypeScript source, and each note carries a change, an explanation, and where there is one a link to the post behind the decision. Send any number of type declarations per request, with or without namespaces and usings, up to 100,000 characters.

How it works, and what it cannot do

No AI and no compiler run. Roslyn, the C# parser that Visual Studio and the dotnet CLI use, turns the text into a syntax tree; a fixed mapping table turns each declaration into TypeScript. The output was checked with the TypeScript compiler, and the interfaces were checked against the JSON System.Text.Json really writes for the same classes, before this page went live. The rules:

  • Types by name, not by meaning. Because nothing is compiled, a type is mapped by how it is written: int, decimal and double become number; long becomes number with a warning, because JavaScript numbers lose precision above 253; string, char, Guid, Uri, DateTime, DateTimeOffset, DateOnly, TimeOnly and TimeSpan become string, which is what the serializer writes; byte[] is a base64 string; object, dynamic and JsonElement become unknown.
  • Collections and dictionaries. Arrays, List<T>, IEnumerable<T>, HashSet<T> and the other collection interfaces all become T[], because JSON has one array shape. Dictionary<K, V> becomes Record<string, V>, because JSON object keys are always strings, whatever K was.
  • Nullable and required. int? and string? become T | null, because the serializer writes null rather than dropping the property. A member with [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] becomes optional (name?:). required changes nothing on the wire: it is a rule for reading, and a null required value is still left out under that condition, so the note says so. { get; } members are readonly.
  • Attributes the serializer honours, honoured here. [JsonPropertyName] (and Newtonsoft's [JsonProperty]) renames, [JsonIgnore] removes, [JsonInclude] adds a non-public member, [JsonExtensionData] becomes an index signature, [JsonStringEnumConverter] turns an enum into a union of strings, [JsonPolymorphic] and [JsonDerivedType] add the type discriminator to the base interface and turn a property typed as the base into a union of the derived types, [Obsolete] becomes @deprecated, and a /// <summary> becomes a JSDoc comment.
  • Enums are numbers unless you say otherwise. That is the System.Text.Json default, and the most common surprise in a front end. With a string converter on the enum or on the property, or enum=string, the output is "Pending" | "Paid" | … instead. A [Flags] enum is typed Enum | number, because a combined value such as 5 has no name.
  • Inheritance and references. class Dog : Animal becomes interface Dog extends Animal when Animal is in the input. A base type or a property type you did not paste gets an empty placeholder interface at the end of the output and a warning, so the file still type-checks; paste every type the DTOs mention to get the real shape.
  • What it skips. Methods, constructors, operators, indexers, events, static and constant members, non-public members: none of them are JSON, so none of them appear, and each type's note says how many were left out. Public fields are left out too, because the serializer ignores fields unless IncludeFields is on; the fields=include directive turns them on. Value tuples become unknown with a warning, because System.Text.Json writes {} for them, and so do nint, Task<T> and other types the serializer refuses. Options you set with JsonSerializerOptions in Program.cs are invisible to the converter; tell it with the // ts: line instead.
  • Not a compiler. Code that would not compile still converts as far as it parses; the parser's complaints come back as warnings with line numbers. Generic types keep their type parameters. Nested types are flattened to their own name. Input nested deeper than 256 brackets is refused before parsing, because the parser itself cannot take it. TypeScript → C# is not offered yet.

Learn the why, not just the what

The mapping table, the five traps and the proof behind every rule on this page.