← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/json_writer into lp:widelands

 

GunChleoc has proposed merging lp:~widelands-dev/widelands/json_writer into lp:widelands.

Commit message:
Refactor website binaries to use a real tree data structure for writing JSON

Requested reviews:
  Widelands Developers (widelands-dev)

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/json_writer/+merge/357908

Getting rid of some spaghetti code. Only affects the website, not Widelands itself.
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/json_writer into lp:widelands.
=== modified file 'src/website/CMakeLists.txt'
--- src/website/CMakeLists.txt	2018-08-14 20:42:18 +0000
+++ src/website/CMakeLists.txt	2018-10-27 16:48:10 +0000
@@ -1,3 +1,5 @@
+add_subdirectory(json)
+
 wl_library(website_common
   SRCS
     website_common.cc
@@ -23,6 +25,7 @@
     graphic_surface
     io_fileread
     io_filesystem
+    json
     logic
     map_io_map_loader
     website_common
@@ -38,6 +41,7 @@
     graphic
     io_fileread
     io_filesystem
+    json
     logic
     logic_tribe_basic_info
     website_common

=== added directory 'src/website/json'
=== added file 'src/website/json/CMakeLists.txt'
--- src/website/json/CMakeLists.txt	1970-01-01 00:00:00 +0000
+++ src/website/json/CMakeLists.txt	2018-10-27 16:48:10 +0000
@@ -0,0 +1,10 @@
+wl_library(json
+  SRCS
+    json.cc
+    json.h
+    value.cc
+    value.h
+  DEPENDS
+    io_fileread
+    io_filesystem
+)

=== added file 'src/website/json/json.cc'
--- src/website/json/json.cc	1970-01-01 00:00:00 +0000
+++ src/website/json/json.cc	2018-10-27 16:48:10 +0000
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2018 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#include "io/filewrite.h"
+#include "website/json/json.h"
+
+// ########################## JSON Element #############################
+
+namespace JSON {
+const std::string JSON::Element::tab_ = "   ";
+
+JSON::Object* Element::add_object(const std::string& key) {
+	children_.push_back(std::unique_ptr<JSON::Object>(new JSON::Object(key, level_ + 1)));
+	return dynamic_cast<JSON::Object*>(children_.back().get());
+}
+
+JSON::Array* Element::add_array(const std::string& key) {
+	children_.push_back(std::unique_ptr<JSON::Array>(new JSON::Array(key, level_ + 1)));
+	return dynamic_cast<JSON::Array*>(children_.back().get());
+}
+
+void Element::add_bool(const std::string& key, bool value) {
+	values_.push_back(std::make_pair(key, std::unique_ptr<JSON::Value>(new JSON::Boolean(value))));
+}
+
+void Element::add_double(const std::string& key, double value) {
+	values_.push_back(std::make_pair(key, std::unique_ptr<JSON::Value>(new JSON::Double(value))));
+}
+void Element::add_int(const std::string& key, int value) {
+	values_.push_back(std::make_pair(key, std::unique_ptr<JSON::Value>(new JSON::Int(value))));
+}
+void Element::add_empty(const std::string& key) {
+	values_.push_back(std::make_pair(key, std::unique_ptr<JSON::Value>(new JSON::Empty())));
+}
+void Element::add_string(const std::string& key, const std::string& value) {
+	values_.push_back(std::make_pair(key, std::unique_ptr<JSON::Value>(new JSON::String(value))));
+}
+
+void Element::write_to_file(FileSystem& fs, const std::string& filename) const {
+	FileWrite file_writer;
+	file_writer.text(as_string());
+	file_writer.write(fs, filename);
+}
+
+std::string Element::as_string() const {
+	return "{\n" + children_as_string() + "}\n";
+}
+
+std::string Element::values_as_string(const std::string& tabs) const {
+	std::string result = "";
+	if (!values_.empty()) {
+		for (size_t i = 0; i < values_.size() - 1; ++i) {
+			const auto& element = values_.at(i);
+			const std::string element_as_string = element.second->as_string();
+			result +=
+			   tabs + tab_ + key_to_string(element.first, element_as_string.empty()) + element_as_string + ",\n";
+		}
+		const auto& element = values_.at(values_.size() - 1);
+		const std::string element_as_string = element.second->as_string();
+		result += tabs + tab_ + key_to_string(element.first, element_as_string.empty()) + element_as_string +
+		          (children_.empty() ? "\n" : ",\n");
+	}
+	return result;
+}
+
+std::string Element::children_as_string() const {
+	std::string result = "";
+	if (!children_.empty()) {
+		for (size_t i = 0; i < children_.size() - 1; ++i) {
+			result += children_.at(i)->as_string() + ",\n";
+		}
+		result += children_.at(children_.size() - 1)->as_string() + "\n";
+	}
+	return result;
+}
+
+std::string Element::key_to_string(const std::string& value, bool value_is_empty) {
+	return "\"" + value + (value_is_empty ? "\"" : "\": ");
+}
+
+// ########################## JSON Object #############################
+
+Object::Object(const std::string key, int level) : JSON::Element(key, level) {
+}
+
+std::string Object::as_string() const {
+	std::string result = "";
+	std::string tabs = "";
+	for (int i = 0; i < level_; ++i) {
+		tabs += tab_;
+	}
+
+	result += tabs + (key_.empty() ? "" : key_to_string(key_)) + "{\n";
+	result += values_as_string(tabs);
+	result += children_as_string();
+	result += tabs + "}";
+	return result;
+}
+
+// ########################## JSON Array #############################
+
+Array::Array(const std::string key, int level) : JSON::Element(key, level) {
+}
+
+std::string Array::as_string() const {
+	std::string result = "";
+	std::string tabs = "";
+	for (int i = 0; i < level_; ++i) {
+		tabs += tab_;
+	}
+
+	result += tabs + key_to_string(key_) + "[\n";
+	result += values_as_string(tabs);
+	result += children_as_string();
+	result += tabs + "]";
+	return result;
+}
+
+}  // namespace JSON

=== added file 'src/website/json/json.h'
--- src/website/json/json.h	1970-01-01 00:00:00 +0000
+++ src/website/json/json.h	2018-10-27 16:48:10 +0000
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2018 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#ifndef WL_WEBSITE_JSON_JSON_H
+#define WL_WEBSITE_JSON_JSON_H
+
+#include <cassert>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "io/filesystem/filesystem.h"
+#include "website/json/value.h"
+
+namespace JSON {
+
+class Array;
+class Object;
+
+class Element {
+protected:
+	// Constructor for child node
+	explicit Element(const std::string key, int level) : key_(key), level_(level) {
+	}
+
+public:
+	// Constructor for root node
+	explicit Element() : JSON::Element("", 0) {
+	}
+
+	JSON::Object* add_object(const std::string& key = "");
+	JSON::Array* add_array(const std::string& key);
+	void add_bool(const std::string& key, bool value);
+	void add_double(const std::string& key, double value);
+	void add_int(const std::string& key, int value);
+	void add_empty(const std::string& key);
+	void add_string(const std::string& key, const std::string& value);
+
+	void write_to_file(FileSystem& fs, const std::string& filename) const;
+
+	virtual std::string as_string() const;
+
+protected:
+	static const std::string tab_;
+
+	std::string values_as_string(const std::string& tabs) const;
+	std::string children_as_string() const;
+	static std::string key_to_string(const std::string& value, bool value_is_empty = false);
+
+	std::string key_;
+	const int level_;
+	std::vector<std::unique_ptr<JSON::Element>> children_;
+	std::vector<std::pair<std::string, std::unique_ptr<JSON::Value>>> values_;
+};
+
+class Object : public Element {
+	friend class JSON::Element;
+
+protected:
+	// Constructor for child node
+	explicit Object(const std::string key, int level);
+
+public:
+	// Constructor for root node
+	explicit Object() : JSON::Element("", 0) {
+	}
+	std::string as_string() const override;
+};
+
+class Array : public Element {
+	friend class JSON::Element;
+protected:
+	// Constructor for child node
+	explicit Array(const std::string key, int level);
+
+public:
+	std::string as_string() const override;
+};
+}  // namespace JSON
+#endif  // end of include guard: WL_WEBSITE_JSON_JSON_H

=== added file 'src/website/json/value.cc'
--- src/website/json/value.cc	1970-01-01 00:00:00 +0000
+++ src/website/json/value.cc	2018-10-27 16:48:10 +0000
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2018 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#include "website/json/value.h"
+
+#include <sstream>
+
+namespace JSON {
+
+Boolean::Boolean(bool value) : bool_value(value) {
+}
+std::string Boolean::as_string() const {
+	return bool_value ? "true" : "false";
+}
+
+Double::Double(double value) : double_value(value) {
+}
+std::string Double::as_string() const {
+	std::ostringstream strs;
+	strs << double_value;
+	return strs.str();
+}
+
+Int::Int(int value) : int_value(value) {}
+std::string Int::as_string() const {
+	std::ostringstream strs;
+	strs << int_value;
+	return strs.str();
+}
+
+std::string Empty::as_string() const {
+	return "";
+}
+
+String::String(std::string value) : string_value(value) {}
+std::string String::as_string() const {
+	return "\"" + string_value + "\"";
+}
+
+}  // namespace JSON

=== added file 'src/website/json/value.h'
--- src/website/json/value.h	1970-01-01 00:00:00 +0000
+++ src/website/json/value.h	2018-10-27 16:48:10 +0000
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#ifndef WL_WEBSITE_JSON_VALUE_H
+#define WL_WEBSITE_JSON_VALUE_H
+
+#include <string>
+
+namespace JSON {
+
+/// Value types for JSON
+struct Value {
+	Value() = default;
+	virtual std::string as_string() const = 0;
+};
+
+struct Boolean : Value {
+	explicit Boolean(bool value);
+	std::string as_string() const override;
+private:
+	const bool bool_value;
+};
+
+struct Double : Value {
+	explicit Double(double value);
+	std::string as_string() const override;
+private:
+	const double double_value;
+};
+
+struct Empty : Value {
+	Empty() = default;
+	std::string as_string() const override;
+};
+
+struct Int : Value {
+	explicit Int(int value);
+	std::string as_string() const override;
+private:
+	const int int_value;
+};
+
+struct String : Value {
+	explicit String(std::string value);
+	std::string as_string() const override;
+private:
+	const std::string string_value;
+};
+
+}  // namespace JSON
+#endif  // end of include guard: WL_WEBSITE_JSON_VALUE_H

=== modified file 'src/website/map_info.cc'
--- src/website/map_info.cc	2018-08-14 20:42:18 +0000
+++ src/website/map_info.cc	2018-10-27 16:48:10 +0000
@@ -36,6 +36,7 @@
 #include "logic/editor_game_base.h"
 #include "logic/map.h"
 #include "map_io/widelands_map_loader.h"
+#include "website/json/json.h"
 #include "website/website_common.h"
 
 using namespace Widelands;
@@ -83,48 +84,20 @@
 
 		// Write JSON.
 		{
-			FileWrite fw;
-
-			const auto write_string = [&fw](const std::string& s) { fw.data(s.c_str(), s.size()); };
-			const auto write_key_value = [&write_string](
-			   const std::string& key, const std::string& quoted_value) {
-				write_string((boost::format("\"%s\": %s") % key % quoted_value).str());
-			};
-			const auto write_key_value_string = [&write_key_value](
-			   const std::string& key, const std::string& value) {
-				std::string quoted_value = value;
-				boost::replace_all(quoted_value, "\"", "\\\"");
-				write_key_value(key, "\"" + value + "\"");
-			};
-			const auto write_key_value_int = [&write_key_value](
-			   const std::string& key, const int value) {
-				write_key_value(key, boost::lexical_cast<std::string>(value));
-			};
-			write_string("{\n  ");
-			write_key_value_string("name", map->get_name());
-			write_string(",\n  ");
-			write_key_value_string("author", map->get_author());
-			write_string(",\n  ");
-			write_key_value_string("description", map->get_description());
-			write_string(",\n  ");
-			write_key_value_string("hint", map->get_hint());
-			write_string(",\n  ");
-			write_key_value_int("width", map->get_width());
-			write_string(",\n  ");
-			write_key_value_int("height", map->get_height());
-			write_string(",\n  ");
-			write_key_value_int("nr_players", map->get_nrplayers());
-			write_string(",\n  ");
+			std::unique_ptr<JSON::Object> json(new JSON::Object());
+			json->add_string("name", map->get_name());
+			json->add_string("author", map->get_author());
+			json->add_string("description", map->get_description());
+			json->add_string("hint", map->get_hint());
+			json->add_int("width", map->get_width());
+			json->add_int("height", map->get_height());
+			json->add_int("nr_players", map->get_nrplayers());
 
 			const std::string world_name =
 			   static_cast<Widelands::WidelandsMapLoader*>(ml.get())->old_world_name();
-			write_key_value_string("world_name", world_name);
-			write_string(",\n  ");
-			write_key_value_string("minimap", map_path + ".png");
-			write_string("\n");
-
-			write_string("}\n");
-			fw.write(*in_out_filesystem, (map_file + ".json").c_str());
+			json->add_string("world_name", world_name);
+			json->add_string("minimap", map_path + ".png");
+			json->write_to_file(*in_out_filesystem, (map_file + ".json").c_str());
 		}
 		egbase.cleanup_objects();
 	} catch (std::exception& e) {

=== modified file 'src/website/map_object_info.cc'
--- src/website/map_object_info.cc	2018-08-14 20:42:18 +0000
+++ src/website/map_object_info.cc	2018-10-27 16:48:10 +0000
@@ -35,6 +35,7 @@
 #include "logic/map_objects/tribes/tribe_basic_info.h"
 #include "logic/map_objects/tribes/tribes.h"
 #include "logic/map_objects/world/world.h"
+#include "website/json/json.h"
 #include "website/website_common.h"
 
 using namespace Widelands;
@@ -43,95 +44,12 @@
 
 /*
  ==========================================================
- SPECIALIZED FILEWRITE
- ==========================================================
- */
-
-// Defines some convenience writing functions for the JSON format
-class JSONFileWrite : public FileWrite {
-public:
-	JSONFileWrite() : FileWrite(), level_(0) {
-	}
-
-	void write_string(const std::string& s, bool use_indent = false) {
-		std::string writeme = s;
-		if (use_indent) {
-			for (int i = 0; i < level_; ++i) {
-				writeme = (boost::format("   %s") % writeme).str();
-			}
-		}
-		data(writeme.c_str(), writeme.size());
-	}
-	void write_key(const std::string& key) {
-		write_string((boost::format("\"%s\":\n") % key).str(), true);
-	}
-	void write_value_string(const std::string& quoted_value) {
-		write_string((boost::format("\"%s\"") % quoted_value).str(), true);
-	}
-	void write_key_value(const std::string& key, const std::string& quoted_value) {
-		write_string((boost::format("\"%s\": %s") % key % quoted_value).str(), true);
-	}
-	void write_key_value_string(const std::string& key, const std::string& value) {
-		std::string quoted_value = value;
-		boost::replace_all(quoted_value, "\"", "\\\"");
-		write_key_value(key, "\"" + value + "\"");
-	}
-	void write_key_value_int(const std::string& key, const int value) {
-		write_key_value(key, boost::lexical_cast<std::string>(value));
-	}
-	void open_brace() {
-		write_string("{\n", true);
-		++level_;
-	}
-	// JSON hates a final comma. This defaults to having NO comma.
-	void close_brace(bool precede_newline = false, int current = 0, int total = 0) {
-		--level_;
-		if (precede_newline) {
-			write_string("\n");
-		}
-		if (current < total - 1) {
-			write_string("},\n", true);
-		} else {
-			write_string("}", true);
-		}
-	}
-	void open_array(const std::string& name) {
-		write_string((boost::format("\"%s\":[\n") % name).str(), true);
-		++level_;
-	}
-	// JSON hates a final comma. This defaults to having NO comma.
-	void close_array(int current = 0, int total = 0) {
-		--level_;
-		write_string("\n");
-		if (current < total - 1) {
-			write_string("],\n", true);
-		} else {
-			write_string("]\n", true);
-		}
-	}
-	// JSON hates a final comma. This defaults to having a comma.
-	void close_element(int current = -2, int total = 0) {
-		if (current < total - 1) {
-			write_string(",\n");
-		}
-	}
-
-private:
-	int level_;
-};
-
-/*
- ==========================================================
  BUILDINGS
  ==========================================================
  */
 
 void write_buildings(const TribeDescr& tribe, EditorGameBase& egbase, FileSystem* out_filesystem) {
-
 	log("\n==================\nWriting buildings:\n==================\n");
-	JSONFileWrite fw;
-	fw.open_brace();             // Main
-	fw.open_array("buildings");  // Buildings
 
 	// We don't want any partially finished buildings
 	std::vector<const BuildingDescr*> buildings;
@@ -143,144 +61,92 @@
 		}
 	}
 
-	// Now write
+	std::unique_ptr<JSON::Element> json(new JSON::Element());
+	JSON::Array* json_buildings_array = json->add_array("buildings");
 	for (size_t i = 0; i < buildings.size(); ++i) {
 		const BuildingDescr& building = *buildings[i];
 		log(" %s", building.name().c_str());
-		fw.open_brace();  // Building
-
-		fw.write_key_value_string("name", building.name());
-		fw.close_element();
-		fw.write_key_value_string("descname", building.descname());
-		fw.close_element();
-		fw.write_key_value_string("icon", building.representative_image_filename());
-		fw.close_element();
-
-		// Conditional stuff in between, so we won't run into trouble with the commas.
+
+		JSON::Object* json_building = json_buildings_array->add_object();
+		json_building->add_string("name", building.name());
+		json_building->add_string("descname", building.descname());
+		json_building->add_string("icon", building.representative_image_filename());
 
 		// Buildcost
 		if (building.is_buildable()) {
-			fw.open_array("buildcost");  // Buildcost
-			size_t buildcost_counter = 0;
+			JSON::Array* json_builcost_array = json_building->add_array("buildcost");
 			for (WareAmount buildcost : building.buildcost()) {
 				const WareDescr& ware = *tribe.get_ware_descr(buildcost.first);
-				fw.open_brace();  // Buildcost
-				fw.write_key_value_string("name", ware.name());
-				fw.close_element();
-				fw.write_key_value_int("amount", buildcost.second);
-				fw.close_brace(true, buildcost_counter, building.buildcost().size());  // Buildcost
-				++buildcost_counter;
+				JSON::Object* json_builcost = json_builcost_array->add_object();
+				json_builcost->add_string("name", ware.name());
+				json_builcost->add_int("amount", buildcost.second);
 			}
-			fw.close_array(1, 5);  // Buildcost - we need a comma
 		}
 
 		if (building.is_enhanced()) {
-			fw.write_key_value_string(
+			json_building->add_string(
 			   "enhanced", tribe.get_building_descr(building.enhanced_from())->name());
-			fw.close_element();
 		}
 
 		if (building.enhancement() != INVALID_INDEX) {
-			fw.write_key_value_string(
+			json_building->add_string(
 			   "enhancement", tribe.get_building_descr(building.enhancement())->name());
-			fw.close_element();
 		}
 
 		if (upcast(ProductionSiteDescr const, productionsite, &building)) {
-			// Produces
+			// Produces wares
 			if (productionsite->output_ware_types().size() > 0) {
-				fw.open_array("produced_wares");  // Produces
-				size_t produces_counter = 0;
+				JSON::Array* json_wares_array = json_building->add_array("produced_wares");
 				for (DescriptionIndex ware_index : productionsite->output_ware_types()) {
-					fw.write_value_string(tribe.get_ware_descr(ware_index)->name());
-					fw.close_element(produces_counter, productionsite->output_ware_types().size());
-					++produces_counter;
+					json_wares_array->add_empty(tribe.get_ware_descr(ware_index)->name());
 				}
-				fw.close_array(1, 5);  // Produces - we need a comma
 			}
+			// Produces workers
 			if (productionsite->output_worker_types().size() > 0) {
-				fw.open_array("produced_workers");  // Produces
-				size_t produces_counter = 0;
+				JSON::Array* json_workers_array = json_building->add_array("produced_workers");
 				for (DescriptionIndex worker_index : productionsite->output_worker_types()) {
-					fw.write_value_string(tribe.get_worker_descr(worker_index)->name());
-					fw.close_element(produces_counter, productionsite->output_worker_types().size());
-					++produces_counter;
+					json_workers_array->add_empty(tribe.get_worker_descr(worker_index)->name());
 				}
-				fw.close_array(1, 5);  // Produces - we need a comma
 			}
 
 			// Consumes
 			if (productionsite->input_wares().size() > 0) {
-				fw.open_array("stored_wares");  // Consumes
-				size_t consumes_counter = 0;
+				JSON::Array* json_wares_array = json_building->add_array("stored_wares");
 				for (WareAmount input : productionsite->input_wares()) {
 					const WareDescr& ware = *tribe.get_ware_descr(input.first);
-					fw.open_brace();  // Input
-					fw.write_key_value_string("name", ware.name());
-					fw.close_element();
-					fw.write_key_value_int("amount", input.second);
-					fw.close_brace(
-					   true, consumes_counter, productionsite->input_wares().size());  // Input
-					++consumes_counter;
+					JSON::Object* json_input = json_wares_array->add_object();
+					json_input->add_string("name", ware.name());
+					json_input->add_int("amount", input.second);
 				}
-				fw.close_array(1, 5);  // Consumes - we need a comma
 			}
 
-			fw.open_array("workers");  // Workers
-			size_t worker_counter = 0;
+			// Workers
+			JSON::Array* json_workers_array = json_building->add_array("workers");
 			for (WareAmount input : productionsite->working_positions()) {
 				const WorkerDescr& worker = *tribe.get_worker_descr(input.first);
-				fw.open_brace();  // Worker
-				fw.write_key_value_string("name", worker.name());
-				fw.close_element();
-				fw.write_key_value_int("amount", input.second);
-				fw.close_brace(
-				   true, worker_counter, productionsite->working_positions().size());  // Worker
-				++worker_counter;
+				JSON::Object* json_input = json_workers_array->add_object();
+				json_input->add_string("name", worker.name());
+				json_input->add_int("amount", input.second);
 			}
-			fw.close_array(1, 5);  // Workers - we need a comma
 		} else if (upcast(MilitarySiteDescr const, militarysite, &building)) {
-			fw.write_key_value_int("conquers", militarysite->get_conquers());
-			fw.close_element();
-			fw.write_key_value_int("max_soldiers", militarysite->get_max_number_of_soldiers());
-			fw.close_element();
-			fw.write_key_value_int("heal_per_second", militarysite->get_heal_per_second());
-			fw.close_element();
+			json_building->add_int("conquers", militarysite->get_conquers());
+			json_building->add_int("max_soldiers", militarysite->get_max_number_of_soldiers());
+			json_building->add_int("heal_per_second", militarysite->get_heal_per_second());
 		}
 
-		switch (building.type()) {
-		case MapObjectType::PRODUCTIONSITE:
-			fw.write_key_value_string("type", "productionsite");
-			break;
-		case MapObjectType::WAREHOUSE:
-			fw.write_key_value_string("type", "warehouse");
-			break;
-		case MapObjectType::MARKET:
-			fw.write_key_value_string("type", "market");
-			break;
-		case MapObjectType::MILITARYSITE:
-			fw.write_key_value_string("type", "militarysite");
-			break;
-		case MapObjectType::TRAININGSITE:
-			fw.write_key_value_string("type", "trainingsite");
-			break;
-		default:
-			NEVER_HERE();
-		}
-		fw.close_element();
+		json_building->add_string("type", to_string(building.type()));
 
 		// Size
 		if (building.type() == MapObjectType::WAREHOUSE && !building.is_buildable() &&
 		    !building.is_enhanced()) {
-			fw.write_key_value_string("size", "headquarters");
+			json_building->add_string("size", "headquarters");
 		} else if (building.get_ismine()) {
-			fw.write_key_value_string("size", "mine");
+			json_building->add_string("size", "mine");
 		} else if (building.get_isport()) {
-			fw.write_key_value_string("size", "port");
+			json_building->add_string("size", "port");
 		} else {
-			fw.write_key_value_string("size", BaseImmovable::size_to_string(building.get_size()));
+			json_building->add_string("size", BaseImmovable::size_to_string(building.get_size()));
 		}
-		fw.close_element();
 
 		// Helptext
 		try {
@@ -290,16 +156,13 @@
 			cr->push_arg(building.helptext_script());
 			cr->resume();
 			const std::string help_text = cr->pop_string();
-			fw.write_key_value_string("helptext", help_text);
+			json_building->add_string("helptext", help_text);
 		} catch (LuaError& err) {
-			fw.write_key_value_string("helptext", err.what());
+			json_building->add_string("helptext", err.what());
 		}
-
-		fw.close_brace(true, i, buildings.size());  // Building
 	}
-	fw.close_array();  // Buildings
-	fw.close_brace();  // Main
-	fw.write(*out_filesystem, (boost::format("%s_buildings.json") % tribe.name()).str().c_str());
+
+	json->write_to_file(*out_filesystem, (boost::format("%s_buildings.json") % tribe.name()).str().c_str());
 	log("\n");
 }
 
@@ -311,22 +174,16 @@
 
 void write_wares(const TribeDescr& tribe, EditorGameBase& egbase, FileSystem* out_filesystem) {
 	log("\n===============\nWriting wares:\n===============\n");
-	JSONFileWrite fw;
-	fw.open_brace();  // Main
 
-	fw.open_array("wares");  // Wares
-	size_t counter = 0;
-	const size_t no_of_wares = tribe.wares().size();
+	std::unique_ptr<JSON::Element> json(new JSON::Element());
+	JSON::Array* json_wares_array = json->add_array("wares");
 	for (DescriptionIndex ware_index : tribe.wares()) {
 		const WareDescr& ware = *tribe.get_ware_descr(ware_index);
 		log(" %s", ware.name().c_str());
-		fw.open_brace();
-		fw.write_key_value_string("name", ware.name());
-		fw.close_element();
-		fw.write_key_value_string("descname", ware.descname());
-		fw.close_element();
-		fw.write_key_value_string("icon", ware.icon_filename());
-		fw.close_element();
+		JSON::Object* json_ware = json_wares_array->add_object();
+		json_ware->add_string("name", ware.name());
+		json_ware->add_string("descname", ware.descname());
+		json_ware->add_string("icon", ware.icon_filename());
 
 		// Helptext
 		try {
@@ -337,17 +194,13 @@
 			cr->push_arg(ware.helptext_script());
 			cr->resume();
 			const std::string help_text = cr->pop_string();
-			fw.write_key_value_string("helptext", help_text);
+			json_ware->add_string("helptext", help_text);
 		} catch (LuaError& err) {
-			fw.write_key_value_string("helptext", err.what());
+			json_ware->add_string("helptext", err.what());
 		}
-		fw.close_brace(true, counter, no_of_wares);  // Ware
-		++counter;
 	}
-	fw.close_array();  // Wares
 
-	fw.close_brace();  // Main
-	fw.write(*out_filesystem, (boost::format("%s_wares.json") % tribe.name()).str().c_str());
+	json->write_to_file(*out_filesystem, (boost::format("%s_wares.json") % tribe.name()).str().c_str());
 	log("\n");
 }
 
@@ -359,22 +212,16 @@
 
 void write_workers(const TribeDescr& tribe, EditorGameBase& egbase, FileSystem* out_filesystem) {
 	log("\n================\nWriting workers:\n================\n");
-	JSONFileWrite fw;
-	fw.open_brace();  // Main
 
-	fw.open_array("workers");  // Workers
-	size_t counter = 0;
-	const size_t no_of_workers = tribe.workers().size();
+	std::unique_ptr<JSON::Element> json(new JSON::Element());
+	JSON::Array* json_workers_array = json->add_array("workers");
 	for (DescriptionIndex worker_index : tribe.workers()) {
 		const WorkerDescr& worker = *tribe.get_worker_descr(worker_index);
 		log(" %s", worker.name().c_str());
-		fw.open_brace();
-		fw.write_key_value_string("name", worker.name());
-		fw.close_element();
-		fw.write_key_value_string("descname", worker.descname());
-		fw.close_element();
-		fw.write_key_value_string("icon", worker.icon_filename());
-		fw.close_element();
+		JSON::Object* json_worker = json_workers_array->add_object();
+		json_worker->add_string("name", worker.name());
+		json_worker->add_string("descname", worker.descname());
+		json_worker->add_string("icon", worker.icon_filename());
 
 		// Helptext
 		try {
@@ -384,28 +231,20 @@
 			cr->push_arg(worker.helptext_script());
 			cr->resume();
 			const std::string help_text = cr->pop_string();
-			fw.write_key_value_string("helptext", help_text);
+			json_worker->add_string("helptext", help_text);
 		} catch (LuaError& err) {
-			fw.write_key_value_string("helptext", err.what());
+			json_worker->add_string("helptext", err.what());
 		}
 
 		if (worker.becomes() != INVALID_INDEX) {
-			fw.close_element();
 			const WorkerDescr& becomes = *tribe.get_worker_descr(worker.becomes());
-			fw.write_key("becomes");
-			fw.open_brace();
-			fw.write_key_value_string("name", becomes.name());
-			fw.close_element();
-			fw.write_key_value_int("experience", worker.get_needed_experience());
-			fw.close_brace(true);
+			JSON::Object* json_becomes = json_worker->add_object("becomes");
+			json_becomes->add_string("name", becomes.name());
+			json_becomes->add_int("experience", worker.get_needed_experience());
 		}
-		fw.close_brace(true, counter, no_of_workers);  // Worker
-		++counter;
 	}
-	fw.close_array();  // Workers
 
-	fw.close_brace();  // Main
-	fw.write(*out_filesystem, (boost::format("%s_workers.json") % tribe.name()).str().c_str());
+	json->write_to_file(*out_filesystem, (boost::format("%s_workers.json") % tribe.name()).str().c_str());
 	log("\n");
 }
 
@@ -415,22 +254,17 @@
  ==========================================================
  */
 
-void add_tribe_info(const Widelands::TribeBasicInfo& tribe_info, JSONFileWrite* fw) {
-	fw->write_key_value_string("name", tribe_info.name);
-	fw->close_element();
-	fw->write_key_value_string("descname", tribe_info.descname);
-	fw->close_element();
-	fw->write_key_value_string("author", tribe_info.author);
-	fw->close_element();
-	fw->write_key_value_string("tooltip", tribe_info.tooltip);
-	fw->close_element();
-	fw->write_key_value_string("icon", tribe_info.icon);
+void add_tribe_info(const Widelands::TribeBasicInfo& tribe_info, JSON::Element* json_tribe) {
+	json_tribe->add_string("name", tribe_info.name);
+	json_tribe->add_string("descname", tribe_info.descname);
+	json_tribe->add_string("author", tribe_info.author);
+	json_tribe->add_string("tooltip", tribe_info.tooltip);
+	json_tribe->add_string("icon", tribe_info.icon);
 }
 
 void write_tribes(EditorGameBase& egbase, FileSystem* out_filesystem) {
-	JSONFileWrite fw;
-	fw.open_brace();          // Main
-	fw.open_array("tribes");  // Tribes
+	std::unique_ptr<JSON::Element> json(new JSON::Element());
+	JSON::Array* json_tribes_array = json->add_array("tribes");
 
 	/// Tribes
 	egbase.mutable_tribes()->postload();  // Make sure that all values have been set.
@@ -442,28 +276,22 @@
 		log("\n\n=========================\nWriting tribe: %s\n=========================\n",
 		    tribe_info.name.c_str());
 
-		fw.open_brace();  // TribeDescr
-		add_tribe_info(tribe_info, &fw);
-		fw.close_brace(true, tribe_index, tribeinfos.size());  // TribeDescr
+		// Main file
+		JSON::Object* json_tribe = json_tribes_array->add_object();
+		add_tribe_info(tribe_info, json_tribe);
 
 		// These go in separate files
-
-		JSONFileWrite fw_tribe;
-		fw_tribe.open_brace();  // TribeDescr
-		add_tribe_info(tribe_info, &fw_tribe);
-		fw_tribe.close_brace(true);  // TribeDescr
-		fw_tribe.write(
-		   *out_filesystem, (boost::format("tribe_%s.json") % tribe_info.name).str().c_str());
+		std::unique_ptr<JSON::Object> json_tribe_for_file(new JSON::Object());
+		add_tribe_info(tribe_info, json_tribe_for_file.get());
+		json_tribe_for_file->write_to_file(*out_filesystem, (boost::format("tribe_%s.json") % tribe_info.name).str().c_str());
 
 		const TribeDescr& tribe = *tribes.get_tribe_descr(tribes.tribe_index(tribe_info.name));
-
 		write_buildings(tribe, egbase, out_filesystem);
 		write_wares(tribe, egbase, out_filesystem);
 		write_workers(tribe, egbase, out_filesystem);
 	}
-	fw.close_array();  // Tribes
-	fw.close_brace();  // Main
-	fw.write(*out_filesystem, "tribes.json");
+
+	json->write_to_file(*out_filesystem, "tribes.json");
 }
 
 }  // namespace


Follow ups