Declarar e inicializar un diccionario en mecanografiado

248

Dado el siguiente código

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

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

¿Por qué no se rechaza la inicialización? Después de todo, el segundo objeto no tiene la propiedad "apellido".

mgs
fuente
11
Nota: esto ya se ha solucionado (no estoy seguro de qué versión exacta de TS) Recibo estos errores en VS, como era de esperar: Index signatures are incompatible. Type '{ firstName: string; }' is not assignable to type 'IPerson'. Property 'lastName' is missing in type '{ firstName: string; }'.
Simon_Weaver

Respuestas:

289

Editar : Esto se ha solucionado en las últimas versiones de TS. Citando el comentario de @ Simon_Weaver sobre la publicación del OP:

Nota: esto ya se ha solucionado (no estoy seguro de qué versión exacta de TS). Recibo estos errores en VS, como era de esperar:Index signatures are incompatible. Type '{ firstName: string; }' is not assignable to type 'IPerson'. Property 'lastName' is missing in type '{ firstName: string; }'.


Aparentemente, esto no funciona al pasar los datos iniciales en la declaración. Supongo que esto es un error en TypeScript, por lo que debería plantear uno en el sitio del proyecto.

Puede utilizar el diccionario escrito dividiendo su ejemplo en declaración e inicialización, como:

var persons: { [id: string] : IPerson; } = {};
persons["p1"] = { firstName: "F1", lastName: "L1" };
persons["p2"] = { firstName: "F2" }; // will result in an error
thomaux
fuente
3
¿Por qué necesitas el idsímbolo? Parece que es innecesario.
kiewic
44
Usando el idsímbolo, puede declarar cuál debería ser el tipo de las claves del diccionario. Con la declaración anterior, no podría hacer lo siguiente:persons[1] = { firstName: 'F1', lastName: 'L1' }
thomaux
2
¡Siempre olvide esta sintaxis por alguna razón!
eddiewould
12
el idsímbolo se puede nombrar como desee y se diseñó de esa manera para que sea más fácil leer el código. por ejemplo { [username: string] : IPerson; }
Guy Park el
1
@Robouste Utilizaría el método findKey de Lodash o, si prefiere una solución nativa, podría construir en Object.entries . Si está interesado en obtener la lista completa de claves, eche un vistazo a Object.keys
thomaux
82

Para usar el objeto de diccionario en mecanografiado, puede usar la interfaz de la siguiente manera:

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

y, use esto para su tipo de propiedad de clase.

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

para usar e inicializar esta clase,

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
fuente
60

Estoy de acuerdo con thomaux en que el error de comprobación del tipo de inicialización es un error de TypeScript. Sin embargo, todavía quería encontrar una manera de declarar e inicializar un Diccionario en una sola declaración con la verificación de tipo correcta. Esta implementación es más larga, sin embargo, agrega funcionalidades adicionales como un containsKey(key: string)yremove(key: string) método. Sospecho que esto podría simplificarse una vez que los genéricos estén disponibles en la versión 0.9.

Primero declaramos la clase de diccionario base y la interfaz. La interfaz es necesaria para el indexador porque las clases no pueden implementarlos.

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

Ahora declaramos el tipo específico de persona y la interfaz Diccionario / Diccionario. En la nota PersonDictionary, cómo anulamos values()y toLookup()devolvemos los tipos correctos.

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

Y aquí hay un ejemplo simple de inicialización y uso:

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
fuente
Código de muestra muy útil. La "interfaz IDictionary" contiene un pequeño error tipográfico, ya que hay una referencia a IPerson.
mgs
sería bueno implementar el recuento de elementos también
nurettin 11/11
@dmck La declaración containsKey(key: string): bool;no funciona con TypeScript 1.5.0-beta . Debería cambiarse a containsKey(key: string): boolean;.
Amarjeet Singh
1
¿Por qué no te importa el tipo genérico? Diccionario <T>, entonces no es necesario crear la clase PersonDictionary. Lo declaras así: var persons = new Dictionary <IPerson> ();
Benoit
1
He usado un diccionario genérico de este tipo de manera efectiva. Lo encontré aquí: fabiolandoni.ch/…
CAK2
5

Aquí hay una implementación de diccionario más general inspirada en esto 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
fuente
3

Si desea ignorar una propiedad, márquela como opcional agregando un signo de interrogación:

interface IPerson {
    firstName: string;
    lastName?: string;
}
Massimiliano Kraus
fuente
1
El punto central de la pregunta es por qué el código dado se compiló sin proporcionar un apellido ...
Pierre Arlaud
-1

Ahora, hay una biblioteca que proporciona colecciones fuertemente tipadas y consultables en mecanografiado.

Estas colecciones son:

  • Lista
  • Diccionario

La biblioteca se llama ts-generic-collections-linq .

Código fuente en GitHub:

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

NPM:

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

Con esta biblioteca, puede crear colecciones (como List<T>) y consultarlas como se muestra a continuación.

    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();
Juan
fuente
no puedo encontrar un paquete npm para esto
Harry
1
@Harry - el paquete npm se llama "ts-generic-collections-linq"
Ade