Data Franca API

Github: wayward/support/data_franca

Namespace: wayward::data_franca

Data Franca is a data observation/mutation library. It provides a single, unified interface to querying and modifying arbitrary data structures.

Possible use cases:

Data Franca is based around 4 core concepts:


Spectators


Making a spectator can be constructed from a reference to any object that has an adapter.

std::map<std::string, std::string> dict = {{"foo", "bar"}, {"baz", "car"}};

Spectator spec { dict };
std::string result;
if (spec["foo"] >> result) {
  std::cout << result; // Will print "bar".
}

Scalar values (strings, numbers) are extracted with operator>>, which returns a boolean that is true if the value could be successfully extracted to the requested type. Extraction attempts to convert the underlying value to the requested type, so if the underlying type is a string, but an integer or float is requested, it will be interpreted as a string representation of a number. Extraction as string always succeeds, unless the accessed value is a dictionary or list.

Compound values (dictionaries, lists) are accessed with operator[]. If called with a string argument, the value is assumed to be a dictionary, and if called with an integer argument, it is assumed to be a list. If the underlying type is not the requested container type (e.g. if a list or scalar is accessed with a string subscript), an empty spectator representing Nothing will be returned.

type

Returns: DataType

One of DataType::Nothing, DataType::Boolean, DataType::Integer, DataType::Real, DataType::String, DataType::List, DataType::Dictionary.

is_nothing

Returns: bool

True if the represented value is conceptually "nothing" (Nothing, NULL, etc.).

operator bool

Returns: bool

True if the represented value is not conceptually "nothing". Reverse of is_nothing.

operator>>(Boolean&)

Returns: bool

Extract boolean value. Returns true if the underlying type is a boolean, or could be converted to a boolean.

operator>>(Integer&)

Returns: bool

Extract integer value. Returns true if the underlying type is an integer, or could be converted to an integer.

operator>>(Real&)

Returns: bool

Extract float value. Returns true if the underlying type is a float, or could be converted to a float.

operator>>(String&)

Returns: bool

Extract string value. Returns true if the underlying type is a string, or could be converted to a string.

operator

Returns: Spectator

Access value as a dictionary. If the value is not a dictionary, or the key does not exist in the dictionary, a spectator representing "nothing" is returned.

operator

Returns: Spectator

Access value as a list. If the value is not a list, or the index is out of bounds, a spectator representing "nothing" is returned.

has_key

Invoke: has_key(key)

Returns: bool

True if the underlying value is a dictionary and has key key.

length

Invoke: length()

Returns: size_t

If the underlying value is a dictionary or list, returns the number of pairs or elements. Otherwise, returns 0.

begin

Invoke: begin()

Returns: An iterator that can be used to enumerate all elements or pairs of the underlying list or dictionary, or "end" if they are empty, or "end" if the underlying value is not a container.

end

Invoke: end()

Returns: An iterator pointing to the conceptual "end" (nothing). May never be dereferenced.


Mutators


Mutators are used like Spectators, except they can also modify the underlying data structures.

Example:

std::map<std::string, std::string> dict = {{"foo", "bar"}, {"baz", "car"}};

Mutator mut { dict };
if (mut["foo"] << "lol") {
  // dict["foo"] == "lol"
}

In contrast to spectators, type coercion is enforced at the adapter level — some adapters allow changing the type of represented values at runtime, while other don't, so the mutator interface can't make the decision. Therefore, setting a value can fail, and the return value of operator<< determines if the write was successful.

In addition to the interface provided by Spectators, they implement the following:

operator<<(NothingType)

Returns: bool

Set the represented value to Nothing.

operator<<(Boolean)

Returns: bool

Set the represented value to the boolean value.

operator<<(Integer)

Returns: bool

Set the represented value to the integer value.

operator<<(Real)

Returns: bool

Set the represented value to the floating-point value.

operator<<(String)

Returns: bool

Set the represented value to the string value.

operator

Returns: Mutator

Access dictionary value. Some adapters may coerce the underlying type to become a dictionary.

operator

Returns: Mutator

Access list element. Some adapters may coerce the underlying type to become a list.

erase

Invoke: erase(std::string)

Returns: bool

Erase the dictionary element. Returns true if the erase was successful.

push_back

Invoke: push_back(value)

Returns: bool

Append a value to the end of a list. Some adapters may coerce the underlying type to become a list.


Adapters


Implementing custom adapters

To allow inspection of a custom type, implement Adapter<T>, where T is your custom type.

Adapter<T> must implement the interface IAdapter, which implies IReader and IWriter.

Relevant interfaces:


Objects


A Data Franca Object implements the Mutator interface (including the Spectator interface), but owns its own data.

Write-operations on objects always convert the internal type of the object to match the corresponding write operation. Therefore, a write operation on an Object never fails.

Example:

Object o;
o["foo"]["bar"] << 123;
o["lol"].push_back("baz");
o["lol"].push_back("boo");

If o in the above were converted to, say, JSON, it would look like this:

{
  "foo": {
    "bar": 123
  },
  "lol": ["baz", "boo"]
}