Déclarez et initialisez un dictionnaire en tapuscrit


248

Étant donné le code suivant

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

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

Pourquoi l'initialisation n'est-elle pas rejetée? Après tout, le deuxième objet n'a pas la propriété "lastName".


11
Remarque: cela a depuis été corrigé (je ne sais pas quelle version exacte de TS). Je reçois ces erreurs dans VS, comme vous vous en Index signatures are incompatible. Type '{ firstName: string; }' is not assignable to type 'IPerson'. Property 'lastName' is missing in type '{ firstName: string; }'.
doutez

Réponses:


289

Edit : Cela a depuis été corrigé dans les dernières versions TS. Citant le commentaire de @ Simon_Weaver sur le post du PO:

Remarque: cela a depuis été corrigé (je ne sais pas quelle version exacte de TS). Je reçois ces erreurs dans VS, comme vous vous en doutez:Index signatures are incompatible. Type '{ firstName: string; }' is not assignable to type 'IPerson'. Property 'lastName' is missing in type '{ firstName: string; }'.


Apparemment, cela ne fonctionne pas lors du passage des données initiales lors de la déclaration. Je suppose que c'est un bogue dans TypeScript, vous devriez donc en soulever un sur le site du projet.

Vous pouvez utiliser le dictionnaire tapé en divisant votre exemple en déclaration et initialisation, comme:

var persons: { [id: string] : IPerson; } = {};
persons["p1"] = { firstName: "F1", lastName: "L1" };
persons["p2"] = { firstName: "F2" }; // will result in an error

3
Pourquoi avez-vous besoin du idsymbole? Il semble que ce ne soit pas nécessaire.
kiewic

4
En utilisant le idsymbole, vous pouvez déclarer quel doit être le type des touches du dictionnaire. Avec la déclaration ci-dessus, vous ne pouvez pas faire ce qui suit:persons[1] = { firstName: 'F1', lastName: 'L1' }
thomaux

2
Oubliez toujours cette syntaxe pour une raison quelconque!
eddiewould

12
le idsymbole peut être nommé comme vous voulez et a été conçu de cette façon pour faciliter la lecture du code. par exemple { [username: string] : IPerson; }
Guy Park

1
@Robouste J'utiliserais la méthode findKey de Lodash ou si vous préférez une solution native, vous pourriez construire sur Object.entries . Si vous souhaitez obtenir la liste complète des clés, consultez Object.keys
thomaux

82

Pour utiliser un objet dictionnaire dans un script, vous pouvez utiliser l'interface comme ci-dessous:

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

et utilisez-le pour votre type de propriété de classe.

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

d'utiliser et d'initialiser cette classe,

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

61

Je suis d'accord avec thomaux que l'erreur de vérification du type d'initialisation est un bogue TypeScript. Cependant, je voulais toujours trouver un moyen de déclarer et d'initialiser un dictionnaire dans une seule instruction avec une vérification de type correcte. Cette implémentation est plus longue, mais elle ajoute des fonctionnalités supplémentaires telles que a containsKey(key: string)et la remove(key: string)méthode. Je soupçonne que cela pourrait être simplifié une fois que les génériques seront disponibles dans la version 0.9.

Nous déclarons d'abord la classe et l'interface de base du dictionnaire. L'interface est requise pour l'indexeur car les classes ne peuvent pas les implémenter.

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

Nous déclarons maintenant le type spécifique à la personne et l'interface Dictionnaire / Dictionnaire. Dans la note PersonDictionary comment nous remplaçons values()et toLookup()de retourner les bons 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;
    }
}

Et voici un simple exemple d'initialisation et d'utilisation:

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

Exemple de code très utile. L '"interface IDictionary" contient une petite faute de frappe, car il y a une référence à IPerson.
mgs

serait bien d'implémenter le nombre d'éléments aussi
nurettin

@dmck La déclaration containsKey(key: string): bool;ne fonctionne pas avec TypeScript 1.5.0-beta . Il doit être remplacé par containsKey(key: string): boolean;.
Amarjeet Singh

1
pourquoi ne pas delcare type générique? Dictionnaire <T>, alors pas besoin de créer la classe PersonDictionary. Vous le déclarez comme ceci: var persons = new Dictionary <IPerson> ();
Benoit

1
J'ai utilisé un tel dictionnaire générique efficacement. Je l'ai trouvé ici: fabiolandoni.ch/…
CAK2

5

Voici une implémentation du dictionnaire plus générale inspirée de celle de @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;
      }
    }

3

Si vous souhaitez ignorer une propriété, marquez-la comme facultative en ajoutant un point d'interrogation:

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

1
Toute la question est de savoir pourquoi le code donné a été compilé sans fournir de nom de famille…
Pierre Arlaud

-1

Maintenant, il existe une bibliothèque qui fournit des collections fortement typées et interrogeables en dactylographie.

Ces collections sont:

  • liste
  • dictionnaire

La bibliothèque s'appelle ts-generic-collections-linq .

Code source sur GitHub:

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

NPM:

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

Avec cette bibliothèque, vous pouvez créer des collections (comme List<T>) et les interroger comme indiqué ci-dessous.

    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();

ne peut pas trouver de package npm pour cela
Harry

1
@Harry - paquet NPM est appelé "ts-generic-collections-LINQ"
Ade
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.