Maps

Introduction

A FSTR::Map is analogous to the Wiring HashMap class, allowing content to be indexed using a key value.

The Map contains an array of FSTR::MapPair structures:

struct MapPair<KeyType, ContentType> {
   KeyType key_;
   ContentType* content_;
};

KeyType can be any simple type such as char, int, float, enum etc. It may also be a String Object (or, more precisely, String*).

ContentType can be any Object type (String, Array, Vector or Map). This allows hierarchical structures to be created.

Example: int ⇒ String

Here’s a basic example using integer keys:

#include <FlashString/Map.hpp>

IMPORT_FSTR_LOCAL(content1, PROJECT_DIR "/files/index.html");
IMPORT_FSTR_LOCAL(content2, PROJECT_DIR "/files/favicon.html");

DEFINE_FSTR_MAP(intmap, int, FSTR::String,
   {35, &content1},
   {180, &content2}
);

You should generally use IMPORT_FSTR_LOCAL() when referencing imported objects. If you need global access to imported data as well, then use IMPORT_FSTR().

Note

Older toolchains (generally GCC earlier than version 6) will fail to compile with error: the value of ‘FS_content1’ is not usable in a constant expression.

You can work around this as follows:

IMPORT_FSTR(content1, PROJECT_DIR "/files/index.html"); // Make this a global reference
IMPORT_FSTR_LOCAL(content2, PROJECT_DIR "/files/favicon.html");

DEFINE_FSTR_MAP(intmap, int, FSTR::String,
   {35, &FSTR_DATA_NAME(FS_content1).as<FSTR::String>()}, // Cast the actual content
   {180, &content2}
);

We can now do this:

void printValue(int key)
{
   auto value = intmap[key];
   if(value) {
      Serial.printf("Found '%u' in map, containing %u chars\n", value.key(), value.content().length());
      Serial.println(value.printer());
   } else {
      Serial.printf("Couldn't find '%u' in map\n", key);
   }
}

Example: String ⇒ String

Both the key and the content are stored as Strings:

#include <FlashString/Map.hpp>

DEFINE_FSTR_LOCAL(key1, "index.html");
DEFINE_FSTR_LOCAL(key2, "favicon.ico");
IMPORT_FSTR_LOCAL(content1, PROJECT_DIR "/files/index.html");
IMPORT_FSTR_LOCAL(content2, PROJECT_DIR "/files/favicon.html");

DEFINE_FSTR_MAP(fileMap, FlashString, FlashString,
   {&key1, &content1},
   {&key2, &content2},
);

We can now do this:

void onFile(HttpRequest& request, HttpResponse& response)
{
   String fileName = request.uri.getRelativePath();
   auto& value = fileMap[fileName];
   if(value) {
      // Found
      Serial.printf("Found '%s' in fileMap\n", String(value.key()).c_str());
      auto stream = new FlashMemoryStream(value);
      response.sendDataStream(stream, ContentType::fromFullFileName(fileName));
   } else {
      Serial.printf("File '%s' not found\n", fileName.c_str());
   }
}

Note

As with Vector<String>, Map<String, ...> lookups are by default case-insensitive.

If you require a case-sensitive lookup, use the indexOf method with ignoreCase = false.

Structure

The macro in the first example above produces a structure like this:

constexpr const struct {
   ObjectBase object;
   MapPair<int, String> data[2];
} __fstr__intmap PROGMEM = {
   {16},
   {35, &content1},
   {180, &content2},
};
const Map<int, String>& intmap = __fstr__intmap.object.as<Map<int, String>>();

Note: FSTR:: namespace qualifier omitted for clarity.

Usually, each MapPair is 8 bytes, but if the key is a double or int64 it would be 12 bytes.

Macros

DECLARE_FSTR_MAP(name, KeyType, ContentType)

Declare a global Map& reference.

Note

Use DEFINE_FSTR_MAP to instantiate the global object

Parameters:
  • name – Name of the Map& reference to define

  • KeyType – Integral type to use for key

  • ContentType – Object type to declare for content

DEFINE_FSTR_MAP(name, KeyType, ContentType, ...)

Define a Map Object with global reference.

Note

Size will be calculated

Parameters:
  • name – Name of the Map& reference to define

  • KeyType – Integral type to use for key

  • ContentType – Object type to declare for content

  • ... – List of MapPair definitions { key, &content }

DEFINE_FSTR_MAP_LOCAL(name, KeyType, ContentType, ...)

Like DEFINE_FSTR_MAP except reference is declared static constexpr.

DEFINE_FSTR_MAP_SIZED(name, KeyType, ContentType, size, ...)

Define a Map Object with global reference, specifying the number of elements.

Parameters:
  • name – Name of the Map& reference to define

  • KeyType – Integral type to use for key

  • ContentType – Object type to declare for content

  • size – Number of elements

  • ... – List of MapPair definitions { key, &content }

DEFINE_FSTR_MAP_SIZED_LOCAL(name, KeyType, ContentType, size, ...)

Like DEFINE_FSTR_MAP_SIZED except reference is declared static.

DEFINE_FSTR_MAP_DATA(name, KeyType, ContentType, ...)

Define a Map data structure.

Note

Size will be calculated

Parameters:
  • name – Name of data structure

  • KeyType – Integral type to use for key

  • ContentType – Object type to declare for content

  • ... – List of MapPair definitions { key, &content }

DEFINE_FSTR_MAP_DATA_SIZED(name, KeyType, ContentType, size, ...)

Define a Map data structure, specifying the number of elements.

Parameters:
  • name – Name of data structure

  • KeyType – Integral type to use for key

  • ContentType – Object type to declare for content

  • size – Number of elements

  • ... – List of MapPair definitions { key, &content }

template<typename KeyType, class ContentType>
class MapPair
#include <MapPair.hpp>

describes a pair mapping key => data for a specified key type

Template Parameters:
  • KeyType – Integral, floating point, enum or String

  • ContentTypeObject type to use for content

Class Templates

template<typename KeyType, class ContentType, class Pair = MapPair<KeyType, ContentType>>
class Map : public FSTR::Object<Map<KeyType, ContentType>, MapPair<KeyType, ContentType>>

Class template to access an associative map.

Template Parameters:
  • KeyType

  • ContentType

Public Functions

inline const Pair valueAt(unsigned index) const

Get a map entry by index, if it exists.

Note

Result validity can be checked using if()

template<typename TRefKey, typename T = KeyType>
inline std::enable_if<!std::is_class<T>::value, int>::type indexOf(const TRefKey &key) const

Lookup an integral key and return the index.

Parameters:

key – Key to locate, must be compatible with KeyType for equality comparison

Return values:

int – If key isn’t found, return -1

template<typename TRefKey, typename T = KeyType>
inline std::enable_if<std::is_same<T, String>::value, int>::type indexOf(const TRefKey &key, bool ignoreCase = true) const

Lookup a String key and return the index.

Parameters:
  • key

  • ignoreCase – Whether search is case-sensitive (default: true)

Return values:

int – If key isn’t found, return -1

template<typename TRefKey>
inline const Pair operator[](const TRefKey &key) const

Lookup a key and return the entry, if found.

Note

Result validity can be checked using if()

Parameters:

key

inline MapPrinter<Map> printer() const

Returns a printer object for this array.

Note

ElementType must be supported by Print

template<typename KeyType, class ContentType>
class MapPair

describes a pair mapping key => data for a specified key type

Template Parameters:
  • KeyType – Integral, floating point, enum or String

  • ContentTypeObject type to use for content

Public Functions

inline operator IfHelperType() const

Provides bool() operator to determine if Pair is valid.

template<typename T = KeyType>
inline std::enable_if<!std::is_class<T>::value, KeyType>::type key() const

Get the key (non-class key types)

template<typename T = KeyType>
inline std::enable_if<std::is_same<T, String>::value, const KeyType&>::type key() const

Get the key (String key type)

inline const ContentType &content() const

Accessor to get a reference to the content.

Public Static Functions

static inline const MapPair empty()

Get an empty Pair object, identifies as invalid when lookup fails.