Skip to content

A C# to TypeScript converter that focuses on ease of use and client side awesomeness.

License

Notifications You must be signed in to change notification settings

gkinsman/Typescriptr

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Typescriptr

appveyor tests nuget github license semantic-release

A C# to TypeScript converter that focuses on ease of use and client side awesomeness.

Install-Package Typescriptr

Yet another C# to TypeScript converter?!

There are a few options out there for generating TypeScript currently, and they might work for you! This library focuses on 'fitting in' with TypeScript to make the developer experience of using server types in TS projects awesome.

Specifically, it does these things:

  • It renders IDictionary<TKey,TValue> to { [key: TKey]: TValue } out of the box
  • It renders IEnumerable to T[] out of the box
  • Enum values are rendered as strings instead of numbers, and are generated separately from regular types.
  • Enum properties are rendered as string unions propName: 'EnumVal1' | 'EnumVal2' | 'EnumVal3';.
  • It is small and flexible: you can easily override the defaults below.

Getting Started

Install-Package Typescriptr

  1. Create a TypeScript generator with the default config:
var generator = TypeScriptGenerator.CreateDefault();
  1. Extract the types from your assemblies that you'd like to compile to TypeScript, and then pass them through the generator:
var typesToGenerate = 
  typeof(MyApiClient).Assembly.ExportedTypes
    .Where(type => typeof(ApiData).IsAssignableFrom(type));

var result = generator.Generate(typesToGenerate);
  1. Do whatever you like with the output Types and Enums. For example, write Types to a type definition file, and Enums to a regular TypeScript file:
using(var fs = File.Create("types.d.ts"))
using(var tw = new StreamWriter(fs)) {
	tw.Write(result.Types);
}

using(var fs = File.Create("enums.ts"))
using(var tw = new StreamWriter(fs)) {
	tw.Write(result.Enums);
}

Defaults

By default, Typescriptr will map most common BCL types, and the following complex types when created with the CreateDefault method:

public static TypeScriptGenerator CreateDefault() => new TypeScriptGenerator()
            .WithPropertyTypeFormatter<DateTimeOffset>(t => "string")
            .WithEnumFormatter(EnumFormatter.ValueNamedEnumFormatter, 
              EnumFormatter.UnionStringEnumPropertyTypeFormatter)
            .WithQuoteStyle(QuoteStyle.Single)
            .WithTypeMembers(MemberType.PropertiesOnly)
            .WithMemberFilter(() => true))
            .WithDictionaryPropertyFormatter(DictionaryPropertyFormatter.KeyValueFormatter)
            .WithCollectionPropertyFormatter(CollectionPropertyFormatter.Format)
            .WithCamelCasedPropertyNames()
	    .WithMemberFilter(() => true);

Enums

I'm a firm believer in using strings for Enums in APIs, as it makes them usable without needing to refer to documentation or code to understand the meaning of an enum's value. In .NET with JSON.NET, that means using the StringEnumConverter to convert Enums to strings.

In TypeScript, enums can be a bit of a pain to work when rendering .NET types into TS interfaces, as TS interfaces have no code output: they are just a way to provide intellisense and compilation errors over results returned from a server API. When writing code against those interfaces however, we also want to be able to use enums as values.

To that end, Typescriptr by default will render enum values as strings, and enum property types as string unions, and will provide them as separately rendered output in the generator result. This achieves the best of both worlds, as everything is just strings, yet still statically typed.

class TypeWithEnum
{
    public enum EnumType
    {
        FirstEnum,
        SecondEnum,
        ThirdEnum
    }
    public EnumType AnEnum { get; set; }
}

produces result.Types:

interface TypeWithEnum {
  anEnum: 'FirstEnum' | 'SecondEnum' | 'ThirdEnum';
}

and result.Enums:

enum EnumType {
  FirstEnum = 'FirstEnum',
  SecondEnum = 'SecondEnum',
  ThirdEnum = 'ThirdEnum',
}

Notes

  • It is possible to override this behaviour, and to provide custom formatters for enum properties and values. A numeric value formatter is built in, however there is no support for how to handle numeric values on properties, as referencing enums from a type definition file is error-prone.

Dictionaries

Any object properties assignable to IDictionary will be converted into TypeScript object syntax.

Notes

  • Keys must be resolvable to either TypeScript number or string (as those are the only allowed types for TS indexes). Custom type resolvers can be added using WithPropertyTypeFormatter.
  • Any type passed to the generator that is assignable to IDictionary will be rendered poorly, as it wouldn't make much sense. If you must, you can provide a custom type formatter using [WithPropertyTypeFormatter].(https://github.com/gkinsman/Typescriptr/blob/master/src/Typescriptr/TypeScriptGenerator.cs#L56)
  • Non-generic IDictionary isn't supported
class TypeWithDictionaryProp
{
    public Dictionary<string, int> DictProp { get; set; }
}

produces

interface TypeWithDictionaryProp {
  dictProp: { [key: string]: number };
}

Collections

Any object properties assignable to IEnumerable will be converted into TypeScript arrays.

class TypeWithArrayProp
{
    public string[] ArrayProp { get; set; }
}

produces:

interface TypeWithArrayProp {
  arrayProp: string[];
}

Notes

  • As with IDictionary types, it is recommended to not have API types inherit directly from the IEnumerable interface, but to have properties of type IEnumerable to avoid transpiling the properties of IEnumerable.

Member Filtering

Members that should not be emitted can be filtered using a Func<MemberInfo, bool> passed into WithMemberFilter when creating the typescript generator. For example, you might want to filter out properties using an IgnoreAttribute:

var generator = TypeScriptGenerator
                    .CreateDefault()
                    .WithMemberFilter(memberInfo => memberInfo.GetCustomAttribute<IgnoreAttribute>() == null)

Nullable Value Types

Nullable value types are rendered to properties with a null union in TypeScript:

class TypeWithNullable
{
    public int? NullableInt { get; set; }
    public Guid? NullableGuid { get; set; }
}
interface TypeWithNullable {
  nullableInt: number | null;
  nullableGuid: string | null;
}

Inheritance

Types that inherit from other types will be rendered with TypeScript extends:

class BaseClass
{
    public string Property { get; set; }
}

class TypeWithBaseClass : BaseClass
{           
}
interface TypeWithBaseClass extends BaseClass {
}
interface BaseClass {
  property: string;
}

Generics

Types with both open and closed generic types will be rendered to TypeScript generics:

class Pie<Apple> { }
class Apple { }
interface Pie<Apple> {
}
interface Apple {
}