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?

Robert Taylor
fuente
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['[email protected]'] = new Customer(); // OK
map[14] = new Customer(); // Not OK, 14 is not a string
map['[email protected]'] = '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
Ryan Cavanaugh
fuente
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 ['[email protected] '] = 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());
John Weisz
fuente
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;
Dimitar Mazhlekov
fuente
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> 
}
Twen
fuente
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"])
phil
fuente
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]

Nick N.
fuente
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);
  });
Juan
fuente