Typelite: Why is Dictionary<string, object> mapped to KeyValuePair and not to any or [index:string]:any
Asked Answered
S

3

9

I have a class,

public class Instance : IResource
{
   public Dictionary<string, object> Value { get; set; }

and it is mapped to

interface Instance {
   Value: System.Collections.Generic.KeyValuePair[];

interface KeyValuePair {
   Key: any;
   Value: any;
}

I would have expected

interface Instance {
   Value: any;

or

interface Instance {
   Value: {[index:string]:any};

How can I change the generation?

Also, how can I skip the name space in the generation?

Sudden answered 25/1, 2014 at 12:26 Comment(1)
I tried ".WithConvertor<Dictionary<string,object>>(t => "any")", but the result is "Value: System.Collections.Generic.any[];" Not what I expectedSudden
H
9

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};
Halfslip answered 5/3, 2015 at 6:20 Comment(1)
Awesome! Don't forget this <#@ import namespace="System.Text.RegularExpressions" #>Quentin
R
4

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 ? "[]" : "");
});
Rifleman answered 31/3, 2014 at 10:7 Comment(1)
Note that TypeScriptFluent.WithFormatter() overloads are currently marked [Obsolete].Founder
F
3

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;
    }
}
Founder answered 30/6, 2016 at 19:53 Comment(2)
This is awesome, but why would you choose to emit IDictionary at all when you could emit { [key: string]: string } right in the formatter?Grandparent
Thanks. This is by far the best and cleanest answer.Claud

© 2022 - 2024 — McMap. All rights reserved.