Error in Generic Generation with TypeFormatter

Create issue
Issue #88 new
Brandon Bell created an issue

If I add the suggested formatter to add "I" to my types:

".WithTypeFormatter((type, F) => "I" + ((TypeLite.TsModels.TsClass)type).Name);"

Then, a class with a simple generic:

    public class UserPreference

        public ModuleType ModuleType { get; set; }

        public Dictionary<string, string> Preferences { get; set; }

Gets the incorrect output:

declare module App.Platform {
    interface IUserPreference {
        ModuleType: App.Platform.ModuleType;
        Preferences: System.Collections.Generic.IKeyValuePair[];

declare module System.Collections.Generic {
    interface IKeyValuePair {
        Key: ITKey;
        Value: ITValue;

I would expect the dictionary to be defined as:

declare module System.Collections.Generic {
    interface IKeyValuePair<TKey, TValue> {
        Key: TKey;
        Value: TValue;

And the reference to it to be:

Preferences: System.Collections.Generic.KeyValuePair<string, string>[];

Note: I do get this without the TypeFormatter. It just seems like the type formatter is identifying the template parameters incorrectly.

Comments (8)

  1. Lukas Kabrt repo owner

    .WithTypeFormatter((type, F) => "I" + ((TypeLite.TsModels.TsClass)type).Name) adds 'I' in front of all types so IKeyValuePair is correct, but it shouldn't probably be applied to open generics parameters TKey and TValue. It will be fixed in future.

  2. Tyson Benson

    I got around this. The documentation for customisation leads to you replacing the formatter which writes the generics, so you need to either copy the base TsClass implementation in your own formatter, or take a reference to it and call it yourself.

        var ts = TypeScript.Definitions().For(Assembly.LoadFrom(dll));
        var baseClassFormatter = ts.ScriptGenerator.Formaters[typeof(TsClass)];
        ts = ts.WithTypeFormatter((type, formatter) => {
                var format = baseClassFormatter(type, formatter);
                var isOpenGenericTypeArg = type.Type.IsGenericParameter && type.Type.FullName == null && type.Type.Name.StartsWith("T");
                if (isOpenGenericTypeArg) //e.g. IDictionary<TKey, TResult>
                    return format;
                return "I" + format;
            .WithMemberFormatter((identifier) => CamelCase(identifier.Name))
  3. Brandon Bell reporter

    Thanks Tyson. This workaround works great. But for completeness, you also need to import TsModels for this to work:

    <#@ import namespace="TypeLite.TsModels" #>
  4. Murali

    I could not able to make it with the given solution. Here is my C# Dictionary class

       public class ComponentMap
            public Dictionary<int, List<int>> MappingData { get; set; }
            public List<int> Numberss { get; set; }    

    The generated TypeScript looks like

        interface IComponentMap {
            MappingData: System.Collections.Generic.IKeyValuePair<System.IInt32,System.Collections.Generic.IList`1>[];
            Numberss: number[];

    with the error Cannot find namespace 'System'

    My expectation was

    MappingData: System.Collections.Generic.IKeyValuePair<number,number[]>;

    TypeScript Generator Class

    private static void GenerateTypeScriptContracts(string assemblyFile, string outputPath)
                var assembly = Assembly.LoadFrom(assemblyFile);
                // If you want a subset of classes from this assembly, filter them here
                var models = assembly.GetTypes();
                var ts = TypeScript.Definitions().For(assembly);
                TsTypeFormatter baseClassFormatter = ts.ScriptGenerator.Formaters[typeof(TsClass)];
                var generator = new TypeScriptFluent()
                    .WithConvertor<Guid>(c => "string")
                    .WithTypeFormatter((type, f) => GenerateName(type, baseClassFormatter, f));
                    //.WithMemberFormatter((identifier) => CamelCase(identifier.Name));
                foreach (var model in models)
                //Generate enums
                string fileNamePrefix = assembly.FullName.Split(',').First().ToLowerInvariant();
                var enumDefinitions = generator.Generate(TsGeneratorOutput.Enums);
                File.WriteAllText(Path.Combine(outputPath, fileNamePrefix + ".enums.ts"), enumDefinitions);
                //Generate interface definitions for all classes
                var classDefinitions = generator.Generate(TsGeneratorOutput.Properties | TsGeneratorOutput.Fields);
                File.WriteAllText(Path.Combine(outputPath, fileNamePrefix + ".classes.d.ts"), classDefinitions);
            private static string CamelCase(string name)
                return char.ToLower(name[0]) + name.Substring(1);
            // FIX:
            private static string GenerateName(TsType type, TsTypeFormatter classFormatter, ITsTypeFormatter formatter)
                TsClass @class = (TsClass)type;
                string format;
                // NOTE: Modification to fix to handle interface on generic parameters.
                if (@class.GenericArguments.Any(x => !x.Type.IsGenericParameter))
                    string genericArgs = string.Join(",", @class.GenericArguments.Select(x => FormatGenericParameter(x.Type)));
                    format = $"{@class.Name}<{genericArgs}>";
                    format = classFormatter(type, formatter);
                var isOpenGenericTypeArg = type.Type.IsGenericParameter && type.Type.FullName == null && type.Type.Name.StartsWith("T");
                if (isOpenGenericTypeArg) 
                    //e.g. IDictionary<TKey, TResult>
                    return format;
                return "I" + format;
            private static string FormatGenericParameter(Type type)
                return type.IsGenericParameter ? type.Namespace + "." + type.Name : type.Namespace + ".I" + type.Name;
  5. Log in to comment