Dichiarare e inizializzare un dizionario in Typescript


248

Dato il seguente codice

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

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

Perché l'inizializzazione non viene rifiutata? Dopotutto, il secondo oggetto non ha la proprietà "lastName".


11
Nota: da allora questo è stato risolto (non so quale versione TS esatta). Ottengo questi errori in VS, come ti aspetteresti: Index signatures are incompatible. Type '{ firstName: string; }' is not assignable to type 'IPerson'. Property 'lastName' is missing in type '{ firstName: string; }'.
Simon_Weaver,

Risposte:


290

Modifica : da allora è stato risolto nelle ultime versioni di TS. Citando il commento di @ Simon_Weaver sul post dell'OP:

Nota: da allora questo è stato risolto (non sono sicuro di quale versione TS esatta). Ottengo questi errori in VS, come ti aspetteresti:Index signatures are incompatible. Type '{ firstName: string; }' is not assignable to type 'IPerson'. Property 'lastName' is missing in type '{ firstName: string; }'.


Apparentemente questo non funziona quando si trasmettono i dati iniziali alla dichiarazione. Immagino che questo sia un bug in TypeScript, quindi dovresti sollevarne uno sul sito del progetto.

Puoi utilizzare il dizionario digitato suddividendo il tuo esempio in dichiarazione e inizializzazione, come:

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

4
Perché hai bisogno del idsimbolo? Sembra che non sia necessario.
kiewic

4
Usando il idsimbolo, puoi dichiarare quale dovrebbe essere il tipo di chiavi del dizionario. Con la dichiarazione sopra, non puoi fare quanto segue:persons[1] = { firstName: 'F1', lastName: 'L1' }
thomaux

3
Dimentica sempre questa sintassi per qualche motivo!
eddiewould

13
il idsimbolo può essere nominato come preferisci ed è stato progettato in questo modo per facilitare la lettura del codice. ad es. { [username: string] : IPerson; }
Guy Park il

1
@Robouste Userei il metodo findKey di Lodash o se preferisci una soluzione nativa, potresti basarti su Object.entries . Se sei interessato a ottenere l'elenco completo delle chiavi, dai un'occhiata a Object.keys
thomaux,

82

Per usare l'oggetto dizionario in dattiloscritto puoi usare l'interfaccia come di seguito:

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

e, usalo per il tipo di proprietà della tua classe.

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

utilizzare e inizializzare questa 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

Concordo con thomaux che l'errore di verifica del tipo di inizializzazione sia un errore TypeScript. Tuttavia, volevo ancora trovare un modo per dichiarare e inizializzare un dizionario in una singola istruzione con il controllo del tipo corretto. Questa implementazione è più lunga, tuttavia aggiunge funzionalità aggiuntive come a containsKey(key: string)eremove(key: string) metodo. Sospetto che ciò potrebbe essere semplificato una volta che i generici saranno disponibili nella versione 0.9.

Per prima cosa dichiariamo la classe e l'interfaccia del dizionario di base. L'interfaccia è necessaria per l'indicizzatore perché le classi non possono implementarle.

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

Ora dichiariamo il tipo specifico di Persona e l'interfaccia Dizionario / Dizionario. Nella nota PersonDictionary come sostituiamo values()e toLookup()restituiamo i tipi corretti.

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

Ed ecco un semplice esempio di inizializzazione e utilizzo:

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

Codice di esempio molto utile. "Interface IDictionary" contiene un piccolo errore di battitura, in quanto esiste un riferimento a IPerson.
mg

sarebbe bello implementare anche il conteggio degli elementi
nurettin

@dmck La dichiarazione containsKey(key: string): bool;non funziona con TypeScript 1.5.0-beta . Dovrebbe essere cambiato in containsKey(key: string): boolean;.
Amarjeet Singh,

1
perché non delcare il tipo generico? Dizionario <T>, quindi non è necessario creare la classe PersonDictionary. Lo dichiari così: var persons = new Dictionary <IPerson> ();
Benoit,

1
Ho usato un dizionario così generico in modo efficace. L'ho trovato qui: fabiolandoni.ch/…
CAK2

5

Ecco un'implementazione del Dizionario più generale ispirata da questo da @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

Se si desidera ignorare una proprietà, contrassegnarla come facoltativa aggiungendo un punto interrogativo:

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

1
L'intero punto della domanda è perché il codice dato sia stato compilato senza fornire un cognome ...
Pierre Arlaud,

-1

Ora, c'è una libreria che fornisce raccolte tipicamente forti e interrogabili in dattiloscritto.

Queste collezioni sono:

  • Elenco
  • Dizionario

La libreria si chiama ts-generic-collections-linq .

Codice sorgente su GitHub:

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

NPM:

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

Con questa libreria, è possibile creare raccolte (come List<T>) e interrogarle come mostrato di seguito.

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

non riesco a trovare un pacchetto npm per questo
Harry

1
@Harry - il pacchetto npm si chiama "ts-generic-collections-linq"
Ade,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.