Typelite: Why is Dictionary mapped to KeyValuePair and not to any or [index:string]:any

前端 未结 3 1234
遥遥无期
遥遥无期 2021-02-14 21:16

I have a class,

public class Instance : IResource
{
   public Dictionary Value { get; set; }

and it is mapped to

<
相关标签:
3条回答
  • 2021-02-14 21:46

    Collection types (any type implementing IEnumerable) is converted to arrays. Dictionary<> implements IEnumerable<KeyValuePair<>> and thus is converted to an array. The item-type is then expanded to its fully qualified name (FQN): System.Collections.Generic.KeyValuePair.

    Using Type Converters will let you change the type-name, but not the FQN. So it is only applicable to local types. In the case of dictionaries, you can't change the item type by inheritance.

    You could either create a new dictionary type, without inheriting from Dictionary<>. Another way around this problem, is to also use Type Formatters:

    ts.WithConvertor<Dictionary<string,object>>(t => {
        // Embed the real type in $
        // "System.Collections.Generic.${ [key: string]: any }$[]"
        return "${ [key: string]: any }$";
    });
    ts.WithFormatter((string memberTypeName, bool isMemberCollection) => {
        // Extract the content inside $
        string[] pieces = memberTypeName.Split('$');
        if (pieces.Length == 3) return pieces[1];
        // Default behaviour
        return memberTypeName + (isMemberCollection ? "[]" : "");
    });
    
    0 讨论(0)
  • 2021-02-14 21:49

    A quick and dirty workaround is to use a regex to alter output:

    Using

    <#= Regex.Replace( ts.Generate(TsGeneratorOutput.Properties)
            , @":\s*System\.Collections\.Generic\.KeyValuePair\<(?<k>[^\,]+),(?<v>[^\,]+)\>\[\];"
            , m=>": {[key: "+m.Groups["k"].Value+"]: "+m.Groups["v"].Value+"};"
            , RegexOptions.Multiline)
    #>
    

    Transforms a field

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

    to

        myField: {[key: string]: OtherClass};
    
    0 讨论(0)
  • 2021-02-14 21:56

    Here's a more generalized (and updated) solution that builds off of Markus Jarderot's answer:

    static void RegisterDictionaryMemberFormatter(this TsGenerator tsGenerator)
    {
        tsGenerator.SetMemberTypeFormatter((tsProperty, memberTypeName) => {
            var dictionaryInterface =
                tsProperty.PropertyType.Type.GetInterface(typeof(IDictionary<,>).Name) ??
                tsProperty.PropertyType.Type.GetInterface(typeof(IDictionary).Name);
    
            if (dictionaryInterface != null)
            {
                return tsGenerator.GetFullyQualifiedTypeName(new TsClass(dictionaryInterface));
            }
            else
            {
                return tsGenerator.DefaultMemberTypeFormatter(tsProperty, memberTypeName);
            }
        });
    }
    
    // and if you like the fluent syntax...
    static TypeScriptFluent WithDictionaryMemberFormatter(this TypeScriptFluent typeScriptFluent)
    {
        typeScriptFluent.ScriptGenerator.RegisterDictionaryMemberFormatter();
        return typeScriptFluent;
    }
    

    Use it like this:

    var ts = TypeLite.TypeScript.Definitions().For(typeof(SomeClass).Assembly);
    ts.ScriptGenerator.RegisterDictionaryMemberFormatter();
    
    // alternatively with fluent syntax:
    
    var ts = TypeLite.TypeScript.Definitions()
        .For(typeof(SomeClass).Assembly)
        .WithDictionaryMemberFormatter();
    

    N.B. this only fixes type signatures of properties (or fields) that have dictionary types. Also definitions for IDictionary are not automatically emitted, you'd have to add them manually:

    declare module System.Collections.Generic {
        interface IDictionary<TKey extends String, TValue> {
            [key: string]: TValue;
        }
    }
    declare module System.Collections {
        interface IDictionary {
            [key: string]: any;
        }
    }
    
    0 讨论(0)
提交回复
热议问题