Objetos de TypeScript como tipos de diccionario como en C #


336

Tengo un código JavaScript que usa objetos como diccionarios; por ejemplo, un objeto 'persona' contendrá algunos datos personales extraídos de la dirección de correo electrónico.

var people = {<email> : <'some personal data'>};

adding   > "people[<email>] = <data>;" 
getting  > "var data = people[<email>];" 
deleting > "delete people[<email>];"

¿Es posible describir esto en Typecript? o tengo que usar una matriz?


55
Publicación anterior, pero tenga en cuenta que existe el mapa ES6
— Old Badman Gray

Respuestas:


547

En las versiones más nuevas de mecanografiado puede usar:

type Customers = Record<string, Customer>

En versiones anteriores puedes usar:

var map: { [email: string]: Customer; } = { };
map['foo@gmail.com'] = new Customer(); // OK
map[14] = new Customer(); // Not OK, 14 is not a string
map['bar@hotmail.com'] = 'x'; // Not OK, 'x' is not a customer

También puede crear una interfaz si no desea escribir esa anotación de tipo completo cada vez:

interface StringToCustomerMap {
    [email: string]: Customer;
}

var map: StringToCustomerMap = { };
// Equivalent to first line of above

2
Esa es una forma útil de asegurarse de que el compilador restrinja los índices a cadenas. Interesante. No parece que pueda especificar que el tipo de índice sea distinto de cadenas o enteros, pero eso tiene sentido, ya que solo se asigna a los índices de objetos JS nativos.
— Ken Smith

55
Puede saber esto, pero también hay algunos problemas potenciales con este enfoque, el más importante es que no hay una forma segura y fácil de iterar a través de todos los miembros. Este código, por ejemplo, muestra que mapcontiene dos miembros: (<any> Object.prototype) .something = function () {}; clase Cliente {} var mapa: {[correo electrónico: cadena]: Cliente; } = {}; map ['foo@gmail.com '] = nuevo Cliente (); for (var i in map) {console.log (map [i])}
— Ken Smith

55
¿Cómo lo eliminas?
— TDaver

24
Otro enfoque interesante es: interfaz MapStringTo <T> {[clave: cadena]: T; } Y el declarar la variable comovar map:MapStringTo<Customer> = {};
— orellabac

1
Tenga en cuenta que la restricción de índice ya no funciona. Lee mas.
— David Sherret

127

Además de usar un objeto similar a un mapa , ha habido un Mapobjeto real desde hace algún tiempo, que está disponible en TypeScript cuando se compila en ES6, o cuando se usa un polyfill con las definiciones de tipo ES6 :

let people = new Map<string, Person>();

Admite la misma funcionalidad que Object, y más, con una sintaxis ligeramente diferente:

// Adding an item (a key-value pair):
people.set("John", { firstName: "John", lastName: "Doe" });

// Checking for the presence of a key:
people.has("John"); // true

// Retrieving a value by a key:
people.get("John").lastName; // "Doe"

// Deleting an item by a key:
people.delete("John");

Esto solo tiene varias ventajas sobre el uso de un objeto similar a un mapa , como:

  • Soporte para claves no basadas en cadenas, por ejemplo, números u objetos, ninguno de los cuales es compatible con Object(no, Objectno admite números, los convierte en cadenas)
  • Menos espacio para errores cuando no se usa --noImplicitAny, ya que Mapsiempre tiene un tipo de clave y un tipo de valor , mientras que un objeto puede no tener una firma de índice
  • La funcionalidad de agregar / eliminar elementos (pares clave-valor) está optimizada para la tarea, a diferencia de crear propiedades en unObject

Además, un Mapobjeto proporciona una API más potente y elegante para tareas comunes, la mayoría de las cuales no están disponibles a través de Objectcorreos electrónicos simples sin hackear las funciones de ayuda (aunque algunas de estas requieren un iterador ES6 completo / polyfill iterable para objetivos ES5 o inferiores):

// Iterate over Map entries:
people.forEach((person, key) => ...);

// Clear the Map:
people.clear();

// Get Map size:
people.size;

// Extract keys into array (in insertion order):
let keys = Array.from(people.keys());

// Extract values into array (in insertion order):
let values = Array.from(people.values());

2
¡Eso es genial! Pero lamentablemente se serializó mal usando JSON.stringify(), por lo que se puede usar, por ejemplo, para socket.io :(
— Lion

@Lion: bueno, sí, la Mapserialización es bastante divertida. Yo, por ejemplo, realizo una conversión a objetos de pares clave-valor antes de serializar, y luego de regreso (por ejemplo, objeto de { key: "John", value: { firstName: "John" } }).
— John Weisz

2
Cometí el error de usar un mapa en lugar de un simple objeto antiguo, y la serialización realmente me atrapó. Manténgase alejado en mi opinión.
— user378380

1
Esto es hermoso. Me alegra que me hayas inspirado para finalmente sumergirme en los mapas. Esto reemplazará mis estructuras habituales de mapa de teclas / diccionario, ya que es mucho más fácil escribir las teclas con fuerza.
— Metodista el

77

Puede usar interfaces con plantillas como esta:

interface Map<T> {
    [K: string]: T;
}

let dict: Map<number> = {};
dict["one"] = 1;

77
Tenga en cuenta que esto colisiona con el tipo de mapa es6. Mejor que la otra respuesta porque se ignora la restricción del índice.
— Old Badman Gray

¿Cómo verifico si existe una clave en el diccionario?
— Samneric

dict.hasOwnProperty ('clave')
— Dimitar Mazhlekov

1
Uso Dictionary en lugar de Map para evitar confusiones, y puedes usar la notación de objeto literal:let dict: Dictionary<number> = { "one": 1, "two": 2 };
— PhiLho

8

También puede usar el tipo de registro en mecanografiado:

export interface nameInterface { 
    propName : Record<string, otherComplexInterface> 
}

5

Lodash tiene una implementación de diccionario simple y tiene un buen soporte de TypeScript

Instalar Lodash:

npm install lodash @types/lodash --save

Importación y uso:

import { Dictionary } from "lodash";
let properties : Dictionary<string> = {
    "key": "value"        
}
console.log(properties["key"])

3

Puedes usar Recordpara esto:

https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkt

Ejemplo (un mapeo entre la cita ApNameStatus y algunos metadatos):

  const iconMapping: Record<AppointmentStatus, Icon> = {
    [AppointmentStatus.Failed]: { Name: 'calendar times', Color: 'red' },
    [AppointmentStatus.Canceled]: { Name: 'calendar times outline', Color: 'red' },
    [AppointmentStatus.Confirmed]: { Name: 'calendar check outline', Color: 'green' },
    [AppointmentStatus.Requested]: { Name: 'calendar alternate outline', Color: 'orange' },
    [AppointmentStatus.None]: { Name: 'calendar outline', Color: 'blue' }
  }

Ahora con interfaz como valor:

interface Icon { Name: string Color: string }

Uso:

const icon: SemanticIcon = iconMapping[appointment.Status]


Esto es muy util. ¿Usarías una cadena enumo un class/objectfor AppointmentStatus, o importa?
— Drenai

@Drenai no importa, es lo que prefieres
— Nick N.

-2

Hay una biblioteca que proporciona colecciones fuertemente tipadas y consultables en mecanografiado.

Las colecciones son:

  • Lista
  • Diccionario

La biblioteca se llama ts-generic-collections . ( Código fuente en GitHub )

Puede crear un diccionario y consultarlo como a continuación:

  it('firstOrDefault', () => {
    let dictionary = new Dictionary<Car, IList<Feature>>();

    let car = new Car(1, "Mercedez", "S 400", Country.Germany);
    let car2 = new Car(2, "Mercedez", "S 500", Country.Germany);

    let features = new List<Feature>();

    let feature = new Feature(1, "2 - Door Sedan");
    features.add(feature);

    dictionary.add(car, features);

    features = new List<Feature>();
    feature = new Feature(2, "4 - Door Sedan");
    features.add(feature);

    dictionary.add(car2, features);

    //query
    let first = dictionary.firstOrDefault(x => x.key.name == "Mercedez");

    expect(first.key.id = 1);
  });
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.