Advanced Types

In addition to Inlet’s primitive types (bool, int, double, string), user-defined types and functions can also be defined as part of an input file.

In this section, we first describe how Individual Structs can be added to our schemas. We then extend it to Arrays and Dictionaries of Structs.

Individual Structs

Defining And Storing

To add a single (i.e., not array) user-defined type to the input file, use the addStruct method of the Inlet or Container classes to add a Container (collection of Fields and sub-Containers) that will represent the fields of the struct.

Consider a simple Lua table that contains only primitive types, whose definition might look like:

car = {
    make = "BestCompany",
    seats = 2,
    horsepower = 200,
    color = "blue"
}

or in YAML, something like:

car:
  make: BestCompany
  seats: 2
  horsepower: 200
  color: blue

Its Inlet schema can be defined as follows:

struct Car
{
  std::string make;
  std::string color;
  int seats;
  int horsepower;

  // A function can be used to define a schema for a particular struct
  // For convenience, it can be implemented as a static method of the struct
  static void defineSchema(inlet::Container& car_schema)
  {
    car_schema.addString("make", "Make of car");
    car_schema.addString("color", "Color of car").defaultValue("red");
    car_schema.addInt("seats", "Number of seats").range(2, 7);
    car_schema.addInt("horsepower", "Amount of horsepower");
  }
};

This would be used by creating an inlet::Container for the Car instance and then defining the struct schema on that subcontainer, e.g.:

  // Create a container off the global container for the car object
  // then define its schema
  auto& car_schema = inlet.addStruct("car", "Vehicle description");
  Car::defineSchema(car_schema);

Note

The definition of a static defineSchema member function is not required, and is just used for convenience. The schema definition for a class or struct could also be implemented as a free function for third-party types, or even in the same place as the sub-container declaration.

Accessing

In order to convert from Inlet’s internal representation to a custom C++ struct, you must provide deserialization logic. This is accomplished by a specialization of the FromInlet<T> functor for your type T, which implements a member function with the signature T operator()(const inlet::Container&). This function should return an instance of type T with its members populated with the corresponding fields in the input file. For the simple Car example whose schema is defined above, the specialization might look like:

template <>
struct FromInlet<Car>
{
  Car operator()(const inlet::Container& input_data)
  {
    Car result;
    result.make = input_data["make"];
    result.color = input_data["color"];
    result.seats = input_data["seats"];
    result.horsepower = input_data["horsepower"];
    return result;
  }
};

In the above snippet, Container::operator[] is used to extract data from Inlet’s internal representation which is automatically converted to the correct member variable types when the function’s return value is constructed. This conversion does not happen automatically for user-defined types. If a Car object as defined above is located at the path “car” within the input file, it can be retrieved as follows:

Car car = inlet["car"].get<Car>();

Arrays and Dictionaries of Structs

Arrays of user-defined types are also supported in Inlet.

Defining And Storing

Consider a collection of cars, described in Lua as:

fleet = {
  {
    make = "Globex Corp",
    seats = 3,
    horsepower = 155,
    color = "green"
  },
  {
    make = "Initech",
    seats = 4,
    horsepower = 370
    -- uses default value for color
  },
  {
    make = "Cyberdyne",
    seats = 1,
    horsepower = 101,
    color = "silver"
  }
}

or in YAML, as:

fleet:
  - make: Globex Corp
    seats: 3
    horsepower: 155
    color: green
  - make: Initech
    seats: 4
    horsepower: 370
    # uses default value for color
  - make: Cyberdyne
    seats: 1
    horsepower: 101
    color: silver

First, use the addStructArray function to create a subcontainer, then define the schema on that container using the same Car::defineSchema used above:

  // Create a fleet of cars with the same Car::defineSchema
  auto& fleet_schema = inlet.addStructArray("fleet", "A collection of cars");
  Car::defineSchema(fleet_schema);

Note

The schema definition logic for a struct is identical between individual instances of structs and arrays of structs. The distinction is made by Container on which the struct schema is defined - specifically, whether it is obtained via addStruct or addStructArray.

Associative arrays are also supported, using string keys or a mixture of string and integer keys. The addStructDictionary function can be used analogously to the addStructArray function for these associative arrays.

Note

Although many of Inlet’s input file languages do not distinguish between a “dictionary” type and a “record” type, Inlet treats them differently for type safety reasons:

Dictionaries use arbitrary strings or integers for their keys, and their values (entries) can only be retreived as a homogeneous type. In other words, dictionaries must map to std::unordered_map<Key, Value> for fixed key and value types.

Structs contain a fixed set of named fields, but these fields can be of any type. As the name suggests, these map to structs in C++.

Accessing

As with the schema definition, the FromInlet specialization for a user-defined type will work for both single instances of the type and arrays of the type.

To retrieve an array of structs as a contiguous array of user-defined type, use std::vector:

auto fleet = base["fleet"].get<std::vector<Car>>();

Some input file languages support non-contiguous array indexing, so you can also retrieve arrays as std::unordered_map<int, T>:

auto fleet = inlet["fleet"].get<std::unordered_map<int, Car>>();

Note

If a non-contiguous array is retrieved as a (contiguous) std::vector, the elements will be ordered by increasing index.

String-keyed dictionaries are implemented as std::unordered_map<std::string, T> and can be retrieved in the same way as the array above. For dictionaries with a mix of string and integer keys, the inlet::VariantKey type can be used, namely, by retrieving a std::unordered_map<inlet::VariantKey, T>.