daniellittledev / Typescriptr

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

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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)
            .WithDictionaryPropertyFormatter(DictionaryPropertyFormatter.KeyValueFormatter)
            .WithCollectionPropertyFormatter(CollectionPropertyFormatter.Format)
            .WithNamespace("Api")
            .WithCamelCasedPropertyNames();

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:

declare namespace Api {
  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

declare namespace Api {
  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:

declare namespace Api {
  interface TypeWithArrayProp {
    arrayProp: string[];
  }
}

Notes

  • As with IDictionary types, it is recommended to not have API types inherit directly from the interface, but to have properties of the type, else the expected output doesn't make much sense.

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; }
}
declare namespace Api {
  interface TypeWithNullable {
    nullableInt: number | null;
    nullableGuid: string | null;
  }
}

Limitations

Inheritance

Inheritance isn't directly supported by Typescriptr, in that types passed into the generator that have base clases will not, in the output, also derive from that base class. Instead, base types that aren't object or ValueType will be rendered with all of their respective publicly available properties. The resulting effect is the same as with inheritance, but with slightly larger code output, and simpler generator code.

Generics

Top level types with type parameters are not supported at all. Properties with generic type parameters may be mapped with custom property type formatters, but a non-generic base type is required to match the type on. This scenario is fairly obscure and untested, as generics are unusual for DTO's.

About

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

License:MIT License


Languages

Language:C# 95.3%Language:JavaScript 3.8%Language:PowerShell 0.9%