Deklarieren und initialisieren Sie ein Wörterbuch in Typescript

247

Gegeben den folgenden Code

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

var persons: { [id: string]: IPerson; } = {
   "p1": { firstName: "F1", lastName: "L1" },
   "p2": { firstName: "F2" }
};

Warum wird die Initialisierung nicht abgelehnt? Schließlich hat das zweite Objekt nicht die Eigenschaft "lastName".

mgs
quelle
11
Hinweis: Dies wurde inzwischen behoben (nicht sicher, welche genaue TS-Version). Ich bekomme diese Fehler in VS, wie Sie erwarten würden: Index signatures are incompatible. Type '{ firstName: string; }' is not assignable to type 'IPerson'. Property 'lastName' is missing in type '{ firstName: string; }'.
Simon_Weaver

Antworten:

289

Bearbeiten : Dies wurde seitdem in den neuesten TS-Versionen behoben. Zitat von @ Simon_Weavers Kommentar zum Beitrag des OP:

Hinweis: Dies wurde inzwischen behoben (nicht sicher, welche genaue TS-Version). Ich erhalte diese Fehler in VS, wie Sie es erwarten würden:Index signatures are incompatible. Type '{ firstName: string; }' is not assignable to type 'IPerson'. Property 'lastName' is missing in type '{ firstName: string; }'.


Anscheinend funktioniert dies nicht, wenn die ursprünglichen Daten bei der Deklaration übergeben werden. Ich denke, dies ist ein Fehler in TypeScript, daher sollten Sie einen auf der Projektsite auslösen.

Sie können das typisierte Wörterbuch verwenden, indem Sie Ihr Beispiel in Deklaration und Initialisierung aufteilen, wie z.

var persons: { [id: string] : IPerson; } = {};
persons["p1"] = { firstName: "F1", lastName: "L1" };
persons["p2"] = { firstName: "F2" }; // will result in an error
thomaux
quelle
3
Warum brauchst du das idSymbol? Es scheint unnötig.
kiewic
4
Mit dem idSymbol können Sie den Typ der Schlüssel des Wörterbuchs festlegen. Mit der obigen Erklärung konnten Sie Folgendes nicht tun:persons[1] = { firstName: 'F1', lastName: 'L1' }
Thomaux
2
Vergessen Sie diese Syntax immer aus irgendeinem Grund!
Eddiewould
12
Das idSymbol kann beliebig benannt werden und wurde so gestaltet, dass das Lesen von Code erleichtert wird. zB { [username: string] : IPerson; }
Guy Park
1
@Robouste Ich würde die findKey- Methode von Lodash verwenden. Wenn Sie eine native Lösung bevorzugen, können Sie auf Object.entries aufbauen . Wenn Sie daran interessiert sind, die vollständige Liste der Schlüssel zu erhalten, werfen Sie
thomaux
82

Für die Verwendung eines Wörterbuchobjekts in Typoskript können Sie die folgende Schnittstelle verwenden:

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

Verwenden Sie dies für Ihren Klasseneigenschaftstyp.

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

um diese Klasse zu verwenden und zu initialisieren,

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));
    }
Amol Bhor
quelle
60

Ich stimme thomaux zu, dass der Fehler bei der Überprüfung des Initialisierungstyps ein TypeScript-Fehler ist. Ich wollte jedoch immer noch einen Weg finden, ein Wörterbuch in einer einzelnen Anweisung mit korrekter Typprüfung zu deklarieren und zu initialisieren. Diese Implementierung ist länger, fügt jedoch zusätzliche Funktionen wie a containsKey(key: string)und remove(key: string)method hinzu. Ich vermute, dass dies vereinfacht werden könnte, sobald Generika in der Version 0.9 verfügbar sind.

Zuerst deklarieren wir die Basis-Dictionary-Klasse und die Schnittstelle. Die Schnittstelle wird für den Indexer benötigt, da Klassen sie nicht implementieren können.

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;
    }
}

Jetzt deklarieren wir den personenbezogenen Typ und die Dictionary / Dictionary-Schnittstelle. Beachten Sie im PersonDictionary, wie wir die richtigen Typen überschreiben values()und toLookup()zurückgeben.

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;
    }
}

Und hier ist ein einfaches Initialisierungs- und Verwendungsbeispiel:

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
dmck
quelle
Sehr hilfreicher Beispielcode. Das "Interface IDictionary" enthält einen kleinen Tippfehler, da auf IPerson verwiesen wird.
mgs
wäre schön, auch element count zu implementieren
nurettin
@dmck Die Deklaration containsKey(key: string): bool;funktioniert nicht mit TypeScript 1.5.0-beta . Es sollte geändert werden zu containsKey(key: string): boolean;.
Amarjeet Singh
1
Warum delcare Sie nicht generischen Typ? Dictionary <T>, dann muss die PersonDictionary-Klasse nicht erstellt werden. Sie deklarieren es folgendermaßen: var person = new Dictionary <IPerson> ();
Benoit
1
Ich habe ein solches generisches Wörterbuch effektiv verwendet. Ich fand es hier: fabiolandoni.ch/…
CAK2
5

Hier ist eine allgemeinere Dictionary-Implementierung, die von @dmck inspiriert wurde

    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;
      }
    }
mbcom
quelle
3

Wenn Sie eine Eigenschaft ignorieren möchten, markieren Sie sie als optional, indem Sie ein Fragezeichen hinzufügen:

interface IPerson {
    firstName: string;
    lastName?: string;
}
Massimiliano Kraus
quelle
1
Der springende Punkt der Frage ist, warum der angegebene Code ohne Angabe eines Nachnamens kompiliert wurde…
Pierre Arlaud
-1

Jetzt gibt es eine Bibliothek, die stark typisierte, abfragbare Sammlungen in Typoskript bereitstellt .

Diese Sammlungen sind:

  • Aufführen
  • Wörterbuch

Die Bibliothek heißt ts-generic-collection-linq .

Quellcode auf GitHub:

https://github.com/VeritasSoftware/ts-generic-collections

NPM:

https://www.npmjs.com/package/ts-generic-collections-linq

Mit dieser Bibliothek können Sie Sammlungen (wie List<T>) erstellen und wie unten gezeigt abfragen.

    let owners = new List<Owner>();

    let owner = new Owner();
    owner.id = 1;
    owner.name = "John Doe";
    owners.add(owner);

    owner = new Owner();
    owner.id = 2;
    owner.name = "Jane Doe";
    owners.add(owner);    

    let pets = new List<Pet>();

    let pet = new Pet();
    pet.ownerId = 2;
    pet.name = "Sam";
    pet.sex = Sex.M;

    pets.add(pet);

    pet = new Pet();
    pet.ownerId = 1;
    pet.name = "Jenny";
    pet.sex = Sex.F;

    pets.add(pet);

    //query to get owners by the sex/gender of their pets
    let ownersByPetSex = owners.join(pets, owner => owner.id, pet => pet.ownerId, (x, y) => new OwnerPet(x,y))
                               .groupBy(x => [x.pet.sex])
                               .select(x =>  new OwnersByPetSex(x.groups[0], x.list.select(x => x.owner)));

    expect(ownersByPetSex.toArray().length === 2).toBeTruthy();

    expect(ownersByPetSex.toArray()[0].sex == Sex.F).toBeTruthy();
    expect(ownersByPetSex.toArray()[0].owners.length === 1).toBeTruthy();
    expect(ownersByPetSex.toArray()[0].owners.toArray()[0].name == "John Doe").toBeTruthy();

    expect(ownersByPetSex.toArray()[1].sex == Sex.M).toBeTruthy();
    expect(ownersByPetSex.toArray()[1].owners.length == 1).toBeTruthy();
    expect(ownersByPetSex.toArray()[1].owners.toArray()[0].name == "Jane Doe").toBeTruthy();
John
quelle
kann kein npm-Paket dafür finden
Harry
1
@ Harry - npm Paket heißt "ts-generic-collection-linq"
Ade