Dictionaries

5 min read

2 min read

3 min read

3 min read

2 min read

3 min read

2 min read

2 min read

2 min read

A dictionary is a mapping from a key type to a value type.

For example:

Slice
module M
{
struct Employee
{
long number;
string firstName;
string lastName;
}
dictionary<long, Employee> EmployeeMap;
}

This definition creates a dictionary named EmployeeMap that maps from an employee number to a structure containing the details for an employee.

The value type of a dictionary can be any Slice type. However, the key type of a dictionary is limited to one of the following types:

Here is the definition of our EmployeeMap once more:

Slice
dictionary<long, Employee> EmployeeMap;

The following code is generated for this definition:

C++
using EmployeeMap = std::map<std::int64_t, Employee>;

Again, there are no surprises here: a Slice dictionary simply maps to a standard std::map. As a result, you can use the dictionary like any other map, for example:

C++
EmployeeMap em;
Employee e;
e.number = 42;
e.firstName = "Stan";
e.lastName = "Lippman";
em[e.number] = e;
e.number = 77;
e.firstName = "Herb";
e.lastName = "Sutter";
em[e.number] = e;

You can override the default mapping of Slice dictionaries to C++ with a cpp:type metadata directive, for example:

Slice
[["cpp:include:unordered_map"]]
["cpp:type:std::unordered_map<std::int64_t, Employee>"]
dictionary<long, Employee> EmployeeMap;

With this metadata directive, the dictionary now maps to a C++ std::unordered_map:

C++
#include <unordered_map>
using EmployeeMap = std::unordered_map<std::int64_t, Employee>;

Like with sequences, anything following the cpp:type: prefix is taken to be the name of the type. For example, we could use ["cpp:type:::std::unordered_map<std::int64_t, std::string>"]. In that case, the compiler would use a fully-qualified name to define the type:

C++
using IntStringDict = ::std::unordered_map<std::int64_t, std::string>;

To avoid compilation errors in the generated code, you must instruct the compiler to generate an appropriate include directive with the cpp:include file metadata directive. This causes the compiler to add the line

C++
#include <unordered_map>

to the generated header file.

Instead of std::unordered_map, you can specify a type of your own as the dictionary type, for example:

Slice
[["cpp:include:CustomMap.h"]]
["cpp:type:MyCustomMap<std::int64_t, Employee>"]
dictionary<long, Employee> EmployeeMap;

With these metadata directives, the compiler will use a C++ type MyCustomMap as the dictionary type, and add an include directive for the header file CustomMap.h to the generated code.

The class or template class you provide must meet the following requirements:

  • The class must have a default constructor.
  • The class must have a copy constructor.
  • The class must provide nested types named key_type, mapped_type and value_type.
  • The class must provide iterator and const_iterator types and provide begin and end member functions with the usual semantics; these iterators must be comparable for equality and inequality.
  • The class must provide a clear function.
  • The class must provide a size function that returns the number of entries.
  • The class must provide an insert function that takes an iterator (as location hint) plus a value_type parameter, and returns an iterator to the new entry or to the existing entry with the given key.

Less formally, this means you can use any class or template class that looks like a standard map or unordered_map as your custom dictionary type.

In addition to modifying the type of a dictionary itself, you can also modify the mapping for particular return values or parameters. For example:

Slice
[["cpp:include:unordered_map"]]
module HR
{
struct Employee
{
long number;
string firstName;
string lastName;
}
dictionary<long, Employee> EmployeeMap;
interface Office
{
["cpp:type:std::unordered_map<std::int64_t, Employee>"]
EmployeeMap getAllEmployees();
}
}

With this definition, getAllEmployees returns an unordered_map, while other unqualified parameters of type EmployeeMap would use the default mapping (to a std::map).

Here is the definition of our EmployeeMap once more:

Slice
dictionary<long, Employee> EmployeeMap;

By default, the Slice-to-C# compiler maps the dictionary to the following type:

C#
// Standard Dictionary from System.Collections.Generic
Dictionary<long, Employee>

You can use the "cs:generic:SortedDictionary" or "cs:generic:SortedList" metadata directives to change the default mapping to use a sorted dictionary or sorted list instead. For example:

Slice
["cs:generic:SortedDictionary"]
dictionary<long, Employee> EmployeeMap;

With this definition, the type of the dictionary becomes:

C#
// From namespace System.Collections.Generic
SortedDictionary<long, Employee>

Here is the definition of our EmployeeMap once more:

Slice
dictionary<long, Employee> EmployeeMap;

As for sequences, the Java mapping does not create a separate named type for this definition. Instead, the dictionary is simply an instance of the generic type java.util.Map<K, V>, where K is the mapping of the key type and V is the mapping of the value type. In the example above, EmployeeMap is mapped to the Java type java.util.Map<Long, Employee>. The following code demonstrates how to allocate and use an instance of EmployeeMap:

Java
var em = new java.util.HashMap<Long, Employee>();
Employee e = new Employee();
e.number = 31;
e.firstName = "James";
e.lastName = "Gosling";
em.put(e.number, e);

If the semantics of a HashMap are not suitable for your application, you can specify an alternate type using the java:type metadata directive as shown in the example below:

Slice
["java:type:java.util.TreeMap<String, String>"]
dictionary<string, string> StringMap;

It is your responsibility to use type parameters for the Java class (String in the example above) that are the correct mappings for the dictionary's key and value types.

The compiler requires the formal type to implement java.util.Map<K, V>. If you do not specify a formal type, the compiler uses this type by default.

The instance type must have an accessible parameterless constructor and must be modifiable: when unmarshaling a dictionary, the generated code creates the instance with this constructor and fills it with put.

Note that extra care must be taken when defining dictionary types that contain nested generic types, such as a dictionary whose element type is a custom sequence. The Java compiler strictly enforces type safety, therefore any compatibility issues in the custom type metadata will be apparent when the generated code is compiled.

Refer to the Sequences for more information about java:type.

A Slice dictionary maps to:

  • A JavaScript Map when the key is a Slice basic type or an enumeration.
  • An Ice.HashMap when the key is a Slice structure.

This distinction is necessary because:

  • JavaScript Map uses the === operator for key equality.
  • Ice.HashMap accepts a key comparator, so struct keys can use their equals method for equality.
Slice
struct Employee
{
long number;
string firstName;
string lastName;
}
dictionary<long, Employee> EmployeeMap;

In this example, EmployeeMap maps to a JavaScript Map with:

  • key type = BigInt (from Slice long)
  • value type = Employee (the JavaScript class generated from the Slice struct).

If the key is a Slice struct, the compiler generates code that uses Ice.HashMap.

Slice
dictionary<Employee, string> EmployeeDeptMap;

Generated JavaScript/TypeScript code:

JavaScript
class EmployeeDeptMap extends Ice.HashMap {
constructor(h) {
const keyComparator = ...;
super(h || keyComparator);
}
}
TypeScript
class EmployeeDeptMap extends Ice.HashMap<Employee, string> { ... }
  • new EmployeeDeptMap() creates an Ice.HashMap that compares keys with their equals method.
  • new Ice.HashMap() compares keys with === unless you pass a key comparator to its constructor.

A Slice dictionary maps to a MATLAB dictionary.

The key type of the MATLAB dictionary is the mapped type for the Slice dictionary key. For example, a Slice string key maps to a MATLAB char key, which MATLAB interprets as a string type.

The value type of the MATLAB dictionary depends on the Slice value type:

Slice Value TypeMATLAB Value Type
bool, numeric type, enum, struct
Corresponding MATLAB type
string
MATLAB string
class, proxy, sequence, dictionary
Cell of the corresponding MATLAB type

Consider the definition of our EmployeeMap once more:

Slice
struct Employee
{
["matlab:identifier:Number"]
long number;
["matlab:identifier:FirstName"]
string firstName;
["matlab:identifier:LastName"]
string lastName;
}
dictionary<long, Employee> EmployeeMap;

EmployeeMap maps to a dictionary with key type = int64 and value type = Employee (a MATLAB class mapped from a Slice struct).

MATLAB
em = configureDictionary('int64', 'M.Employee');
e = M.Employee();
e.Number = 31;
e.FirstName = 'James';
e.LastName = 'Gosling';
em(e.Number) = e;

A Slice dictionary maps to a native PHP associative array. The PHP mapping does not currently support all Slice dictionary types, however, because native PHP associative arrays support only integers and strings as keys.

A Slice dictionary whose key type is an enumeration or one of the primitive types boolean, byte, short, int, or long is mapped as an associative array with an integer key.

A Slice dictionary with a string key type is mapped as an associative array with a string key. All other key types cause a warning to be generated.

Here is the definition of our EmployeeMap:

Slice
dictionary<long, Employee> EmployeeMap;

You can create an instance of this dictionary as shown below:

PHP
$e1 = new Employee;
$e1->number = 42;
$e1->firstName = "Stan";
$e1->lastName = "Lippman";
$e2 = new Employee;
$e2->number = 77;
$e2->firstName = "Herb";
$e2->lastName = "Sutter";
$em = array($e1->number => $e1, $e2->number => $e2);

The Ice runtime validates the elements of a dictionary to ensure that they are compatible with the declared type; InvalidArgumentException exception is thrown if an incompatible type is encountered.

Here is the definition of our EmployeeMap once more:

Slice
dictionary<long, Employee> EmployeeMap;

As for sequences, the Python mapping does not create a separate named type for this definition. Instead, all dictionaries are simply instances of Python's dictionary type. For example:

Python
em = {}
e = Employee()
e.number = 31
e.firstName = "James"
e.lastName = "Gosling"
em[e.number] = e

The Ice runtime validates the elements of a dictionary to ensure that they are compatible with the declared type; a ValueError exception is raised if an incompatible type is encountered.

Here is the definition of our EmployeeMap once more:

Slice
dictionary<long, Employee> EmployeeMap;

As for sequences, the Ruby mapping does not create a separate named type for this definition. Instead, all dictionaries are simply instances of Ruby's hash collection type. For example:

Ruby
em = {}
e = Employee.new
e.number = 31
e.firstName = "James"
e.lastName = "Gosling"
em[e.number] = e

The Ice runtime validates the elements of a dictionary to ensure that they are compatible with the declared type; a TypeError exception is raised if an incompatible type is encountered.

Here is the definition of our EmployeeMap once more:

Slice
dictionary<long, Employee> EmployeeMap;

The following code is generated for this definition:

Swift
public typealias EmployeeMap = [Int64: Employee]

Again, there are no surprises here: a Slice dictionary simply maps to a standard Swift dictionary. As a result, you can use the dictionary like any other dictionary, for example:

Swift
let stan = Employee(number: 42, firstName: "Stan", lastName: "Lippman")
let herb = Employee(number: 77, firstName: "Herb", lastName: "Sutter")
let em = [stan.number: stan, herb.number: herb]