Retrieving Values #
This section explains how to access the values stored in toml::value
.
Accessing Values Using Member Functions #
is_something
and as_something
#
toml::value
has member functions like is_boolean()
and is_integer()
which allow you to check the type of the stored value.
Additionally, it has member functions like as_boolean()
and as_integer()
that allow you to access the value of that type.
For a complete list, refer to the toml::value
reference.
toml::value v = /* ... */;
if(v.is_string())
{
std::cout << v.as_string() << std::endl;
}
If the stored value is of a different type than specified, a toml::type_error
is thrown.
The what()
method will contain a message like the following:
[error] toml::value::as_string(): bad_cast to string
--> input.toml
|
1 | a = 123_456
| ^^^^^^^-- the actual type is integer
toml::value_t
#
Type information can be identified using enum class toml::value_t
.
The type()
member function returns the type information of the currently stored value.
toml::value v = /* ... */;
switch(v.type())
{
case toml:value_t::empty : { /*...*/ break; }
case toml:value_t::boolean : { /*...*/ break; }
case toml:value_t::integer : { /*...*/ break; }
case toml:value_t::floating : { /*...*/ break; }
case toml:value_t::string : { /*...*/ break; }
case toml:value_t::offset_datetime: { /*...*/ break; }
case toml:value_t::local_datetime : { /*...*/ break; }
case toml:value_t::local_date : { /*...*/ break; }
case toml:value_t::local_time : { /*...*/ break; }
case toml:value_t::array : { /*...*/ break; }
case toml:value_t::table : { /*...*/ break; }
default: {break;}
}
The is(toml::value_t)
member function returns true
if the stored value is of the given value_t
type, otherwise it returns false
.
toml::value v = /* ... */;
if(v.is(toml::value_t::integer))
{
std::cout << v.as_integer() << std::endl;
}
at
, []
, contains
, size
, push_back
, emplace_back
#
toml::value
provides some member functions similar to those of standard library containers.
These functions internally convert toml::value
to the corresponding type and call the respective member functions.
at(std::size_t i)
, operator[](std::size_t i)
#
These are equivalent to as_array().at(i)
and as_array()[i]
.
toml::value
uses std::vector<toml::value>
as array_type
by default, so at
throws std::out_of_range
on error, while operator[]
results in undefined behavior.
toml::value v(toml::array{1,2,3});
std::cout << v.at(1);
If the stored type is not array_type
, a type_error
is thrown.
at(std::string key)
, operator[](std::string key)
#
These are equivalent to as_table().at(key)
and as_table()[key]
.
toml::value
uses std::unordered_map<std::string, toml::value>
as table_type
by default, so if the corresponding value does not exist, at
throws std::out_of_range
, while operator[]
constructs a new toml::value
and returns a reference to it. Therefore, there is no const
version of operator[]
.
toml::value v(toml::table{});
v["a"] = 42;
If the stored type is not table_type
, a type_error
is thrown.
size()
#
Returns the length.
For array_type
or table_type
, it returns the number of elements; for string_type
, it returns the number of characters (in bytes).
If the stored type is none of these, a type_error
is thrown.
push_back()
, emplace_back()
#
These are equivalent to as_array().push_back()
and as_array().emplace_back()
.
If the stored type is not array_type
, a type_error
is thrown.
Accessing Comments #
In toml11, comments are parsed by default and saved line by line with the corresponding value.
A comment corresponds to the value that comes immediately after the consecutive lines of comments, or to the value on the same line as the comment.
If there is no value immediately before or after the comment, it is not associated with any value and is ignored.
# input.toml
# This is a comment about a.
a = 42
b = 3.14 # This is a comment about b.
# This comment is ignored because it has no corresponding value.
# This is the 1st comment about c.
# This is the 2nd comment about c.
c = "foo" # This is the final comment about c.
# This comment is NOT a comment about c.
Comments corresponding to a value can be accessed using the comments()
member function of toml::value
.
comments()
returns a container that has the same member functions as std::vector<std::string>
.
const auto v = toml::parse("input.toml");
const auto& a = v.at("a");
const auto& b = v.at("b");
const auto& c = v.at("c");
assert(a.comments().size() == 1);
assert(a.comments().at(0) == "# This is a comment about a.");
assert(b.comments().size() == 1);
assert(b.comments().at(0) == "# This is a comment about b.");
assert(c.comments().size() == 3);
assert(c.comments().at(0) == "# This is the 1st comment about c.");
assert(c.comments().at(1) == "# This is the 2nd comment about c.");
assert(c.comments().at(2) == "# This is the final comment about c.");
Comments related to the root table of the entire file are written at the beginning of the file.
# This is a comment about the root table.
# This is also a comment about the root table.
# This comment is ignored.
# This is a comment about a.
a = 42
However, if a value immediately follows the initial comment, the comment is interpreted as pertaining to that value, and there are no comments for the root table.
# This is a comment about a.
# This is also a comment about a.
a = 42
Handling Inline Tables and Dotted Keys #
An inline table is simply a table, and there is no difference in handling it compared to other tables in C++ code.
a = {b = 42, c = "foo"}
Dotted keys are also just tables, with no difference in handling compared to other tables in C++ code.
a.b = 42
a.c = "foo"
These TOML files are identical to the following file.
[a]
b = 42
c = "foo"
Thus, they can all be processed with the exact same code.
const auto input = toml::parse("input.toml");
assert(input.at("a").at("b").as_integer() == 42);
assert(input.at("a").at("c").as_string() == "foo");
However, it is possible to distinguish them based on format information.
const auto input = toml::parse("input.toml");
switch(input.at("a").as_table_fmt().fmt)
{
case toml::table_format::oneline:
{
std::cout << "inline table" << std::endl;
break;
}
case toml::table_format::multiline:
{
std::cout << "normal table" << std::endl;
break;
}
case toml::table_format::dotted:
{
std::cout << "dotted keys" << std::endl;
break;
}
}
This format information is also considered during serialization, which will be described later.
Handling Date and Time Information #
local_date
,
local_time
,
local_datetime
, and
offset_datetime
are parsed into dedicated structures with corresponding member variables in toml11.
When using these, you can directly extract the values or use toml::get
and toml::find
to convert them into types such as std::chrono::system_clock::time_point
or std::tm
.
Converting Using toml::get<T>
#
toml::get<T>
is a function that converts and retrieves the value stored in toml::value
.
Specify the desired target type as T
.
const toml::value v = /*...*/;
std::cout << toml::get<int>(v) << std::endl;
The toml::find<T>
function, described later, also has the same functionality for type conversion.
If an unsupported type is specified for the stored value, a toml::type_error
is thrown.
Simple Conversions #
boolean_type #
Conversion from boolean_type
is possible only to bool
.
integer_type #
Any type for which std::is_integral<T>
is true
, except bool
, can be converted from integer_type
.
toml::value v(42);
const auto u32 = toml::get<std::uint32_t>(v);
const auto i16 = toml::get<short>(v);
floating_type #
Any type for which std::is_floating_point<T>
is true
can be converted from floating_type
.
toml::value v(3.14);
const auto f64 = toml::get<double>(v);
const auto f32 = toml::get<float >(v);
string_type #
string_type
can be converted to std::string
.
From C++17 onwards, it can also be converted to std::string_view
.
toml::value v("foo");
const auto s = toml::get<std::string>(v);
// C++17
const auto sv = toml::get<std::string_view>(v);
datetime variants #
local_date
,
local_datetime
, and
offset_datetime
represent specific dates and times,
so they can be converted to std::chrono::system_clock::time_point
.
However, since local_time
does not include date information, it supports conversion to std::chrono::duration
as the elapsed time from 00:00.00
.
Additionally, local_date
and local_datetime
conversions take the executing machine’s timezone into account.
date = 2024-01-23
time = 12:30:00
l_dt = 2024-01-23T12:30:00
o_dt = 2024-01-23T12:30:00+09:00
const auto input = toml::parse("input.toml");
const auto date = toml::get<std::chrono::system_clock::time_point>(input.at("date"));
const auto l_dt = toml::get<std::chrono::system_clock::time_point>(input.at("l_dt"));
const auto o_dt = toml::get<std::chrono::system_clock::time_point>(input.at("o_dt"));
const auto time = toml::get<std::chrono::minutes>(input.at("time")); // 12 * 60 + 30 min
Conditions for Obtaining References #
toml::get<T>
can return a reference if T
is the exact type stored in toml::value
.
Conversely, if a conversion is necessary (e.g., extracting an integer stored as std::int64_t
into std::uint32_t
), it is not possible to return a reference to the converted type.
When no conversion is needed, the returned reference can be used to modify the value.
toml::value v(42);
toml::get<toml::value::integer_type>(v) = 6 * 9;
assert(v.as_integer() == 54);
Converting Arrays to STL Containers #
If all elements in an array have the same type and can be converted to T
, it is possible to convert them to std::vector<T>
.
a = [1, 2, 3, 4, 5]
const auto a = toml::get<std::vector<int>>(input.at("a"));
Other STL containers can also be used.
const auto a1 = toml::get<std::deque<int>>(input.at("a"));
const auto a2 = toml::get<std::list <int>>(input.at("a"));
const auto a3 = toml::get<std::array<int, 5>>(input.at("a"));
When converting to std::array
, the number of elements must match. If they don’t match, a std::out_of_range
exception is thrown.
Non-STL containers that have a default constructor and a push_back
method can also be converted using toml::get
.
const auto a = toml::get<boost::container::small_vector<int, 8>>(input.at("a"));
Converting Arrays to std::pair
or std::tuple
#
If an array contains elements of different types, it can be converted to std::pair
or std::tuple
.
a = [true, 3.14]
b = [42, 2.718, "foo"]
const auto a = toml::get<std::pair<bool, double>>(input.at("a"));
const auto b = toml::get<std::tuple<int, double, std::string>>(input.at("b"));
As with std::array
, the length of the array must match the number of elements in the std::pair
or std::tuple
. If they don’t match, a std::out_of_range
exception is thrown.
Additionally, each element must be convertible to the corresponding element in the std::pair
or std::tuple
. If conversion is not possible, a toml::type_error
exception is thrown.
Converting Nested Arrays #
Nested arrays can be converted to nested containers.
a = [ [1, 2, 3], [4, 5, 6] ]
const auto a = toml::get<std::vector<std::vector<int>>>(input.at("a"));
If the types are different, std::pair
or std::tuple
can be useful.
a = [ [1, 2, 3], ["foo", "bar"] ]
const auto a = toml::get<
std::pair<std::vector<int>, std::vector<std::string>>
>(input.at("a"));
Converting Tables to std::map
#
If all values in a table have the same type, they can be converted to std::map
or std::unordered_map
.
t = {a = 1, b = 2}
const auto t = toml::get<std::map<std::string, int>>(input.at("t"));
Non-STL containers that have a default constructor and an emplace(key, mapped)
method can also be converted using toml::get
.
const auto t = toml::get<boost::container::flat_map<std::string, int>>(input.at("t"));
If the conversion of any element fails, a toml::type_error
exception is thrown.
Using toml::get_or
to Specify a Value on Failure
#
toml::get
throws a toml::type_error
exception if the conversion fails.
By using toml::get_or
, you can specify a default value to return instead of an exception in case of a conversion failure.
Unlike toml::get<T>
, get_or
infers the target type from the arguments, so there is no need to specify <T>
.
const auto a = toml::get_or(input.at("a"), 42);
The types that can be converted are the same as for toml::get
.
If you specify toml::value::xxx_type
, you can also retrieve a reference, but in that case, the argument must also be a reference.
toml::value::integer_type a_default = 42;
auto a& = toml::get_or(input.at("a"), a_default);
Using toml::find<T>
to Search and Convert Simultaneously
#
toml::find<T>
is a function that searches for a value in a toml::value
holding a table and simultaneously performs the same type conversion as toml::get
.
const auto a = toml::find<int>(input, "a");
// Equivalent to: const auto a = toml::get<int>(input.at("a"));
toml::find<T>
can also be used with arrays.
const auto a = input.at("a");
const auto a2 = toml::find<int>(a, 2);
// Equivalent to: const auto a2 = toml::get<int>(input.at("a").at(2));
If an error occurs during type conversion, toml::find<T>
throws the same toml::type_error
as toml::get
.
If the key is not found or the index does not exist, it throws std::out_of_range
.
If no type is specified, toml::find
returns a toml::value
without type conversion.
const auto a = toml::find(input, "a");
// Equivalent to: const auto a = input.at("a");
toml::find<T>
can access values recursively.
a = {b = {c = 42}}
const auto a_b_c = toml::find<int>(input, "a", "b", "c");
// Equivalent to: const auto a = toml::get<int>(input.at("a").at("b").at("c"));
You can mix keys and indexes.
a = [ {b = 1}, {b = 2}, {b = 3} ]
const auto a_2_b = toml::find<int>(input, "a", 2, "b");
// Equivalent to: const auto a = toml::get<int>(input.at("a").at(2).at("c"));
TOML supports a feature called quoted keys, which allows using normally disallowed characters in keys by enclosing them in
""
or''
. Within quoted keys,.
does not introduce tables."127.0.0.1" = "value" site."google.com" = true
You can read this TOML file as follows:
const auto input = toml::parse("input.toml"); assert(input.at("127.0.0.1").as_string() == "value"); assert(input.at("site").at("google.com").as_boolean());
To handle such cases seamlessly, toml11 does not automatically split keys containing
.
. Explicitly specifying the table hierarchy helps in structuring the input file correctly.Reference: toml.io/Key
Using toml::find_or
to Search and Specify a Value on Failure
#
toml::find_or
works similarly to toml::find<T>
, but allows specifying a default value to return if the search or conversion fails.
This is useful when you want to provide a fallback value instead of handling exceptions.
const auto a = toml::find_or(input, "a", 42);
Defining Conversions for User-Defined Types #
With toml::get
and toml::find
, you can use user-defined types by employing one of the following methods.
Defining toml::from
#
In toml11, there is a toml::from
type that supports conversions from user-defined types by specializing it as follows:
namespace extlib
{
struct foo
{
int a;
std::string b;
};
} // extlib
namespace toml
{
template<>
struct from<extlib::foo>
{
static extlib::foo from_toml(const toml::value& v)
{
return extlib::foo{
toml::find<int>(v, "a"),
toml::find<std::string>(v, "b")
};
}
};
} // toml
If you also want to support toml::value
with a modified type configuration, do as follows:
namespace extlib
{
struct foo
{
int a;
std::string b;
};
} // extlib
namespace toml
{
template<>
struct from<extlib::foo>
{
template<typename TC>
static extlib::foo from_toml(const toml::basic_value<TC>& v)
{
return extlib::foo{
toml::find<int>(v, "a"),
toml::find<std::string>(v, "b")
};
}
};
} // toml
This definition can be automatically generated using TOML11_DEFINE_CONVERSION_NON_INTRUSIVE
.
namespace extlib
{
struct foo
{
int a;
std::string b;
};
} // extlib
TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(extlib::foo, a, b)
Alternatively, you can use a reflection library. Refer to the sample in example
using boost-ext/reflect
.
Defining a from_toml
Member Function
#
You can also define a conversion by defining a from_toml
member function.
When using this method, a default constructor is required.
struct bar
{
int a;
std::string b;
void from_toml(const toml::value& v)
{
this->a = toml::find<int>(v, "a");
this->b = toml::find<std::string>(v, "b");
return ;
}
};
If both are defined, toml::from
takes precedence.
Constructor Accepting toml::value
#
If there is a constructor that accepts toml::value
, conversion via toml::get
can be performed.
struct baz
{
explicit baz(const toml::value& v)
: a(toml::find<int>(v, "a")), b(toml::find<std::string>(v, "b"))
{}
int a;
std::string b;
};
If both are defined, toml::from
and from_toml
take precedence.
Applying Functions with toml::visit
#
If you have a function object that can be applied to all types stored in toml::value
, you can directly call that function without type conversion using toml::visit
.
struct type_name_of
{
std::string operator()(const toml::value::boolean_type &) const {return "boolean";}
std::string operator()(const toml::value::integer_type &) const {return "integer";}
std::string operator()(const toml::value::floating_type &) const {return "floating";}
std::string operator()(const toml::value::string_type &) const {return "string";}
std::string operator()(const toml::value::local_time_type &) const {return "local_time";}
std::string operator()(const toml::value::local_date_type &) const {return "local_date";}
std::string operator()(const toml::value::local_datetime_type &) const {return "local_datetime";}
std::string operator()(const toml::value::offset_datetime_type&) const {return "offset_datetime";}
std::string operator()(const toml::value::array_type &) const {return "array";}
std::string operator()(const toml::value::table_type &) const {return "table";}
};
toml::value v(3.14);
std::cout << toml::visit(type_name_of{}, v) << std::endl; // floating
Constructing toml::value
#
toml::value
can be constructed not only internally by the parser but also in user code.
You can construct it by passing a type that is either the same as or convertible to the types stored in toml::value
.
toml::value v1(true);
toml::value v2(42);
toml::value v3(3.14);
For arrays, you can use toml::array
:
toml::value v(toml::array{1, 2, 3});
Alternatively, you can pass containers such as std::vector
directly:
const std::vector<toml::value> a{1,2,3};
toml::value v(a);
For tables, you can use toml::table
:
toml::value v(toml::table{{"foo", 1}, {"bar", 2}, {"baz", 3}});
Or pass containers such as std::map
directly:
const std::map<std::string, toml::value> t{
{"foo", 1}, {"bar", 2}, {"baz", 3}
}
toml::value v(t);
You can pass format_info
and comments to the constructor.
The type of comments is std::vector<std::string>
.
Each element corresponds to a line.
toml::integer_format_info fmt;
fmt.fmt = toml::integer_format::hex;
fmt.spacer = 4;
toml::value v1(0xDEADBEEF, fmt);
toml::value v2(0xC0FFEE, fmt, {"hex value!"});
Converting to toml::value
#
When constructing toml::value
from user-defined types, you can customize the behavior by defining toml::into
or into_toml
.
toml::into
is particularly useful when converting types from other libraries.
Defining toml::into
#
By specializing toml::into
, you can enable conversions to toml::value
.
This is useful for types from external libraries that do not provide a conversion to toml::value
.
Since toml::value
passes type_config
during conversion, you need to accept the template
argument of basic_value
.
namespace extlib
{
struct foo
{
int a;
std::string b;
};
} // extlib
template<>
struct into<extlib::foo>
{
template<typename TC>
static toml::basic_value<TC> into_toml(const extlib::foo& f)
{
return toml::basic_value<TC>(typename toml::basic_value<TC>::table_type{{"a", f.a}, {"b", f.b}});
}
};
Defining into_toml
Member Function
#
Similar to from_toml
, you can define the conversion through a member function.
If toml::into
is defined, it takes precedence.
struct bar
{
int a;
std::string b;
template<typename TC>
toml::basic_value<TC> into_toml() const
{
return toml::basic_value<TC>(typename toml::basic_value<TC>::table_type{
{"a", this->a}, {"b", this->b}
});
}
};