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".

mg
la source
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
thomaux
la source
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));
    }
Amol Bhor
la source
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
dmck
la source
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;
      }
    }
mbcom
la source
3

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

interface IPerson {
    firstName: string;
    lastName?: string;
}
Massimiliano Kraus
la source
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();
John
la source
ne peut pas trouver de package npm pour cela
Harry
1
@Harry - paquet NPM est appelé "ts-generic-collections-LINQ"
Ade