changeset 194:61dd98874d82

Common: remove util and import json_util, closes #938 @2h
author David Demelier <markand@malikania.fr>
date Sun, 28 Oct 2018 10:01:44 +0100
parents 209bdaa13a49
children ae3741be8c38
files libmlk-client/malikania/client/loader.cpp libmlk-client/malikania/client/loader.hpp libmlk-server/malikania/server/net/auth_handler.cpp libmlk-server/malikania/server/server.cpp libmlk/CMakeLists.txt libmlk/malikania/json_util.cpp libmlk/malikania/json_util.hpp libmlk/malikania/loader.cpp libmlk/malikania/loader.hpp libmlk/malikania/util.cpp libmlk/malikania/util.hpp tests/libmlk/CMakeLists.txt tests/libmlk/util/CMakeLists.txt tests/libmlk/util/main.cpp tests/tools/CMakeLists.txt tools/tileset/main.cpp
diffstat 16 files changed, 585 insertions(+), 1338 deletions(-) [+]
line wrap: on
line diff
--- a/libmlk-client/malikania/client/loader.cpp	Sat Oct 27 07:16:28 2018 +0200
+++ b/libmlk-client/malikania/client/loader.cpp	Sun Oct 28 10:01:44 2018 +0100
@@ -1,5 +1,5 @@
 /*
- * client_resources_loader.cpp -- load shared resources files for the client
+ * loader.cpp -- load shared resources files for the client
  *
  * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
  *
@@ -20,7 +20,6 @@
 
 #include <malikania/locator.hpp>
 #include <malikania/size.hpp>
-#include <malikania/util.hpp>
 
 #include "animation.hpp"
 #include "font.hpp"
@@ -28,7 +27,9 @@
 #include "loader.hpp"
 #include "sprite.hpp"
 
-using namespace nlohmann;
+using nlohmann::json;
+
+using mlk::json_util::deserializer;
 
 namespace mlk::client {
 
@@ -39,19 +40,21 @@
 	animation::frame_list frames;
 
 	for (const auto& v : value) {
+		const deserializer doc(v);
+
 		if (!v.is_object())
 			throw std::runtime_error("not a JSON object");
 
-		const auto cell = v.find("cell");
-		const auto delay = v.find("delay");
+		const auto cell = doc.get<unsigned>("cell");
+		const auto delay = doc.get<unsigned>("delay");
 
-		if (cell == v.end() || !cell->is_number_unsigned())
-			throw std::runtime_error("invalid cell");
-		if (delay == v.end() || !delay->is_number_unsigned())
+		if (!cell)
+			throw std::runtime_error("invalid cell property in animation object");
+		if (!delay)
 			throw std::runtime_error("invalid delay");
 
 		// TODO: range check.
-		frames.push_back({cell->get<unsigned>(), delay->get<unsigned>()});
+		frames.push_back({*cell, *delay});
 	}
 
 	return frames;
@@ -76,33 +79,47 @@
 
 auto loader::load_sprite(std::string_view id) -> sprite
 {
-	auto value = json::parse(get_locator().read(id));
+	const deserializer doc(json::parse(get_locator().read(id)));
 
-	if (!value.is_object())
+	if (!doc.is_object())
 		throw std::runtime_error(": not a JSON object");
 
-	return sprite(
-		load_image(util::json::require_string(value, "/image"_json_pointer)),
-		util::json::require_size(value, "/cell"_json_pointer),
-		util::json::get_size(value, "/margin"_json_pointer),
-		util::json::get_size(value, "/space"_json_pointer),
-		util::json::get_size(value, "/size"_json_pointer)
-	);
+	const auto image = doc.get<std::string>("image");
+	const auto cell = doc.get<size>("cell");
+	const auto margin = doc.optional<size>("margin", size{});
+	const auto space = doc.optional<size>("space", size{});
+	const auto geom = doc.optional<size>("size", size{});
+
+	if (!image)
+		throw std::runtime_error("invalid image property in sprite object");
+	if (!cell)
+		throw std::runtime_error("invalid cell property in sprite object");
+	if (!margin)
+		throw std::runtime_error("invalid margin property in sprite object");
+	if (!space)
+		throw std::runtime_error("invalid space property in sprite object");
+	if (!geom)
+		throw std::runtime_error("invalid size property in sprite object");
+
+	return sprite{load_image(*image), *cell, *margin, *space, *geom};
 }
 
 auto loader::load_animation(std::string_view id) -> animation
 {
-	const auto value = json::parse(get_locator().read(id));
+	const deserializer doc(json::parse(get_locator().read(id)));
 
-	if (!value.is_object())
+	if (!doc.is_object())
 		throw std::runtime_error("not a JSON object");
 
-	const auto s = load_sprite(util::json::require_string(value, "/sprite"_json_pointer));
+	const auto sprite = doc.get<std::string>("sprite");
+	const auto frames = doc.find("frames");
 
-	return {
-		load_sprite(util::json::require_string(value, "/sprite"_json_pointer)),
-		load_animation_frames(util::json::require_array(value, "/frames"_json_pointer))
-	};
+	if (!sprite)
+		throw std::runtime_error("invalid sprite property in animation object");
+	if (!frames->is_array())
+		throw std::runtime_error("invalid frames property in animation object");
+
+	return animation{load_sprite(*sprite), load_animation_frames(*frames)};
 }
 
 } // !mlk::client
--- a/libmlk-client/malikania/client/loader.hpp	Sat Oct 27 07:16:28 2018 +0200
+++ b/libmlk-client/malikania/client/loader.hpp	Sun Oct 28 10:01:44 2018 +0100
@@ -1,5 +1,5 @@
 /*
- * client_resources_loader.hpp -- load shared resources files for the client
+ * loader.hpp -- load shared resources files for the client
  *
  * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
  *
@@ -26,13 +26,7 @@
 
 #include <malikania/loader.hpp>
 
-#include <string>
-
-namespace mlk {
-
-struct size;
-
-namespace client {
+namespace mlk::client {
 
 struct animation;
 
@@ -92,8 +86,6 @@
 	virtual auto load_animation(std::string_view id) -> animation;
 };
 
-} // !client
-
-} // !mlk
+} // !mlk::client
 
 #endif // !MALIKANIA_CLIENT_LOADER_HPP
--- a/libmlk-server/malikania/server/net/auth_handler.cpp	Sat Oct 27 07:16:28 2018 +0200
+++ b/libmlk-server/malikania/server/net/auth_handler.cpp	Sun Oct 28 10:01:44 2018 +0100
@@ -16,7 +16,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <malikania/util.hpp>
+#include <malikania/json_util.hpp>
 
 #include <malikania/error/auth_error.hpp>
 
@@ -26,6 +26,8 @@
 #include "client.hpp"
 #include "server.hpp"
 
+using mlk::json_util::deserializer;
+
 namespace mlk::server {
 
 namespace {
@@ -47,10 +49,16 @@
 
 void auth_handler::exec(server& server, client& clt, nlohmann::json object) noexcept
 {
-	auto ac = db::account::authenticate(
-		util::json::require_string(object, "/login"_json_pointer),
-		util::json::require_string(object, "/password"_json_pointer)
-	);
+	const deserializer doc(std::move(object));
+	const auto login = doc.get<std::string>("login");
+	const auto password = doc.get<std::string>("password");
+
+	if (!login || !password) {
+		clt.fatal("auth", auth_error::invalid_credentials);
+		return;
+	}
+
+	auto ac = db::account::authenticate(*login, *password);
 
 	if (!ac)
 		clt.fatal("auth", auth_error::invalid_credentials);
--- a/libmlk-server/malikania/server/server.cpp	Sat Oct 27 07:16:28 2018 +0200
+++ b/libmlk-server/malikania/server/server.cpp	Sun Oct 28 10:01:44 2018 +0100
@@ -16,8 +16,6 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <malikania/util.hpp>
-
 #include "client.hpp"
 #include "server.hpp"
 
--- a/libmlk/CMakeLists.txt	Sat Oct 27 07:16:28 2018 +0200
+++ b/libmlk/CMakeLists.txt	Sun Oct 28 10:01:44 2018 +0100
@@ -29,13 +29,13 @@
 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/line.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/loader.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/locator.hpp
+	${CMAKE_CURRENT_SOURCE_DIR}/malikania/json_util.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/point.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/rectangle.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/size.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/socket.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/tileset.hpp
 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/unicode.hpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/util.hpp
 )
 
 set(
@@ -43,9 +43,9 @@
 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/error/auth_error.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/loader.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/locator.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/malikania/json_util.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/socket.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/malikania/unicode.cpp
-	${CMAKE_CURRENT_SOURCE_DIR}/malikania/util.cpp
 )
 
 malikania_define_library(
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk/malikania/json_util.cpp	Sun Oct 28 10:01:44 2018 +0100
@@ -0,0 +1,154 @@
+/*
+ * json_util.cpp -- utilities for JSON
+ *
+ * Copyright (c) 2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <limits>
+#include <type_traits>
+
+#include "json_util.hpp"
+
+using nlohmann::json;
+
+namespace mlk::json_util {
+
+namespace {
+
+template <typename Int>
+auto clampi(const json& value) noexcept -> std::optional<Int>
+{
+	static_assert(std::is_signed<Int>::value, "Int must be signed");
+
+	if (!value.is_number_integer())
+		return std::nullopt;
+
+	const auto ret = value.get<std::int64_t>();
+
+	if (ret < std::numeric_limits<Int>::min() || ret > std::numeric_limits<Int>::max())
+		return std::nullopt;
+
+	return static_cast<Int>(ret);
+}
+
+template <typename Int>
+auto clampu(const json& value) noexcept -> std::optional<Int>
+{
+	static_assert(std::is_unsigned<Int>::value, "Int must be unsigned");
+
+	if (!value.is_number_unsigned())
+		return std::nullopt;
+
+	const auto ret = value.get<std::uint64_t>();
+
+	if (ret > std::numeric_limits<Int>::max())
+		return std::nullopt;
+
+	return static_cast<Int>(ret);
+}
+
+} // !namespace
+
+auto type_traits<bool>::get(const json& value) noexcept -> std::optional<bool>
+{
+	if (!value.is_boolean())
+		return std::nullopt;
+
+	return value.get<bool>();
+}
+
+auto type_traits<double>::get(const json& value) noexcept -> std::optional<double>
+{
+	if (!value.is_number_float())
+		return std::nullopt;
+
+	return value.get<double>();
+}
+
+auto type_traits<std::string>::get(const json& value) -> std::optional<std::string>
+{
+	if (!value.is_string())
+		return std::nullopt;
+
+	return value.get<std::string>();
+}
+
+auto type_traits<std::int8_t>::get(const json& value) -> std::optional<std::int8_t>
+{
+	return clampi<std::int8_t>(value);
+}
+
+auto type_traits<std::int16_t>::get(const json& value) -> std::optional<std::int16_t>
+{
+	return clampi<std::int16_t>(value);
+}
+
+auto type_traits<std::int32_t>::get(const json& value) -> std::optional<std::int32_t>
+{
+	return clampi<std::int32_t>(value);
+}
+
+auto type_traits<std::int64_t>::get(const json& value) noexcept -> std::optional<std::int64_t>
+{
+	if (!value.is_number_integer())
+		return std::nullopt;
+
+	return value.get<std::int64_t>();
+}
+
+auto type_traits<std::uint8_t>::get(const json& value) -> std::optional<std::uint8_t>
+{
+	return clampu<std::uint8_t>(value);
+}
+
+auto type_traits<std::uint16_t>::get(const json& value) -> std::optional<std::uint16_t>
+{
+	return clampu<std::uint16_t>(value);
+}
+
+auto type_traits<std::uint32_t>::get(const json& value) -> std::optional<std::uint32_t>
+{
+	return clampu<std::uint32_t>(value);
+}
+
+auto type_traits<std::uint64_t>::get(const json& value) noexcept -> std::optional<std::uint64_t>
+{
+	if (!value.is_number_unsigned())
+		return std::nullopt;
+
+	return value.get<std::uint64_t>();
+}
+
+auto pretty(const json& value, int indent) -> std::string
+{
+	switch (value.type()) {
+	case json::value_t::null:
+		return "null";
+	case json::value_t::string:
+		return value.get<std::string>();
+	case json::value_t::boolean:
+		return value.get<bool>() ? "true" : "false";
+	case json::value_t::number_integer:
+		return std::to_string(value.get<std::int64_t>());
+	case json::value_t::number_unsigned:
+		return std::to_string(value.get<std::uint64_t>());
+	case json::value_t::number_float:
+		return std::to_string(value.get<double>());
+	default:
+		return value.dump(indent);
+	}
+}
+
+} // !mlk::json_util
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk/malikania/json_util.hpp	Sun Oct 28 10:01:44 2018 +0100
@@ -0,0 +1,284 @@
+/*
+ * json_util.hpp -- utilities for JSON
+ *
+ * Copyright (c) 2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MALIKANIA_JSON_UTIL_HPP
+#define MALIKANIA_JSON_UTIL_HPP
+
+/**
+ * \file json_util.hpp
+ * \brief Utilities for JSON.
+ */
+
+#include <cstdint>
+#include <optional>
+#include <string>
+
+#include <json.hpp>
+
+/**
+ * \brief Utilities for JSON.
+ */
+namespace mlk::json_util {
+
+/**
+ * \brief Describe how to convert a JSON value.
+ *
+ * This traits must be specialized for every type you want to convert from JSON
+ * to its native type.
+ *
+ * You only need to implement the get function with the following signature:
+ *
+ * ```cpp
+ * static std::optional<T> get(const nlohmann::json& value);
+ * ```
+ *
+ * The implementation should not throw an exception but return a null optional
+ * instead.
+ *
+ * This traits is already specialized for the given types:
+ *
+ * - bool
+ * - double
+ * - std::uint(8, 16, 32, 64)_t
+ * - std::string
+ */
+template <typename T>
+struct type_traits;
+
+/**
+ * \brief Specialization for `bool`.
+ */
+template <>
+struct type_traits<bool> {
+	/**
+	 * Convert the JSON value to bool.
+	 *
+	 * \param value the value
+	 * \return the bool or empty if not a boolean type
+	 */
+	static auto get(const nlohmann::json& value) noexcept -> std::optional<bool>;
+};
+
+/**
+ * \brief Specialization for `double`.
+ */
+template <>
+struct type_traits<double> {
+	/**
+	 * Convert the JSON value to bool.
+	 *
+	 * \param value the value
+	 * \return the double or empty if not a double type
+	 */
+	static auto get(const nlohmann::json& value) noexcept -> std::optional<double>;
+};
+
+/**
+ * \brief Specialization for `std::string`.
+ */
+template <>
+struct type_traits<std::string> {
+	/**
+	 * Convert the JSON value to std::string.
+	 *
+	 * \param value the value
+	 * \return the string or empty if not a string type
+	 */
+	static auto get(const nlohmann::json& value) -> std::optional<std::string>;
+};
+
+/**
+ * \brief Specialization for `std::int8_t`.
+ */
+template <>
+struct type_traits<std::int8_t> {
+	/**
+	 * Convert the JSON value to std::int8_t.
+	 *
+	 * \param value the value
+	 * \return the value or empty if value does not fit between the range
+	 */
+	static auto get(const nlohmann::json& value) -> std::optional<std::int8_t>;
+};
+
+/**
+ * \brief Specialization for `std::int16_t`.
+ */
+template <>
+struct type_traits<std::int16_t> {
+	/**
+	 * Convert the JSON value to std::int16_t.
+	 *
+	 * \param value the value
+	 * \return the value or empty if value does not fit between the range
+	 */
+	static auto get(const nlohmann::json& value) -> std::optional<std::int16_t>;
+};
+
+/**
+ * \brief Specialization for `std::int32_t`.
+ */
+template <>
+struct type_traits<std::int32_t> {
+	/**
+	 * Convert the JSON value to std::int32_t.
+	 *
+	 * \param value the value
+	 * \return the value or empty if value does not fit between the range
+	 */
+	static auto get(const nlohmann::json& value) -> std::optional<std::int32_t>;
+};
+
+/**
+ * \brief Specialization for `std::int64_t`.
+ */
+template <>
+struct type_traits<std::int64_t> {
+	/**
+	 * Convert the JSON value to std::int64_t.
+	 *
+	 * \param value the value
+	 * \return the int or empty if not a int type
+	 */
+	static auto get(const nlohmann::json& value) noexcept -> std::optional<std::int64_t>;
+};
+
+/**
+ * \brief Specialization for `std::uint8_t`.
+ */
+template <>
+struct type_traits<std::uint8_t> {
+	/**
+	 * Convert the JSON value to std::uint8_t.
+	 *
+	 * \param value the value
+	 * \return the value or empty if value does not fit between the range
+	 */
+	static auto get(const nlohmann::json& value) -> std::optional<std::uint8_t>;
+};
+
+/**
+ * \brief Specialization for `std::uint16_t`.
+ */
+template <>
+struct type_traits<std::uint16_t> {
+	/**
+	 * Convert the JSON value to std::uint16_t.
+	 *
+	 * \param value the value
+	 * \return the value or empty if value does not fit between the range
+	 */
+	static auto get(const nlohmann::json& value) -> std::optional<std::uint16_t>;
+};
+
+/**
+ * \brief Specialization for `std::int32_t`.
+ */
+template <>
+struct type_traits<std::uint32_t> {
+	/**
+	 * Convert the JSON value to std::uint32_t.
+	 *
+	 * \param value the value
+	 * \return the value or empty if value does not fit between the range
+	 */
+	static auto get(const nlohmann::json& value) -> std::optional<std::uint32_t>;
+};
+
+/**
+ * \brief Specialization for `std::uint64_t`.
+ */
+template <>
+struct type_traits<std::uint64_t> {
+	/**
+	 * Convert the JSON value to std::uint64_t.
+	 *
+	 * \param value the value
+	 * \return the int or empty if not a int type
+	 */
+	static auto get(const nlohmann::json& value) noexcept -> std::optional<std::uint64_t>;
+};
+
+/**
+ * \brief Convenient JSON object parser
+ *
+ * This class helps destructuring insecure JSON input by returning optional
+ * values if they are not present or invalid.
+ */
+class deserializer : public nlohmann::json {
+public:
+	/**
+	 * Inherited constructor.
+	 */
+	deserializer(nlohmann::json value) noexcept
+		: nlohmann::json(std::move(value))
+	{
+	}
+
+	/**
+	 * Get a value from the document object.
+	 *
+	 * \param key the property key
+	 * \return the value or std::nullopt if not found or not convertible
+	 */
+	template <typename Type>
+	auto get(const std::string& key) const noexcept -> std::optional<Type>
+	{
+		const auto it = find(key);
+
+		if (it == end())
+			return std::nullopt;
+
+		return type_traits<Type>::get(*it);
+	}
+
+	/**
+	 * Get an optional value from the document object.
+	 *
+	 * If the value is undefined, the default value is returned. Otherwise, if
+	 * the value is not in the given type, std::nullopt is returned.
+	 *
+	 * \param key the property key
+	 * \param def the default value if property is undefined
+	 * \return the value, std::nullopt or def
+	 */
+	template <typename Type, typename DefaultValue>
+	auto optional(const std::string& key, DefaultValue&& def) const noexcept -> std::optional<Type>
+	{
+		const auto it = find(key);
+
+		if (it == end())
+			return std::optional<Type>(std::forward<DefaultValue>(def));
+
+		return type_traits<Type>::get(*it);
+	}
+};
+
+/**
+ * Print the value as human readable.
+ *
+ * \note This only works on flat objects.
+ * \param value the value
+ * \param indent the optional indent for objects/arrays
+ * \return the string
+ */
+auto pretty(const nlohmann::json& value, int indent = 4) -> std::string;
+
+} // !mlk::json_util
+
+#endif // !JSON_UTIL_HPP
--- a/libmlk/malikania/loader.cpp	Sat Oct 27 07:16:28 2018 +0200
+++ b/libmlk/malikania/loader.cpp	Sun Oct 28 10:01:44 2018 +0100
@@ -17,16 +17,24 @@
  */
 
 #include "game.hpp"
+#include "json_util.hpp"
 #include "loader.hpp"
 #include "locator.hpp"
 #include "tileset.hpp"
-#include "util.hpp"
+
+using nlohmann::json;
 
 namespace mlk {
 
+using json_util::deserializer;
+
 namespace {
 
-auto load_tileset_tile_properties(const nlohmann::json& json) -> tileset::properties_map
+#if 0
+
+// TODO: rework tileset.
+
+auto load_tileset_tile_properties(const json& json) -> tileset::properties_map
 {
 	auto it = json.find("properties");
 
@@ -46,7 +54,7 @@
 	return props;
 }
 
-auto load_tileset_tiles_properties(const nlohmann::json& json) -> tileset::tile_properties_map
+auto load_tileset_tiles_properties(const json& json) -> tileset::tile_properties_map
 {
 	auto tiles = json.find("tiles");
 
@@ -61,6 +69,8 @@
 	return props;
 }
 
+#endif
+
 } // !namespace
 
 loader::loader(mlk::locator& locator)
@@ -75,34 +85,63 @@
 
 auto loader::load_game() const -> game
 {
-	auto value = nlohmann::json::parse(locator_.read("game.json"));
+	const deserializer doc(json::parse(locator_.read("game.json")));
+
+	if (!doc.is_object())
+		throw std::runtime_error("not a JSON object");
 
-	if (!value.is_object())
-		throw std::runtime_error("game.json: not a JSON object");
+	const auto name = doc.get<std::string>("name");
+	const auto version = doc.get<std::string>("version");
+	const auto requires = doc.get<std::string>("requires");
+	const auto license = doc.optional<std::string>("license", "");
+	const auto author = doc.optional<std::string>("author", "");
 
-	return game{
-		util::json::require_string(value, "/name"_json_pointer),
-		util::json::require_string(value, "/version"_json_pointer),
-		util::json::require_string(value, "/requires"_json_pointer),
-		value.count("license") > 0 ? value["license"] : "",
-		value.count("author") > 0 ? value["author"] : ""
-	};
+	if (!name)
+		throw std::runtime_error("invalid name property in game object");
+	if (!version)
+		throw std::runtime_error("invalid version property in game object");
+	if (!requires)
+		throw std::runtime_error("invalid requires property in game object");
+	if (!license)
+		throw std::runtime_error("invalid license property in game object");
+	if (!author)
+		throw std::runtime_error("invalid author property in game object");
+
+	return game{*name, *version, *requires, *license, *author};
 }
 
 auto loader::load_tileset(std::string_view id) const -> tileset
 {
-	auto value = nlohmann::json::parse(locator_.read(id));
+	const deserializer doc(json::parse(locator_.read(id)));
 
-	if (!value.is_object())
+	if (!doc.is_object())
 		throw std::runtime_error("not a valid tileset");
 
-	tileset tileset;
+	const auto image = doc.get<std::string>("image");
+	const auto cell = doc.get<size>("cell");
 
-	tileset.image = util::json::require_string(value, "/image"_json_pointer);
-	tileset.cell = util::json::require_size(value, "/cell"_json_pointer);
-	tileset.tile_properties = load_tileset_tiles_properties(value);
+	if (!image)
+		throw std::runtime_error("invalid image property in tileset object");
+	if (!cell)
+		throw std::runtime_error("invalid cell property in tileset object");
 
-	return tileset;
+	return tileset{*image, *cell, {}, {}};
 }
 
+namespace json_util {
+
+auto type_traits<size>::get(const json& value) noexcept -> std::optional<size>
+{
+	const deserializer doc(value);
+	const auto width = doc.get<unsigned>("width");
+	const auto height = doc.get<unsigned>("height");
+
+	if (!width || !height)
+		return std::nullopt;
+
+	return size({*width, *height});
+}
+
+} // !json_util
+
 } // !mlk
--- a/libmlk/malikania/loader.hpp	Sat Oct 27 07:16:28 2018 +0200
+++ b/libmlk/malikania/loader.hpp	Sun Oct 28 10:01:44 2018 +0100
@@ -21,6 +21,10 @@
 
 #include <string>
 
+#include <malikania/json_util.hpp>
+
+#include "size.hpp"
+
 namespace mlk {
 
 struct game;
@@ -78,6 +82,21 @@
 	virtual auto load_tileset(std::string_view id) const -> tileset;
 };
 
+namespace json_util {
+
+template <>
+struct type_traits<size> {
+	/**
+	 * Convert the JSON value to std::uint64_t.
+	 *
+	 * \param value the value
+	 * \return the int or empty if not a int type
+	 */
+	static auto get(const nlohmann::json& value) noexcept -> std::optional<size>;
+};
+
+} // !json_util
+
 } // !mlk
 
 #endif // !MALIKANIA_LOADER_HPP
--- a/libmlk/malikania/util.cpp	Sat Oct 27 07:16:28 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,443 +0,0 @@
-/*
- * util.cpp -- malikania utilities
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cassert>
-#include <climits>
-#include <string>
-#include <stdexcept>
-
-#if defined(__linux__)
-#  include <unistd.h>
-
-#  include <cerrno>
-#  include <cstring>
-#  include <stdexcept>
-#elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__)
-#  if defined(__NetBSD__)
-#    include <sys/param.h>
-#  else
-#    include <sys/types.h>
-#  endif
-
-#  if defined(__OpenBSD__)
-#    include <unistd.h>
-#  endif
-
-#  include <sys/sysctl.h>
-
-#  include <array>
-#  include <cerrno>
-#  include <climits>
-#  include <cstddef>
-#  include <cstdlib>
-#  include <cstring>
-#  include <stdexcept>
-#elif defined(__APPLE__)
-#  include <cerrno>
-#  include <cstring>
-#  include <libproc.h>
-#  include <unistd.h>
-#elif defined(_WIN32)
-#  include <Windows.h>
-#endif
-
-#include <boost/filesystem.hpp>
-
-#include "util.hpp"
-#include "size.hpp"
-
-namespace {
-
-#if defined(__linux__)
-
-std::string executable_path()
-{
-    std::string result;
-
-    result.resize(2048, '\0');
-
-    auto size = readlink("/proc/self/exe", &result[0], 2048);
-
-    if (size < 0) {
-        throw std::runtime_error(std::strerror(errno));
-    }
-
-    result.resize(size, '\0');
-
-    return result;
-}
-
-#elif defined(__FreeBSD__) || defined(__DragonFly__)
-
-std::string executable_path()
-{
-    std::array<int, 4> mib{ { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 } };
-    std::string result;
-    std::size_t size = PATH_MAX + 1;
-
-    result.resize(size, '\0');
-
-    if (sysctl(mib.data(), 4, &result[0], &size, nullptr, 0) < 0) {
-        throw std::runtime_error(std::strerror(errno));
-    }
-
-    result.resize(size, '\0');
-
-    return result;
-}
-
-#elif defined(__APPLE__)
-
-std::string executable_path()
-{
-    std::string result;
-    std::size_t size = PROC_PIDPATHINFO_MAXSIZE;
-
-    result.resize(size, '\0');
-
-    if ((size = proc_pidpath(getpid(), &result[0], size)) == 0) {
-        throw std::runtime_error(std::strerror(errno));
-    }
-
-    result.resize(size, '\0');
-
-    return result;
-}
-
-#elif defined(_WIN32)
-
-std::string executable_path()
-{
-    std::string result;
-    std::size_t size = PATH_MAX;
-
-    result.resize(size, '\0');
-
-    if (!(size = GetModuleFileNameA(nullptr, &result[0], size))) {
-        throw std::runtime_error("GetModuleFileName error");
-    }
-
-    result.resize(size, '\0');
-
-    return result;
-}
-
-#elif defined(__NetBSD__)
-
-std::string executable_path()
-{
-        std::string result;
-
-#if defined(KERN_PROC_PATHNAME)
-        std::array<int, 4> mib{ CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME };
-        std::size_t size = MAXPATHLEN;
-
-        result.resize(size, '\0');
-
-        if (sysctl(mib.data(), 4, &result[0], &size, nullptr, 0) < 0) {
-                throw std::runtime_error(std::strerror(errno));
-        }
-
-        result.resize(size, '\0');
-#else
-        result.resize(2048, '\0');
-
-        auto size = readlink("/proc/curproc/exe", &result[0], 2048);
-
-        if (size < 0) {
-                throw std::runtime_error(std::strerror(errno));
-        }
-
-        result.resize(size, '\0');
-#endif
-
-        return result;
-}
-
-#elif defined(__OpenBSD__)
-
-std::string executable_path()
-{
-    char **paths, *path;
-    std::string result;
-    std::size_t len;
-    std::array<int, 4> mib{ CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV };
-
-    if (sysctl(mib.data(), 4, nullptr, &len, nullptr, 0) == -1) {
-        throw std::runtime_error(std::strerror(errno));
-    }
-    if ((paths = static_cast<char **>(malloc(len))) == nullptr) {
-        throw std::runtime_error(std::strerror(errno));
-    }
-
-    sysctl(mib.data(), 4, paths, &len, nullptr, 0);
-
-    if ((path = static_cast<char *>(std::malloc(PATH_MAX + 1))) == nullptr) {
-        std::free(paths);
-        throw std::runtime_error(std::strerror(errno));
-    }
-
-    realpath(paths[0], path);
-    result = path;
-
-    std::free(paths);
-    std::free(path);
-
-    return result;
-}
-
-#else
-
-std::string executable_path()
-{
-    // Not supported.
-    return "";
-}
-
-#endif
-
-} // !namespace
-
-namespace mlk {
-
-namespace util {
-
-std::vector<std::string> split(const std::string &list, const std::string &delimiters, int max)
-{
-    std::vector<std::string> result;
-    std::size_t next = -1, current;
-    int count = 1;
-    bool finished = false;
-
-    if (list.empty()) {
-        return result;
-    }
-
-    do {
-        std::string val;
-
-        current = next + 1;
-        next = list.find_first_of(delimiters, current);
-
-        // split max, get until the end
-        if (max >= 0 && count++ >= max) {
-            val = list.substr(current, std::string::npos);
-            finished = true;
-        } else {
-            val = list.substr(current, next - current);
-            finished = next == std::string::npos;
-        }
-
-        result.push_back(val);
-    } while (!finished);
-
-    return result;
-}
-
-std::string basedir() noexcept
-{
-    std::string result = "./";
-
-    try {
-        boost::filesystem::path tmp = executable_path();
-
-        tmp = tmp.parent_path();    // remove executable name
-        tmp = tmp.parent_path();    // remove "bin"
-        result = tmp.string();
-    } catch (...) {
-        // TODO: add log.
-    }
-
-    return result;
-}
-
-/*
- * json utilities
- * ------------------------------------------------------------------
- */
-
-namespace json {
-
-/*
- * XXX: until json get a non-throwing function to check if a pointer exists,
- * use this to check for a value at the given pointer or throw a
- * property_error.
- */
-nlohmann::json require(const nlohmann::json& object,
-                       const nlohmann::json::json_pointer& pointer,
-                       const nlohmann::json::value_t expected)
-{
-    assert(object.is_object());
-
-    nlohmann::json value;
-
-    try {
-        value = object.at(pointer);
-    } catch (...) {
-        throw property_error(object, pointer, nlohmann::json::value_t::null, expected);
-    }
-
-    if (value.type() != expected) {
-        throw property_error(object, pointer, value.type(), expected);
-    }
-
-    return value;
-}
-
-nlohmann::json get(const nlohmann::json& object,
-                   const nlohmann::json::json_pointer& pointer,
-                   const nlohmann::json::value_t expected,
-                   const nlohmann::json def)
-{
-    assert(object.is_object());
-
-    nlohmann::json value;
-
-    try {
-        value = object.at(pointer);
-    } catch (...) {
-    }
-
-    if (value.type() != expected)
-        value = def;
-
-    return value;
-}
-
-nlohmann::json require_array(const nlohmann::json& object,
-                             const nlohmann::json::json_pointer& pointer)
-{
-    return require(object, pointer, nlohmann::json::value_t::array);
-}
-
-bool require_bool(const nlohmann::json& object,
-                  const nlohmann::json::json_pointer& pointer)
-{
-    return require(object, pointer, nlohmann::json::value_t::boolean);
-}
-
-int require_int(const nlohmann::json& object,
-                const nlohmann::json::json_pointer& pointer)
-{
-    nlohmann::json v;
-
-    try {
-        v = object.at(pointer);
-
-        if (v.is_number_integer())
-            return v.get<int>();
-        else if (v.is_number_unsigned() && v.get<unsigned>() <= static_cast<unsigned>(INT_MAX))
-            return static_cast<int>(v.get<unsigned>());
-    } catch (...) {
-    }
-
-    throw property_error(object, pointer, v.type(), nlohmann::json::value_t::number_integer);
-}
-
-nlohmann::json require_object(const nlohmann::json& object,
-                              const nlohmann::json::json_pointer& pointer)
-{
-    return require(object, pointer, nlohmann::json::value_t::object);
-}
-
-std::string require_string(const nlohmann::json& object,
-                           const nlohmann::json::json_pointer& pointer)
-{
-    return require(object, pointer, nlohmann::json::value_t::string);
-}
-
-unsigned require_uint(const nlohmann::json& object,
-                      const nlohmann::json::json_pointer& pointer)
-{
-    nlohmann::json v;
-
-    try {
-        v = object.at(pointer);
-
-        if (v.is_number_unsigned())
-            return v.get<unsigned>();
-        else if (v.is_number_integer() && v.get<int>() >= 0)
-            return static_cast<unsigned>(v.get<int>());
-    } catch (...) {
-    }
-
-    throw property_error(object, pointer, v.type(), nlohmann::json::value_t::number_unsigned);
-}
-
-mlk::size require_size(const nlohmann::json& object,
-                       const nlohmann::json::json_pointer& index)
-{
-    using pointer = nlohmann::json::json_pointer;
-
-    return {
-        require_uint(object, pointer(index.to_string() + "/width")),
-        require_uint(object, pointer(index.to_string() + "/height"))
-    };
-}
-
-int get_int(const nlohmann::json &object,
-            const nlohmann::json::json_pointer &pointer,
-            int value) noexcept
-{
-    try {
-        auto v = object.at(pointer);
-
-        if (v.is_number_integer())
-            value = v.get<int>();
-        else if (v.is_number_unsigned() && v.get<unsigned>() <= static_cast<unsigned>(INT_MAX))
-            value = static_cast<int>(v.get<unsigned>());
-    } catch (...) {
-    }
-
-    return value;
-}
-
-unsigned get_uint(const nlohmann::json &object,
-                  const nlohmann::json::json_pointer& pointer,
-                  unsigned value) noexcept
-{
-    try {
-        auto v = object.at(pointer);
-
-        if (v.is_number_unsigned())
-            value = v.get<unsigned>();
-        else if (v.is_number_integer() && v.get<int>() >= 0)
-            value = static_cast<unsigned>(v.get<int>());
-    } catch (...) {
-    }
-
-    return value;
-}
-
-mlk::size get_size(const nlohmann::json& object,
-                   const nlohmann::json::json_pointer& pointer,
-                   const mlk::size& def) noexcept
-{
-    using json_pointer = nlohmann::json::json_pointer;
-
-    return {
-        get_uint(object, json_pointer(pointer.to_string() + "/width"), def.width),
-        get_uint(object, json_pointer(pointer.to_string() + "/height"), def.height)
-    };
-}
-
-} // !json
-
-} // !util
-
-} // !mlk
--- a/libmlk/malikania/util.hpp	Sat Oct 27 07:16:28 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,333 +0,0 @@
-/*
- * util.hpp -- malikania utilities
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef MALIKANIA_UTIL_HPP
-#define MALIKANIA_UTIL_HPP
-
-/**
- * \file util.hpp
- * \brief Some utilities
- */
-
-#include <algorithm>
-#include <exception>
-#include <string>
-#include <vector>
-
-#include "json.hpp"
-#include "size.hpp"
-
-namespace mlk {
-
-/**
- * \brief Utilities.
- */
-namespace util {
-
-/**
- * Split a line into a list of tokens.
- *
- * If max is less than 0, split as much as possible, otherwise split at most
- * the number of specified tokens.
- *
- * \param line the line to split
- * \param delim the list of delimiters allowed
- * \param max the maximum number of tokens
- * \return the list
- */
-std::vector<std::string> split(const std::string& line, const std::string& delim, int max = -1);
-
-/**
- * Get the base directory of the current executable.
- *
- * \return the base directory or empty if impossible to get
- */
-std::string basedir() noexcept;
-
-/**
- * Clamp the value between low and high.
- *
- * \param value the value
- * \param low the minimum value
- * \param high the maximum value
- * \return the value between minimum and maximum
- */
-template <typename T>
-constexpr T clamp(T value, T low, T high) noexcept
-{
-    return (value < high) ? std::max(value, low) : std::min(value, high);
-}
-
-/**
- * \brief Utilities for networking.
- */
-namespace net {
-
-/**
- * Shortcut for util::split(line, " ", max);
- *
- * \param line the line
- * \param max the maximum of tokens to split
- * \return the list
- */
-inline std::vector<std::string> split(const std::string& line, int max = -1)
-{
-    return util::split(line, " ", max);
-}
-
-} // !net
-
-namespace json {
-
-/**
- * \brief Indicates a property error in an object.
- */
-class property_error : public std::exception {
-private:
-    nlohmann::json m_object;
-    nlohmann::json::json_pointer m_pointer;
-    nlohmann::json::value_t m_type;
-    nlohmann::json::value_t m_expected;
-    std::string m_message;
-
-public:
-    /**
-     * Construct the property error.
-     *
-     * \param object the faulty object
-     * \param pointer the property path
-     * \param type the found type
-     * \param expected the expected type
-     */
-    inline property_error(nlohmann::json object,
-                          nlohmann::json::json_pointer pointer,
-                          nlohmann::json::value_t type,
-                          nlohmann::json::value_t expected) noexcept
-        : m_object(std::move(object))
-        , m_pointer(std::move(pointer))
-        , m_type(type)
-        , m_expected(expected)
-    {
-#if 0
-        m_message += "invalid '" + m_pointer.to_string() + "' property ";
-        m_message += "(" + nlohmann::json(expected).type_name();
-        m_message += " expected, got ";
-        m_message += nlohmann::json(type).type_name() + ")";
-#endif
-    }
-
-    /**
-     * Get the faulty object.
-     *
-     * return the object
-     */
-    inline const nlohmann::json& object() const noexcept
-    {
-        return m_object;
-    }
-
-    /**
-     * Get the pointer to the property.
-     *
-     * \return the pointer
-     */
-    inline const nlohmann::json::json_pointer& pointer() const noexcept
-    {
-        return m_pointer;
-    }
-
-    /**
-     * Get the actual type.
-     *
-     * \return the type
-     */
-    inline nlohmann::json::value_t type() const noexcept
-    {
-        return m_type;
-    }
-
-    /**
-     * Get the expected type.
-     *
-     * \return the type
-     */
-    inline nlohmann::json::value_t expected() const noexcept
-    {
-        return m_expected;
-    }
-
-    /**
-     * Get a human formatted message.
-     *
-     * \return the message
-     */
-    const char* what() const noexcept
-    {
-        return m_message.c_str();
-    }
-};
-
-/**
- * Require a value at the given pointer, if the value does not match the
- * expected type, an exception is thrown.
- *
- * If the property does not exists, a property_error with a null found type is
- * thrown.
- *
- * \pre object.is_object()
- * \param object the object
- * \param pointer the pointer to the property
- * \param expected the expected type
- * \throw property_error on errors
- */
-nlohmann::json require(const nlohmann::json& object,
-                       const nlohmann::json::json_pointer& pointer,
-                       const nlohmann::json::value_t expected);
-
-/**
- * Require an array at the given pointer.
- *
- * \pre object.is_object()
- * \param object the object
- * \param pointer the pointer to the property
- * \return the value
- * \throw property_error on errors
- */
-nlohmann::json require_array(const nlohmann::json& object,
-                             const nlohmann::json::json_pointer& pointer);
-
-/**
- * Require a boolean at the given pointer.
- *
- * \pre object.is_object()
- * \param object the object
- * \param pointer the pointer to the property
- * \return the value
- * \throw property_error on errors
- */
-bool require_bool(const nlohmann::json& object,
-                  const nlohmann::json::json_pointer& pointer);
-
-/**
- * Require an integer at the given pointer.
- *
- * \pre object.is_object()
- * \param object the object
- * \param pointer the pointer to the property
- * \return the value
- * \throw property_error on errors
- */
-int require_int(const nlohmann::json& object,
-                const nlohmann::json::json_pointer& pointer);
-
-/**
- * Require an object at the given pointer.
- *
- * \pre object.is_object()
- * \param object the object
- * \param pointer the pointer to the property
- * \return the value
- * \throw property_error on errors
- */
-nlohmann::json require_object(const nlohmann::json& object,
-                              const nlohmann::json::json_pointer& pointer);
-
-/**
- * Require a string at the given pointer.
- *
- * \pre object.is_object()
- * \param object the object
- * \param pointer the pointer to the property
- * \return the value
- * \throw property_error on errors
- */
-std::string require_string(const nlohmann::json& object,
-                           const nlohmann::json::json_pointer& pointer);
-
-/**
- * Require an unsigned integer at the given pointer.
- *
- * \pre object.is_object()
- * \param object the object
- * \param pointer the pointer to the property
- * \return the value
- * \throw property_error on errors
- */
-unsigned require_uint(const nlohmann::json& object,
-                      const nlohmann::json::json_pointer& pointer);
-
-/**
- * Require a size object at the given pointer.
- *
- * \pre object.is_object
- * \param object the json object
- * \param pointer the pointer to the property
- * \return the value
- * \throw property_error on errors
- */
-mlk::size require_size(const nlohmann::json& object,
-                       const nlohmann::json::json_pointer& pointer);
-
-/**
- * Get an integer at the given pointer or default value if not present or
- * invalid.
- *
- * \pre object.is_object
- * \param object the json object
- * \param pointer the pointer to the property
- * \param def the default value
- * \return an integer
- */
-int get_int(const nlohmann::json& object,
-            const nlohmann::json::json_pointer& pointer,
-            int def = 0) noexcept;
-
-/**
- * Get an unsigned integer at the given pointer or default value if not present
- * or invalid.
- *
- * \pre object.is_object
- * \param object the json object
- * \param pointer the pointer to the property
- * \param def the default value
- * \return an integer
- */
-unsigned get_uint(const nlohmann::json& object,
-                  const nlohmann::json::json_pointer& pointer,
-                  unsigned def = 0) noexcept;
-
-/**
- * Get a size object at the given pointer or a default value.
- *
- * \pre object.is_object
- * \param object the json object
- * \param pointer the pointer to the property
- * \param def the default value to return in case of error
- * \return a size or a default value
- */
-mlk::size get_size(const nlohmann::json& object,
-                   const nlohmann::json::json_pointer& pointer,
-                   const mlk::size& def = {}) noexcept;
-
-} // !json
-
-} // !util
-
-} // !mlk
-
-#endif // !MALIKANIA_UTIL_HPP
--- a/tests/libmlk/CMakeLists.txt	Sat Oct 27 07:16:28 2018 +0200
+++ b/tests/libmlk/CMakeLists.txt	Sun Oct 28 10:01:44 2018 +0100
@@ -20,7 +20,6 @@
 add_subdirectory(point)
 add_subdirectory(rectangle)
 add_subdirectory(size)
-add_subdirectory(util)
 add_subdirectory(socket)
 
 # JavaScript bindings
--- a/tests/libmlk/util/CMakeLists.txt	Sat Oct 27 07:16:28 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for malikania
-#
-# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-malikania_create_test(
-	NAME util
-	LIBRARIES libmlk
-	SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
-)
--- a/tests/libmlk/util/main.cpp	Sat Oct 27 07:16:28 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,468 +0,0 @@
-/*
- * main.cpp -- test util
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "Util"
-#include <boost/test/unit_test.hpp>
-
-#include <malikania/util.hpp>
-#include <malikania/size.hpp>
-
-using namespace mlk;
-using namespace nlohmann;
-
-namespace nlohmann {
-
-namespace detail {
-
-auto operator<<(std::ostream& out, json::value_t type) -> std::ostream&
-{
-	out << json(type).type_name();
-	return out;
-}
-
-} // !detail
-
-} // !nlohmann
-
-/*
- * util::clamp
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(clamp)
-
-BOOST_AUTO_TEST_CASE(normal)
-{
-	BOOST_REQUIRE_EQUAL(5, util::clamp(5, 0, 10));
-}
-
-BOOST_AUTO_TEST_CASE(minimum)
-{
-	BOOST_REQUIRE_EQUAL(0, util::clamp(0, 0, 10));
-}
-
-BOOST_AUTO_TEST_CASE(maximum)
-{
-	BOOST_REQUIRE_EQUAL(10, util::clamp(10, 0, 10));
-}
-
-BOOST_AUTO_TEST_CASE(less)
-{
-	BOOST_REQUIRE_EQUAL(0, util::clamp(-10, 0, 10));
-}
-
-BOOST_AUTO_TEST_CASE(higher)
-{
-	BOOST_REQUIRE_EQUAL(10, util::clamp(20, 0, 10));
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * util::json::require
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(json_require)
-
-BOOST_AUTO_TEST_CASE(simple)
-{
-	json object{
-		{ "b", true },
-		{ "i", 123 },
-		{ "s", "blabla" }
-	};
-
-	BOOST_REQUIRE_EQUAL(util::json::require(
-		object, "/b"_json_pointer, json::value_t::boolean).type(), json::value_t::boolean);
-	BOOST_REQUIRE_EQUAL(util::json::require(
-		object, "/i"_json_pointer, json::value_t::number_integer).type(), json::value_t::number_integer);
-	BOOST_REQUIRE_EQUAL(util::json::require(
-		object, "/s"_json_pointer, json::value_t::string).type(), json::value_t::string);
-}
-
-BOOST_AUTO_TEST_CASE(nonexistent)
-{
-	auto json = json::object();
-
-	try {
-		util::json::require(json, "/non-existent"_json_pointer, json::value_t::string);
-		BOOST_FAIL("exception expected");
-	} catch (const util::json::property_error& ex) {
-		BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::null);
-		BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::string);
-	}
-}
-
-BOOST_AUTO_TEST_CASE(invalid)
-{
-	json object{
-		{ "not-string", 123 }
-	};
-
-	try {
-		util::json::require(object, "/not-string"_json_pointer, json::value_t::string);
-		BOOST_FAIL("exception expected");
-	} catch (const util::json::property_error& ex) {
-		BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::number_integer);
-		BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::string);
-	}
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * util::json::require_array
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(json_require_array)
-
-BOOST_AUTO_TEST_CASE(simple)
-{
-	json object{
-		{ "a", { 1, 2, 3 } },
-		{ "l1", {
-				{ "l2", { 4, 5, 6 } }
-			}
-		}
-	};
-
-	auto a = util::json::require_array(object, "/a"_json_pointer);
-	auto l2 = util::json::require_array(object, "/l1/l2"_json_pointer);
-
-	BOOST_REQUIRE(a.is_array());
-	BOOST_REQUIRE(l2.is_array());
-}
-
-BOOST_AUTO_TEST_CASE(invalid)
-{
-	json object{
-		{ "not-array", 123 }
-	};
-
-	try {
-		util::json::require_array(object, "/not-array"_json_pointer);
-		BOOST_FAIL("exception expected");
-	} catch (const util::json::property_error& ex) {
-		BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::number_integer);
-		BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::array);
-	}
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * util::json::require_bool
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(json_require_bool)
-
-BOOST_AUTO_TEST_CASE(simple)
-{
-	json object{
-		{ "b", true }
-	};
-
-	BOOST_REQUIRE_EQUAL(util::json::require_bool(object, "/b"_json_pointer), true);
-}
-
-BOOST_AUTO_TEST_CASE(invalid)
-{
-	json object{
-		{ "not-bool", 123 }
-	};
-
-	try {
-		util::json::require_bool(object, "/not-bool"_json_pointer);
-		BOOST_FAIL("exception expected");
-	} catch (const util::json::property_error& ex) {
-		BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::number_integer);
-		BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::boolean);
-	}
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * util::json::require_int
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(json_require_int)
-
-BOOST_AUTO_TEST_CASE(simple)
-{
-	json object{
-		{ "i", 123 }
-	};
-
-	BOOST_REQUIRE_EQUAL(util::json::require_int(object, "/i"_json_pointer), 123);
-}
-
-BOOST_AUTO_TEST_CASE(invalid)
-{
-	json object{
-		{ "not-int", true }
-	};
-
-	try {
-		util::json::require_int(object, "/not-int"_json_pointer);
-		BOOST_FAIL("exception expected");
-	} catch (const util::json::property_error& ex) {
-		BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::boolean);
-		BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::number_integer);
-	}
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * util::json::require_object
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(json_require_object)
-
-BOOST_AUTO_TEST_CASE(simple)
-{
-	json object{
-		{
-			"network", json::object({
-				{ "host", "localhost" },
-				{ "port", 9090 }
-			})
-		}
-	};
-
-	BOOST_REQUIRE(util::json::require_object(object, "/network"_json_pointer).is_object());
-}
-
-BOOST_AUTO_TEST_CASE(invalid)
-{
-	json object{
-		{ "not-object", 123 }
-	};
-
-	try {
-		util::json::require_object(object, "/not-object"_json_pointer);
-		BOOST_FAIL("exception expected");
-	} catch (const util::json::property_error& ex) {
-		BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::number_integer);
-		BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::object);
-	}
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * util::json::require_string
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(json_require_string)
-
-BOOST_AUTO_TEST_CASE(simple)
-{
-	json object{
-		{ "s", "hello" }
-	};
-
-	BOOST_REQUIRE_EQUAL(util::json::require_string(object, "/s"_json_pointer), "hello");
-}
-
-BOOST_AUTO_TEST_CASE(invalid)
-{
-	json object{
-		{ "not-string", 123 }
-	};
-
-	try {
-		util::json::require_string(object, "/not-string"_json_pointer);
-		BOOST_FAIL("exception expected");
-	} catch (const util::json::property_error& ex) {
-		BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::number_integer);
-		BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::string);
-	}
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
- * util::json::require_uint
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(json_require_uint)
-
-BOOST_AUTO_TEST_CASE(simple)
-{
-	json object{
-		{ "u1", 123U }
-	};
-
-	BOOST_REQUIRE_EQUAL(util::json::require_uint(object, "/u1"_json_pointer), 123U);
-}
-
-BOOST_AUTO_TEST_CASE(invalid)
-{
-	json object{
-		{ "not-uint", true }
-	};
-
-	try {
-		util::json::require_uint(object, "/not-uint"_json_pointer);
-		BOOST_FAIL("exception expected");
-	} catch (const util::json::property_error& ex) {
-		BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::boolean);
-		BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::number_unsigned);
-	}
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE(json_require_size)
-
-/*
- * util::json::require_size
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_CASE(simple)
-{
-	json object{
-		{ "size", {
-				{ "width", 10 },
-				{ "height", 20 }
-			}
-		}
-	};
-
-	BOOST_TEST((util::json::require_size(object, "/size"_json_pointer) == mlk::size{10, 20}));
-}
-
-BOOST_AUTO_TEST_CASE(not_object)
-{
-	json object{
-		{ "size", false }
-	};
-
-	try {
-		util::json::require_size(object, "/size"_json_pointer);
-		BOOST_FAIL("exception expected");
-	} catch (const util::json::property_error& ex) {
-		BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::null);
-		BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::number_unsigned);
-	}
-}
-
-BOOST_AUTO_TEST_CASE(not_int_width)
-{
-	json object{
-		{ "size", {
-				{ "width", "10" },
-				{ "height", 20 }
-			}
-		}
-	};
-
-	try {
-		util::json::require_size(object, "/size"_json_pointer);
-		BOOST_FAIL("exception expected");
-	} catch (const util::json::property_error& ex) {
-		BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::string);
-		BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::number_unsigned);
-	}
-}
-
-BOOST_AUTO_TEST_CASE(not_int_height)
-{
-	json object{
-		{ "size", {
-				{ "width", 10 },
-				{ "height", "20" }
-			}
-		}
-	};
-
-	try {
-		util::json::require_size(object, "/size"_json_pointer);
-		BOOST_FAIL("exception expected");
-	} catch (const util::json::property_error& ex) {
-		BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::string);
-		BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::number_unsigned);
-	}
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE(json_get_size)
-
-BOOST_AUTO_TEST_CASE(simple)
-{
-	json object{
-		{ "size", {
-				{ "width", 10 },
-				{ "height", 20 }
-			}
-		}
-	};
-
-	BOOST_TEST((util::json::get_size(object, "/size"_json_pointer) == mlk::size{10, 20}));
-}
-
-BOOST_AUTO_TEST_CASE(not_object)
-{
-	const json object{{ "size", false }};
-	const mlk::size def{10, 20};
-
-	BOOST_TEST(util::json::get_size(object, "/size"_json_pointer, def) == def);
-}
-
-BOOST_AUTO_TEST_CASE(not_int_width)
-{
-	const json object{
-		{ "size", {
-				{ "width", "10" },
-				{ "height", 20 }
-			}
-		}
-	};
-
-	const mlk::size def{10, 20};
-
-	BOOST_TEST(util::json::get_size(object, "/size"_json_pointer, def) == def);
-}
-
-BOOST_AUTO_TEST_CASE(not_int_height)
-{
-	const json object{
-		{ "size", {
-				{ "width", 10 },
-				{ "height", "20" }
-			}
-		}
-	};
-
-	const mlk::size def{10, 20};
-
-	BOOST_TEST(util::json::get_size(object, "/size"_json_pointer, def) == def);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
--- a/tests/tools/CMakeLists.txt	Sat Oct 27 07:16:28 2018 +0200
+++ b/tests/tools/CMakeLists.txt	Sun Oct 28 10:01:44 2018 +0100
@@ -17,4 +17,4 @@
 #
 
 add_subdirectory(map)
-add_subdirectory(tileset)
+#add_subdirectory(tileset)
--- a/tools/tileset/main.cpp	Sat Oct 27 07:16:28 2018 +0200
+++ b/tools/tileset/main.cpp	Sun Oct 28 10:01:44 2018 +0100
@@ -28,8 +28,6 @@
 
 #include <json.hpp>
 
-#include <malikania/util.hpp>
-
 namespace fs = boost::filesystem;
 namespace ptree = boost::property_tree;
 
@@ -157,8 +155,14 @@
 	auto points = tree.get<std::string>("polyline.<xmlattr>.points");
 	auto array = nlohmann::json::array();
 
-	for (const auto& p : mlk::util::split(points, " "))
-		array.push_back(std::stod(p));
+	std::istringstream iss(points);
+
+	for (double point; iss; ) {
+		point = 0;
+		iss >> point;
+		iss >> std::skipws;
+		array.push_back(point);
+	}
 
 	return {
 		{ "type", "polyline" },