Declare and initialize a Dictionary in Typescript

前端 未结 6 1047
闹比i
闹比i 2020-12-02 04:22

Given the following code

interface IPerson {
   firstName: string;
   lastName: string;
}

var persons: { [id: string]: IPerson; } = {
   \"p1\": { firstName         


        
相关标签:
6条回答
  • 2020-12-02 04:41

    For using dictionary object in typescript you can use interface as below:

    interface Dictionary<T> {
        [Key: string]: T;
    }
    

    and, use this for your class property type.

    export class SearchParameters {
        SearchFor: Dictionary<string> = {};
    }
    

    to use and initialize this class,

    getUsers(): Observable<any> {
            var searchParams = new SearchParameters();
            searchParams.SearchFor['userId'] = '1';
            searchParams.SearchFor['userName'] = 'xyz';
    
            return this.http.post(searchParams, 'users/search')
                .map(res => {
                    return res;
                })
                .catch(this.handleError.bind(this));
        }
    
    0 讨论(0)
  • 2020-12-02 04:41

    Typescript fails in your case because it expects all the fields to be present. Use Record and Partial utility types to solve it.

    Record<string, Partial<IPerson>>

    interface IPerson {
       firstName: string;
       lastName: string;
    }
    
    var persons: Record<string, Partial<IPerson>> = {
       "p1": { firstName: "F1", lastName: "L1" },
       "p2": { firstName: "F2" }
    };
    

    Explanation.

    1. Record type creates a dictionary/hashmap.
    2. Partial type says some of the fields may be missing.

    Alternate.

    If you wish to make last name optional you can append a ? Typescript will know that it's optional.

    lastName?: string;
    

    https://www.typescriptlang.org/docs/handbook/utility-types.html

    0 讨论(0)
  • 2020-12-02 04:42

    Here is a more general Dictionary implementation inspired by this from @dmck

        interface IDictionary<T> {
          add(key: string, value: T): void;
          remove(key: string): void;
          containsKey(key: string): boolean;
          keys(): string[];
          values(): T[];
        }
    
        class Dictionary<T> implements IDictionary<T> {
    
          _keys: string[] = [];
          _values: T[] = [];
    
          constructor(init?: { key: string; value: T; }[]) {
            if (init) {
              for (var x = 0; x < init.length; x++) {
                this[init[x].key] = init[x].value;
                this._keys.push(init[x].key);
                this._values.push(init[x].value);
              }
            }
          }
    
          add(key: string, value: T) {
            this[key] = value;
            this._keys.push(key);
            this._values.push(value);
          }
    
          remove(key: string) {
            var index = this._keys.indexOf(key, 0);
            this._keys.splice(index, 1);
            this._values.splice(index, 1);
    
            delete this[key];
          }
    
          keys(): string[] {
            return this._keys;
          }
    
          values(): T[] {
            return this._values;
          }
    
          containsKey(key: string) {
            if (typeof this[key] === "undefined") {
              return false;
            }
    
            return true;
          }
    
          toLookup(): IDictionary<T> {
            return this;
          }
        }
    
    0 讨论(0)
  • 2020-12-02 04:45

    If you want to ignore a property, mark it as optional by adding a question mark:

    interface IPerson {
        firstName: string;
        lastName?: string;
    }
    
    0 讨论(0)
  • 2020-12-02 04:46

    I agree with thomaux that the initialization type checking error is a TypeScript bug. However, I still wanted to find a way to declare and initialize a Dictionary in a single statement with correct type checking. This implementation is longer, however it adds additional functionality such as a containsKey(key: string) and remove(key: string) method. I suspect that this could be simplified once generics are available in the 0.9 release.

    First we declare the base Dictionary class and Interface. The interface is required for the indexer because classes cannot implement them.

    interface IDictionary {
        add(key: string, value: any): void;
        remove(key: string): void;
        containsKey(key: string): bool;
        keys(): string[];
        values(): any[];
    }
    
    class Dictionary {
    
        _keys: string[] = new string[];
        _values: any[] = new any[];
    
        constructor(init: { key: string; value: any; }[]) {
    
            for (var x = 0; x < init.length; x++) {
                this[init[x].key] = init[x].value;
                this._keys.push(init[x].key);
                this._values.push(init[x].value);
            }
        }
    
        add(key: string, value: any) {
            this[key] = value;
            this._keys.push(key);
            this._values.push(value);
        }
    
        remove(key: string) {
            var index = this._keys.indexOf(key, 0);
            this._keys.splice(index, 1);
            this._values.splice(index, 1);
    
            delete this[key];
        }
    
        keys(): string[] {
            return this._keys;
        }
    
        values(): any[] {
            return this._values;
        }
    
        containsKey(key: string) {
            if (typeof this[key] === "undefined") {
                return false;
            }
    
            return true;
        }
    
        toLookup(): IDictionary {
            return this;
        }
    }
    

    Now we declare the Person specific type and Dictionary/Dictionary interface. In the PersonDictionary note how we override values() and toLookup() to return the correct types.

    interface IPerson {
        firstName: string;
        lastName: string;
    }
    
    interface IPersonDictionary extends IDictionary {
        [index: string]: IPerson;
        values(): IPerson[];
    }
    
    class PersonDictionary extends Dictionary {
        constructor(init: { key: string; value: IPerson; }[]) {
            super(init);
        }
    
        values(): IPerson[]{
            return this._values;
        }
    
        toLookup(): IPersonDictionary {
            return this;
        }
    }
    

    And here is a simple initialization and usage example:

    var persons = new PersonDictionary([
        { key: "p1", value: { firstName: "F1", lastName: "L2" } },
        { key: "p2", value: { firstName: "F2", lastName: "L2" } },
        { key: "p3", value: { firstName: "F3", lastName: "L3" } }
    ]).toLookup();
    
    
    alert(persons["p1"].firstName + " " + persons["p1"].lastName);
    // alert: F1 L2
    
    persons.remove("p2");
    
    if (!persons.containsKey("p2")) {
        alert("Key no longer exists");
        // alert: Key no longer exists
    }
    
    alert(persons.keys().join(", "));
    // alert: p1, p3
    
    0 讨论(0)
  • 2020-12-02 05:01

    Edit: This has since been fixed in the latest TS versions. Quoting @Simon_Weaver's comment on the OP's post:

    Note: this has since been fixed (not sure which exact TS version). I get these errors in VS, as you would expect: Index signatures are incompatible. Type '{ firstName: string; }' is not assignable to type 'IPerson'. Property 'lastName' is missing in type '{ firstName: string; }'.


    Apparently this doesn't work when passing the initial data at declaration. I guess this is a bug in TypeScript, so you should raise one at the project site.

    You can make use of the typed dictionary by splitting your example up in declaration and initialization, like:

    var persons: { [id: string] : IPerson; } = {};
    persons["p1"] = { firstName: "F1", lastName: "L1" };
    persons["p2"] = { firstName: "F2" }; // will result in an error
    
    0 讨论(0)
提交回复
热议问题