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.
- Create a TypeScript generator with the default config:
var generator = TypeScriptGenerator.CreateDefault();
- 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);
- 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);
}
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);
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',
}
- 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.
Any object properties assignable to IDictionary will be converted into TypeScript object syntax.
- Keys must be resolvable to either TypeScript
number
orstring
(as those are the only allowed types for TS indexes). Custom type resolvers can be added usingWithPropertyTypeFormatter
. - 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 };
}
Any object properties assignable to IEnumerable will be converted into TypeScript arrays.
class TypeWithArrayProp
{
public string[] ArrayProp { get; set; }
}
produces:
interface TypeWithArrayProp {
arrayProp: string[];
}
- 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.
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 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;
}
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;
}
Types with both open and closed generic types will be rendered to TypeScript generics:
class Pie<Apple> { }
class Apple { }
interface Pie<Apple> {
}
interface Apple {
}