Declarar e inicializar um dicionário no texto datilografado

248

Dado o código a seguir

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

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

Por que a inicialização não é rejeitada? Afinal, o segundo objeto não possui a propriedade "lastName".

mgs
fonte
11
Nota: isso já foi corrigido (não sei qual versão exata do TS). Eu recebo esses erros em VS, como seria 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

Respostas:

289

Editar : isso foi corrigido nas versões mais recentes do TS. Citando o comentário de @ Simon_Weaver no post do OP:

Nota: isso já foi corrigido (não sei qual versão exata do TS). Eu recebo esses erros no VS, como seria de esperar:Index signatures are incompatible. Type '{ firstName: string; }' is not assignable to type 'IPerson'. Property 'lastName' is missing in type '{ firstName: string; }'.


Aparentemente, isso não funciona ao passar os dados iniciais na declaração. Eu acho que isso é um bug no TypeScript, então você deve criar um no site do projeto.

Você pode usar o dicionário digitado dividindo seu exemplo em declaração e inicialização, como:

var persons: { [id: string] : IPerson; } = {};
persons["p1"] = { firstName: "F1", lastName: "L1" };
persons["p2"] = { firstName: "F2" }; // will result in an error
thomaux
fonte
3
Por que você precisa do idsímbolo? Parece que é desnecessário.
kiewic
4
Usando o idsímbolo, você pode declarar qual deve ser o tipo das chaves do dicionário. Com a declaração acima, você não pode fazer o seguinte:persons[1] = { firstName: 'F1', lastName: 'L1' }
thomaux
2
Sempre esqueça esta sintaxe por algum motivo!
Eddiewould
12
o idsímbolo pode ter o nome que você quiser e foi projetado dessa maneira para facilitar a leitura do código. por exemplo { [username: string] : IPerson; }
Guy Park
1
@ Robouste Eu usaria o método findKey do Lodash ou, se você preferir uma solução nativa, poderá criar Object.entries . Se você estiver interessado em obter a lista completa de chaves, dê uma olhada Object.keys
thomaux
82

Para usar o objeto de dicionário no texto datilografado, você pode usar a interface como abaixo:

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

e use isso para o seu tipo de propriedade de classe.

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

para usar e inicializar essa 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
fonte
60

Concordo com o thomaux que o erro de verificação do tipo de inicialização é um bug do TypeScript. No entanto, eu ainda queria encontrar uma maneira de declarar e inicializar um Dicionário em uma única instrução com verificação de tipo correta. Essa implementação é mais longa, porém adiciona funcionalidades adicionais, como a containsKey(key: string)e remove(key: string)method. Suspeito que isso possa ser simplificado quando os genéricos estiverem disponíveis na versão 0.9.

Primeiro, declaramos a classe base Dictionary e a interface. A interface é necessária para o indexador porque as classes não podem implementá-las.

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

Agora declaramos o tipo específico de Pessoa e a interface Dicionário / Dicionário. No PersonDictionary, observe como substituímos values()e toLookup()retornamos os tipos corretos.

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

E aqui está um exemplo simples de inicialização e 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
fonte
Código de amostra muito útil. O "interface IDictionary" contém um pequeno erro de digitação, pois há uma referência ao IPerson.
MGS
seria bom implementar a contagem de elementos também
nurettin
@dmck A declaração containsKey(key: string): bool;não funciona com o TypeScript 1.5.0-beta . Deve ser alterado para containsKey(key: string): boolean;.
Amarjeet Singh
1
por que você não se importa com o tipo genérico? Dictionary <T>, não é necessário criar a classe PersonDictionary. Você declara assim: var people = new Dictionary <IPerson> ();
Benoit
1
Eu usei um dicionário tão genérico de forma eficaz. Encontrei-o aqui: fabiolandoni.ch/…
CAK2 11/11/16
5

Aqui está uma implementação do dicionário mais geral inspirada por isso no @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
fonte
3

Se você deseja ignorar uma propriedade, marque-a como opcional, adicionando um ponto de interrogação:

interface IPerson {
    firstName: string;
    lastName?: string;
}
Massimiliano Kraus
fonte
1
O ponto de toda a questão é por que o código dado compilado sem fornecer um sobrenome ...
Pierre Arlaud
-1

Agora, há uma biblioteca que fornece coleções pesquisáveis ​​e de tipo forte em texto datilografado.

Essas coleções são:

  • Lista
  • Dicionário

A biblioteca é chamada ts-generic-collections-linq .

Código fonte no GitHub:

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

NPM:

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

Com esta biblioteca, você pode criar coleções (como List<T>) e consultá-las, como mostrado abaixo.

    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
fonte
não consegue encontrar um pacote de npm para este
Harry
1
@Harry - pacote npm é chamado de "ts-genéricas-coleções-linq"
Ade