2
0
Эх сурвалжийг харах

Merge pull request #3618 from IvanSavenko/json_refactor

Json refactor
Ivan Savenko 1 жил өмнө
parent
commit
54fefd34c7
95 өөрчлөгдсөн 1533 нэмэгдсэн , 1542 устгасан
  1. 1 1
      client/CMT.cpp
  2. 3 3
      client/ClientCommandManager.cpp
  3. 2 2
      client/globalLobby/GlobalLobbyClient.cpp
  4. 4 4
      client/render/CAnimation.cpp
  5. 1 1
      client/render/Graphics.cpp
  6. 1 1
      config/schemas/artifact.json
  7. 3 3
      config/schemas/creature.json
  8. 8 1
      config/schemas/hero.json
  9. 2 2
      config/schemas/heroClass.json
  10. 2 2
      config/schemas/objectTemplate.json
  11. 1 1
      config/schemas/obstacle.json
  12. 1 1
      config/schemas/river.json
  13. 1 1
      config/schemas/road.json
  14. 3 3
      config/schemas/spell.json
  15. 1 1
      config/schemas/terrain.json
  16. 2 2
      launcher/jsonutils.cpp
  17. 1 1
      launcher/updatedialog_moc.cpp
  18. 3 4
      lib/CArtHandler.cpp
  19. 1 1
      lib/CConfigHandler.cpp
  20. 3 3
      lib/CCreatureHandler.cpp
  21. 3 3
      lib/CHeroHandler.cpp
  22. 1 0
      lib/CMakeLists.txt
  23. 1 1
      lib/CSkillHandler.cpp
  24. 16 16
      lib/CTownHandler.cpp
  25. 6 7
      lib/bonuses/Bonus.cpp
  26. 3 3
      lib/bonuses/BonusEnum.cpp
  27. 1 1
      lib/bonuses/BonusList.cpp
  28. 1 1
      lib/bonuses/BonusParams.cpp
  29. 21 21
      lib/bonuses/Limiters.cpp
  30. 13 13
      lib/bonuses/Updaters.cpp
  31. 1 1
      lib/campaign/CampaignHandler.cpp
  32. 2 2
      lib/filesystem/Filesystem.cpp
  33. 107 111
      lib/json/JsonBonus.cpp
  34. 12 7
      lib/json/JsonBonus.h
  35. 20 0
      lib/json/JsonFormatException.h
  36. 153 82
      lib/json/JsonNode.cpp
  37. 107 110
      lib/json/JsonNode.h
  38. 258 127
      lib/json/JsonParser.cpp
  39. 23 45
      lib/json/JsonParser.h
  40. 4 4
      lib/json/JsonRandom.cpp
  41. 3 3
      lib/json/JsonRandom.h
  42. 12 151
      lib/json/JsonUtils.cpp
  43. 0 22
      lib/json/JsonUtils.h
  44. 523 552
      lib/json/JsonValidator.cpp
  45. 13 19
      lib/json/JsonValidator.h
  46. 37 37
      lib/json/JsonWriter.cpp
  47. 1 0
      lib/json/JsonWriter.h
  48. 1 1
      lib/mapObjectConstructors/CBankInstanceConstructor.cpp
  49. 5 5
      lib/mapObjectConstructors/CObjectClassesHandler.cpp
  50. 1 1
      lib/mapObjectConstructors/CRewardableConstructor.cpp
  51. 1 1
      lib/mapObjectConstructors/CommonConstructors.cpp
  52. 1 1
      lib/mapObjectConstructors/DwellingInstanceConstructor.cpp
  53. 3 6
      lib/mapObjects/MiscObjects.cpp
  54. 3 9
      lib/mapObjects/ObjectTemplate.cpp
  55. 1 1
      lib/mapping/CMapService.cpp
  56. 5 5
      lib/mapping/MapFormatH3M.cpp
  57. 10 13
      lib/mapping/MapFormatJson.cpp
  58. 5 5
      lib/mapping/MapIdentifiersH3M.cpp
  59. 5 2
      lib/modding/CModHandler.cpp
  60. 1 1
      lib/modding/CModInfo.cpp
  61. 5 5
      lib/modding/ContentTypeHandler.cpp
  62. 5 5
      lib/modding/IdentifierStorage.cpp
  63. 3 3
      lib/rewardable/Info.cpp
  64. 3 2
      lib/serializer/ESerializationVersion.h
  65. 1 1
      lib/serializer/JsonDeserializer.cpp
  66. 3 15
      lib/serializer/JsonSerializer.cpp
  67. 4 4
      lib/spells/CSpellHandler.cpp
  68. 1 1
      lib/spells/TargetCondition.cpp
  69. 2 0
      lib/spells/effects/Moat.h
  70. 1 1
      lobby/LobbyServer.cpp
  71. 4 4
      mapeditor/Animation.cpp
  72. 2 2
      mapeditor/jsonutils.cpp
  73. 1 1
      mapeditor/mainwindow.cpp
  74. 3 3
      mapeditor/mapsettings/translations.cpp
  75. 2 2
      scripting/lua/LuaScriptingContext.cpp
  76. 8 8
      scripting/lua/LuaSpellEffect.cpp
  77. 2 2
      scripting/lua/LuaStack.cpp
  78. 3 3
      server/GlobalLobbyProcessor.cpp
  79. 1 1
      test/JsonComparer.cpp
  80. 3 3
      test/entity/CCreatureTest.cpp
  81. 1 1
      test/game/CGameStateTest.cpp
  82. 2 2
      test/map/CMapFormatTest.cpp
  83. 1 1
      test/mock/mock_MapService.cpp
  84. 12 12
      test/scripting/LuaSpellEffectAPITest.cpp
  85. 9 9
      test/scripting/LuaSpellEffectTest.cpp
  86. 1 1
      test/scripting/ScriptFixture.cpp
  87. 1 1
      test/spells/TargetConditionTest.cpp
  88. 1 1
      test/spells/effects/CatapultTest.cpp
  89. 1 1
      test/spells/effects/CloneTest.cpp
  90. 2 2
      test/spells/effects/DamageTest.cpp
  91. 3 3
      test/spells/effects/DispelTest.cpp
  92. 8 8
      test/spells/effects/HealTest.cpp
  93. 2 2
      test/spells/effects/SacrificeTest.cpp
  94. 2 2
      test/spells/effects/SummonTest.cpp
  95. 2 2
      test/spells/effects/TimedTest.cpp

+ 1 - 1
client/CMT.cpp

@@ -243,7 +243,7 @@ int main(int argc, char * argv[])
 
 	// Initialize logging based on settings
 	logConfig->configure();
-	logGlobal->debug("settings = %s", settings.toJsonNode().toJson());
+	logGlobal->debug("settings = %s", settings.toJsonNode().toString());
 
 	// Some basic data validation to produce better error messages in cases of incorrect install
 	auto testFile = [](std::string filename, std::string message)

+ 3 - 3
client/ClientCommandManager.cpp

@@ -248,13 +248,13 @@ void ClientCommandManager::handleGetConfigCommand()
 			{
 				const JsonNode& object = nameAndObject.second;
 
-				std::string name = ModUtility::makeFullIdentifier(object.meta, contentName, nameAndObject.first);
+				std::string name = ModUtility::makeFullIdentifier(object.getModScope(), contentName, nameAndObject.first);
 
 				boost::algorithm::replace_all(name, ":", "_");
 
 				const boost::filesystem::path filePath = contentOutPath / (name + ".json");
 				std::ofstream file(filePath.c_str());
-				file << object.toJson();
+				file << object.toString();
 			}
 		}
 	}
@@ -358,7 +358,7 @@ void ClientCommandManager::handleBonusesCommand(std::istringstream & singleWordB
 	auto format = [outputFormat](const BonusList & b) -> std::string
 	{
 		if(outputFormat == "json")
-			return b.toJsonNode().toJson(true);
+			return b.toJsonNode().toCompactString();
 
 		std::ostringstream ss;
 		ss << b;

+ 2 - 2
client/globalLobby/GlobalLobbyClient.cpp

@@ -273,7 +273,7 @@ void GlobalLobbyClient::onDisconnected(const std::shared_ptr<INetworkConnection>
 
 void GlobalLobbyClient::sendMessage(const JsonNode & data)
 {
-	networkConnection->sendPacket(data.toBytes(true));
+	networkConnection->sendPacket(data.toBytes());
 }
 
 void GlobalLobbyClient::sendOpenPublicRoom()
@@ -362,5 +362,5 @@ void GlobalLobbyClient::sendProxyConnectionLogin(const NetworkConnectionPtr & ne
 	toSend["accountCookie"] = settings["lobby"]["accountCookie"];
 	toSend["gameRoomID"] = settings["lobby"]["roomID"];
 
-	netConnection->sendPacket(toSend.toBytes(true));
+	netConnection->sendPacket(toSend.toBytes());
 }

+ 4 - 4
client/render/CAnimation.cpp

@@ -102,7 +102,7 @@ void CAnimation::initFromJson(const JsonNode & config)
 	std::string basepath;
 	basepath = config["basepath"].String();
 
-	JsonNode base(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode base;
 	base["margins"] = config["margins"];
 	base["width"] = config["width"];
 	base["height"] = config["height"];
@@ -114,7 +114,7 @@ void CAnimation::initFromJson(const JsonNode & config)
 
 		for(const JsonNode & frame : group["frames"].Vector())
 		{
-			JsonNode toAdd(JsonNode::JsonType::DATA_STRUCT);
+			JsonNode toAdd;
 			JsonUtils::inherit(toAdd, base);
 			toAdd["file"].String() = basepath + frame.String();
 			source[groupID].push_back(toAdd);
@@ -129,7 +129,7 @@ void CAnimation::initFromJson(const JsonNode & config)
 		if (source[group].size() <= frame)
 			source[group].resize(frame+1);
 
-		JsonNode toAdd(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode toAdd;
 		JsonUtils::inherit(toAdd, base);
 		toAdd["file"].String() = basepath + node["file"].String();
 		source[group][frame] = toAdd;
@@ -191,7 +191,7 @@ void CAnimation::init()
 		std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
 		stream->read(textData.get(), stream->getSize());
 
-		const JsonNode config((char*)textData.get(), stream->getSize());
+		const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize());
 
 		initFromJson(config);
 	}

+ 1 - 1
client/render/Graphics.cpp

@@ -107,7 +107,7 @@ void Graphics::initializeBattleGraphics()
 		if(!CResourceHandler::get(mod)->existsResource(ResourcePath("config/battles_graphics.json")))
 			continue;
 			
-		const JsonNode config(mod, JsonPath::builtin("config/battles_graphics.json"));
+		const JsonNode config(JsonPath::builtin("config/battles_graphics.json"), mod);
 
 		//initialization of AC->def name mapping
 		if(!config["ac_mapping"].isNull())

+ 1 - 1
config/schemas/artifact.json

@@ -93,7 +93,7 @@
 				"map" : {
 					"type" : "string",
 					"description" : ".def file for adventure map",
-					"format" : "defFile"
+					"format" : "animationFile"
 				}
 			}
 		},

+ 3 - 3
config/schemas/creature.json

@@ -133,12 +133,12 @@
 				"animation" : {
 					"type" : "string",
 					"description" : "File with animation of this creature in battles",
-					"format" : "defFile"
+					"format" : "animationFile"
 				},
 				"map" : {
 					"type" : "string",
 					"description" : "File with animation of this creature on adventure map",
-					"format" : "defFile"
+					"format" : "animationFile"
 				},
 				"mapMask" : {
 					"type" : "array",
@@ -184,7 +184,7 @@
 						"projectile" : {
 							"type" : "string",
 							"description" : "Path to projectile animation",
-							"format" : "defFile"
+							"format" : "animationFile"
 						},
 						"ray" : {
 							"type" : "array",

+ 8 - 1
config/schemas/hero.json

@@ -29,7 +29,14 @@
 		"battleImage" : {
 			"type" : "string",
 			"description" : "Custom animation to be used on battle, overrides hero class property",
-			"format" : "defFile"
+			"format" : "animationFile"
+		},
+		"compatibilityIdentifiers" : {
+			"type" : "array",
+			"items" : {
+				"type" : "string",
+			},
+			"description" : "Additional identifiers that may refer to this object, to provide compatibility after object has been renamed"
 		},
 		"images" : {
 			"type" : "object",

+ 2 - 2
config/schemas/heroClass.json

@@ -42,12 +42,12 @@
 						"female" : {
 							"type" : "string",
 							"description" : "Female version",
-							"format" : "defFile"
+							"format" : "animationFile"
 						},
 						"male" : {
 							"type" : "string",
 							"description" : "Male version",
-							"format" : "defFile"
+							"format" : "animationFile"
 						}
 					}
 				}

+ 2 - 2
config/schemas/objectTemplate.json

@@ -9,12 +9,12 @@
 		"animation" : {
 			"type" : "string",
 			"description" : "Path to def file with animation of this object",
-			"format" : "defFile"
+			"format" : "animationFile"
 		},
 		"editorAnimation" : {
 			"type" : "string",
 			"description" : "Optional path to def file with animation of this object to use in map editor",
-			"format" : "defFile"
+			"format" : "animationFile"
 		},
 		"visitableFrom" : {
 			"type" : "array",

+ 1 - 1
config/schemas/obstacle.json

@@ -45,7 +45,7 @@
 			"type" : "string",
 			"description" : "Image resource",
 			"anyOf" : [
-				{ "format" : "defFile" },
+				{ "format" : "animationFile" },
 				{ "format" : "imageFile" }
 			]
 		},

+ 1 - 1
config/schemas/river.json

@@ -20,7 +20,7 @@
 		{
 			"type" : "string",
 			"description" : "Name of file with river graphics",
-			"format" : "defFile"
+			"format" : "animationFile"
 		},
 		"delta" :
 		{

+ 1 - 1
config/schemas/road.json

@@ -20,7 +20,7 @@
 		{
 			"type" : "string",
 			"description" : "Name of file with road graphics",
-			"format" : "defFile"
+			"format" : "animationFile"
 		},
 		"moveCost" :
 		{

+ 3 - 3
config/schemas/spell.json

@@ -15,13 +15,13 @@
 					{
 						//assumed verticalPosition: top
 						"type" : "string",
-						"format" : "defFile"
+						"format" : "animationFile"
 					},
 					{
 						"type" : "object",
 						"properties" : {
 							"verticalPosition" : {"type" : "string", "enum" :["top","bottom"]},
-							"defName" : {"type" : "string", "format" : "defFile"},
+							"defName" : {"type" : "string", "format" : "animationFile"},
 							"effectName" : { "type" : "string" }
 						},
 						"additionalProperties" : false
@@ -41,7 +41,7 @@
 					"items" : {
 						"type" : "object",
 						"properties" : {
-							"defName" : {"type" : "string", "format" : "defFile"},
+							"defName" : {"type" : "string", "format" : "animationFile"},
 							"minimumAngle" : {"type" : "number", "minimum" : 0}
 						},
 						"additionalProperties" : false

+ 1 - 1
config/schemas/terrain.json

@@ -35,7 +35,7 @@
 		{
 			"type" : "string",
 			"description" : "Name of file with graphicks",
-			"format" : "defFile"
+			"format" : "animationFile"
 		},
 		"rockTerrain" :
 		{

+ 2 - 2
launcher/jsonutils.cpp

@@ -89,7 +89,7 @@ QVariant JsonFromFile(QString filename)
 	}
 
 	const auto data = file.readAll();
-	JsonNode node(data.data(), data.size());
+	JsonNode node(reinterpret_cast<const std::byte*>(data.data()), data.size());
 	return toVariant(node);
 }
 
@@ -116,7 +116,7 @@ JsonNode toJson(QVariant object)
 void JsonToFile(QString filename, QVariant object)
 {
 	std::fstream file(qstringToPath(filename).c_str(), std::ios::out | std::ios_base::binary);
-	file << toJson(object).toJson();
+	file << toJson(object).toString();
 }
 
 }

+ 1 - 1
launcher/updatedialog_moc.cpp

@@ -67,7 +67,7 @@ UpdateDialog::UpdateDialog(bool calledManually, QWidget *parent):
 		}
 		
 		auto byteArray = response->readAll();
-		JsonNode node(byteArray.constData(), byteArray.size());
+		JsonNode node(reinterpret_cast<const std::byte*>(byteArray.constData()), byteArray.size());
 		loadFromJson(node);
 	});
 }

+ 3 - 4
lib/CArtHandler.cpp

@@ -350,8 +350,7 @@ std::vector<JsonNode> CArtHandler::loadLegacyData()
 		{
 			if(parser.readString() == "x")
 			{
-				artData["slot"].Vector().push_back(JsonNode());
-				artData["slot"].Vector().back().String() = artSlot;
+				artData["slot"].Vector().emplace_back(artSlot);
 			}
 		}
 		artData["class"].String() = classes.at(parser.readString()[0]);
@@ -461,7 +460,7 @@ CArtifact * CArtHandler::loadFromJson(const std::string & scope, const JsonNode
 	VLC->identifiers()->requestIdentifier(scope, "object", "artifact", [=](si32 index)
 	{
 		JsonNode conf;
-		conf.setMeta(scope);
+		conf.setModScope(scope);
 
 		VLC->objtypeh->loadSubObject(art->identifier, conf, Obj::ARTIFACT, art->getIndex());
 
@@ -469,7 +468,7 @@ CArtifact * CArtHandler::loadFromJson(const std::string & scope, const JsonNode
 		{
 			JsonNode templ;
 			templ["animation"].String() = art->advMapDef;
-			templ.setMeta(scope);
+			templ.setModScope(scope);
 
 			// add new template.
 			// Necessary for objects added via mods that don't have any templates in H3

+ 1 - 1
lib/CConfigHandler.cpp

@@ -90,7 +90,7 @@ void SettingsStorage::invalidateNode(const std::vector<std::string> &changedPath
 		JsonUtils::minimize(savedConf, schema);
 
 	std::fstream file(CResourceHandler::get()->getResourceName(JsonPath::builtin(dataFilename))->c_str(), std::ofstream::out | std::ofstream::trunc);
-	file << savedConf.toJson();
+	file << savedConf.toString();
 }
 
 JsonNode & SettingsStorage::getNode(const std::vector<std::string> & path)

+ 3 - 3
lib/CCreatureHandler.cpp

@@ -418,7 +418,7 @@ void CCreatureHandler::loadCommanders()
 
 	std::string modSource = VLC->modh->findResourceOrigin(configResource);
 	JsonNode data(configResource);
-	data.setMeta(modSource);
+	data.setModScope(modSource);
 
 	const JsonNode & config = data; // switch to const data accessors
 
@@ -640,7 +640,7 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json
 	VLC->identifiers()->requestIdentifier(scope, "object", "monster", [=](si32 index)
 	{
 		JsonNode conf;
-		conf.setMeta(scope);
+		conf.setModScope(scope);
 
 		VLC->objtypeh->loadSubObject(cre->identifier, conf, Obj::MONSTER, cre->getId().num);
 		if (!advMapFile.isNull())
@@ -649,7 +649,7 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json
 			templ["animation"] = advMapFile;
 			if (!advMapMask.isNull())
 				templ["mask"] = advMapMask;
-			templ.setMeta(scope);
+			templ.setModScope(scope);
 
 			// if creature has custom advMapFile, reset any potentially imported H3M templates and use provided file instead
 			VLC->objtypeh->getHandlerFor(Obj::MONSTER, cre->getId().num)->clearTemplates();

+ 3 - 3
lib/CHeroHandler.cpp

@@ -293,7 +293,7 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js
 	for(auto skillPair : node["secondarySkills"].Struct())
 	{
 		int probability = static_cast<int>(skillPair.second.Integer());
-		VLC->identifiers()->requestIdentifier(skillPair.second.meta, "skill", skillPair.first, [heroClass, probability](si32 skillID)
+		VLC->identifiers()->requestIdentifier(skillPair.second.getModScope(), "skill", skillPair.first, [heroClass, probability](si32 skillID)
 		{
 			heroClass->secSkillProbability[skillID] = probability;
 		});
@@ -310,7 +310,7 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js
 	{
 		int value = static_cast<int>(tavern.second.Float());
 
-		VLC->identifiers()->requestIdentifier(tavern.second.meta, "faction", tavern.first,
+		VLC->identifiers()->requestIdentifier(tavern.second.getModScope(), "faction", tavern.first,
 		[=](si32 factionID)
 		{
 			heroClass->selectionProbability[FactionID(factionID)] = value;
@@ -329,7 +329,7 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js
 		classConf["heroClass"].String() = identifier;
 		if (!node["compatibilityIdentifiers"].isNull())
 			classConf["compatibilityIdentifiers"] = node["compatibilityIdentifiers"];
-		classConf.setMeta(scope);
+		classConf.setModScope(scope);
 		VLC->objtypeh->loadSubObject(identifier, classConf, index, heroClass->getIndex());
 	});
 

+ 1 - 0
lib/CMakeLists.txt

@@ -405,6 +405,7 @@ set(lib_HEADERS
 	filesystem/ResourcePath.h
 
 	json/JsonBonus.h
+	json/JsonFormatException.h
 	json/JsonNode.h
 	json/JsonParser.h
 	json/JsonRandom.h

+ 1 - 1
lib/CSkillHandler.cpp

@@ -168,7 +168,7 @@ std::vector<JsonNode> CSkillHandler::loadLegacyData()
 	std::vector<JsonNode> legacyData;
 	for(int id = 0; id < GameConstants::SKILL_QUANTITY; id++)
 	{
-		JsonNode skillNode(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode skillNode;
 		skillNode["name"].String() = skillNames[id];
 		for(int level = 1; level < NSecondarySkill::levels.size(); level++)
 		{

+ 16 - 16
lib/CTownHandler.cpp

@@ -617,7 +617,7 @@ void CTownHandler::loadSpecialBuildingBonuses(const JsonNode & source, BonusList
 void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source)
 {
 	assert(stringID.find(':') == std::string::npos);
-	assert(!source.meta.empty());
+	assert(!source.getModScope().empty());
 
 	auto * ret = new CBuilding();
 	ret->bid = getMappedValue<BuildingID, std::string>(stringID, BuildingID::NONE, MappedKeys::BUILDING_NAMES_TO_TYPES, false);
@@ -640,11 +640,11 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
 	ret->height = getMappedValue<CBuilding::ETowerHeight>(source["height"], CBuilding::HEIGHT_NO_TOWER, CBuilding::TOWER_TYPES);
 
 	ret->identifier = stringID;
-	ret->modScope = source.meta;
+	ret->modScope = source.getModScope();
 	ret->town = town;
 
-	VLC->generaltexth->registerString(source.meta, ret->getNameTextID(), source["name"].String());
-	VLC->generaltexth->registerString(source.meta, ret->getDescriptionTextID(), source["description"].String());
+	VLC->generaltexth->registerString(source.getModScope(), ret->getNameTextID(), source["name"].String());
+	VLC->generaltexth->registerString(source.getModScope(), ret->getDescriptionTextID(), source["description"].String());
 
 	ret->resources = TResources(source["cost"]);
 	ret->produce =   TResources(source["produce"]);
@@ -729,7 +729,7 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
 
 	ret->town->buildings[ret->bid] = ret;
 
-	registerObject(source.meta, ret->town->getBuildingScope(), ret->identifier, ret->bid.getNum());
+	registerObject(source.getModScope(), ret->town->getBuildingScope(), ret->identifier, ret->bid.getNum());
 }
 
 void CTownHandler::loadBuildings(CTown * town, const JsonNode & source)
@@ -751,14 +751,14 @@ void CTownHandler::loadStructure(CTown &town, const std::string & stringID, cons
 	ret->building = nullptr;
 	ret->buildable = nullptr;
 
-	VLC->identifiers()->tryRequestIdentifier( source.meta, "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable
+	VLC->identifiers()->tryRequestIdentifier( source.getModScope(), "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable
 	{
 		ret->building = town.buildings[BuildingID(identifier)];
 	});
 
 	if (source["builds"].isNull())
 	{
-		VLC->identifiers()->tryRequestIdentifier( source.meta, "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable
+		VLC->identifiers()->tryRequestIdentifier( source.getModScope(), "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable
 		{
 			ret->building = town.buildings[BuildingID(identifier)];
 		});
@@ -944,7 +944,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
 	}
 	else
 	{
-		VLC->identifiers()->requestIdentifier( source.meta, "spell", "castleMoat", [=](si32 ability)
+		VLC->identifiers()->requestIdentifier( source.getModScope(), "spell", "castleMoat", [=](si32 ability)
 		{
 			town->moatAbility = SpellID(ability);
 		});
@@ -984,7 +984,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
 	{
 		int chance = static_cast<int>(node.second.Float());
 
-		VLC->identifiers()->requestIdentifier(node.second.meta, "heroClass",node.first, [=](si32 classID)
+		VLC->identifiers()->requestIdentifier(node.second.getModScope(), "heroClass",node.first, [=](si32 classID)
 		{
 			VLC->heroclassesh->objects[classID]->selectionProbability[town->faction->getId()] = chance;
 		});
@@ -994,7 +994,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
 	{
 		int chance = static_cast<int>(node.second.Float());
 
-		VLC->identifiers()->requestIdentifier(node.second.meta, "spell", node.first, [=](si32 spellID)
+		VLC->identifiers()->requestIdentifier(node.second.getModScope(), "spell", node.first, [=](si32 spellID)
 		{
 			VLC->spellh->objects.at(spellID)->probabilities[town->faction->getId()] = chance;
 		});
@@ -1120,9 +1120,9 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
 			// register town once objects are loaded
 			JsonNode config = data["town"]["mapObject"];
 			config["faction"].String() = name;
-			config["faction"].meta = scope;
-			if (config.meta.empty())// MODS COMPATIBILITY FOR 0.96
-				config.meta = scope;
+			config["faction"].setModScope(scope, false);
+			if (config.getModScope().empty())// MODS COMPATIBILITY FOR 0.96
+				config.setModScope(scope, false);
 			VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index);
 
 			// MODS COMPATIBILITY FOR 0.96
@@ -1163,7 +1163,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
 			// register town once objects are loaded
 			JsonNode config = data["town"]["mapObject"];
 			config["faction"].String() = name;
-			config["faction"].meta = scope;
+			config["faction"].setModScope(scope, false);
 			VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index);
 		});
 	}
@@ -1174,7 +1174,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
 void CTownHandler::loadRandomFaction()
 {
 	JsonNode randomFactionJson(JsonPath::builtin("config/factions/random.json"));
-	randomFactionJson.setMeta(ModScope::scopeBuiltin(), true);
+	randomFactionJson.setModScope(ModScope::scopeBuiltin(), true);
 	loadBuildings(randomTown, randomFactionJson["random"]["town"]["buildings"]);
 }
 
@@ -1201,7 +1201,7 @@ void CTownHandler::initializeRequirements()
 			{
 				logMod->error("Unexpected length of town buildings requirements: %d", node.Vector().size());
 				logMod->error("Entry contains: ");
-				logMod->error(node.toJson());
+				logMod->error(node.toString());
 			}
 
 			auto index = VLC->identifiers()->getIdentifier(requirement.town->getBuildingScope(), node[0]);

+ 6 - 7
lib/bonuses/Bonus.cpp

@@ -26,7 +26,6 @@
 #include "../TerrainHandler.h"
 #include "../constants/StringConstants.h"
 #include "../battle/BattleInfo.h"
-#include "../json/JsonUtils.h"
 #include "../modding/ModUtility.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -72,20 +71,20 @@ si32 CAddInfo::operator[](size_type pos) const
 
 std::string CAddInfo::toString() const
 {
-	return toJsonNode().toJson(true);
+	return toJsonNode().toCompactString();
 }
 
 JsonNode CAddInfo::toJsonNode() const
 {
 	if(size() < 2)
 	{
-		return JsonUtils::intNode(operator[](0));
+		return JsonNode(operator[](0));
 	}
 	else
 	{
-		JsonNode node(JsonNode::JsonType::DATA_VECTOR);
+		JsonNode node;
 		for(si32 value : *this)
-			node.Vector().push_back(JsonUtils::intNode(value));
+			node.Vector().emplace_back(value);
 		return node;
 	}
 }
@@ -143,7 +142,7 @@ static JsonNode additionalInfoToJson(BonusType type, CAddInfo addInfo)
 	switch(type)
 	{
 	case BonusType::SPECIAL_UPGRADE:
-		return JsonUtils::stringNode(ModUtility::makeFullIdentifier("", "creature", CreatureID::encode(addInfo[0])));
+		return JsonNode(ModUtility::makeFullIdentifier("", "creature", CreatureID::encode(addInfo[0])));
 	default:
 		return addInfo.toJsonNode();
 	}
@@ -151,7 +150,7 @@ static JsonNode additionalInfoToJson(BonusType type, CAddInfo addInfo)
 
 JsonNode Bonus::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 	// only add values that might reasonably be found in config files
 	root["type"].String() = vstd::findKey(bonusNameMap, type);
 	if(subtype != BonusSubtypeID())

+ 3 - 3
lib/bonuses/BonusEnum.cpp

@@ -67,13 +67,13 @@ namespace BonusDuration
 		}
 		if(durationNames.size() == 1)
 		{
-			return JsonUtils::stringNode(durationNames[0]);
+			return JsonNode(durationNames[0]);
 		}
 		else
 		{
-			JsonNode node(JsonNode::JsonType::DATA_VECTOR);
+			JsonNode node;
 			for(const std::string & dur : durationNames)
-				node.Vector().push_back(JsonUtils::stringNode(dur));
+				node.Vector().emplace_back(dur);
 			return node;
 		}
 	}

+ 1 - 1
lib/bonuses/BonusList.cpp

@@ -213,7 +213,7 @@ int BonusList::valOfBonuses(const CSelector &select) const
 
 JsonNode BonusList::toJsonNode() const
 {
-	JsonNode node(JsonNode::JsonType::DATA_VECTOR);
+	JsonNode node;
 	for(const std::shared_ptr<Bonus> & b : bonuses)
 		node.Vector().push_back(b->toJsonNode());
 	return node;

+ 1 - 1
lib/bonuses/BonusParams.cpp

@@ -353,7 +353,7 @@ const JsonNode & BonusParams::toJson()
 			ret["targetSourceType"].String() = vstd::findKey(bonusSourceMap, *targetType);
 		jsonCreated = true;
 	}
-	ret.setMeta(ModScope::scopeGame());
+	ret.setModScope(ModScope::scopeGame());
 	return ret;
 };
 

+ 21 - 21
lib/bonuses/Limiters.cpp

@@ -92,7 +92,7 @@ std::string ILimiter::toString() const
 
 JsonNode ILimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 	root["type"].String() = toString();
 	return root;
 }
@@ -127,11 +127,11 @@ std::string CCreatureTypeLimiter::toString() const
 
 JsonNode CCreatureTypeLimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "CREATURE_TYPE_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(creature->getJsonKey()));
-	root["parameters"].Vector().push_back(JsonUtils::boolNode(includeUpgrades));
+	root["parameters"].Vector().emplace_back(creature->getJsonKey());
+	root["parameters"].Vector().emplace_back(includeUpgrades);
 
 	return root;
 }
@@ -199,16 +199,16 @@ std::string HasAnotherBonusLimiter::toString() const
 
 JsonNode HasAnotherBonusLimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 	std::string typeName = vstd::findKey(bonusNameMap, type);
 	auto sourceTypeName = vstd::findKey(bonusSourceMap, source);
 
 	root["type"].String() = "HAS_ANOTHER_BONUS_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(typeName));
+	root["parameters"].Vector().emplace_back(typeName);
 	if(isSubtypeRelevant)
-		root["parameters"].Vector().push_back(JsonUtils::stringNode(subtype.toString()));
+		root["parameters"].Vector().emplace_back(subtype.toString());
 	if(isSourceRelevant)
-		root["parameters"].Vector().push_back(JsonUtils::stringNode(sourceTypeName));
+		root["parameters"].Vector().emplace_back(sourceTypeName);
 
 	return root;
 }
@@ -233,11 +233,11 @@ UnitOnHexLimiter::UnitOnHexLimiter(const std::set<BattleHex> & applicableHexes):
 
 JsonNode UnitOnHexLimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "UNIT_ON_HEXES";
 	for(const auto & hex : applicableHexes)
-		root["parameters"].Vector().push_back(JsonUtils::intNode(hex));
+		root["parameters"].Vector().emplace_back(hex);
 
 	return root;
 }
@@ -278,11 +278,11 @@ std::string CreatureTerrainLimiter::toString() const
 
 JsonNode CreatureTerrainLimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "CREATURE_TERRAIN_LIMITER";
 	auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey();
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainName));
+	root["parameters"].Vector().emplace_back(terrainName);
 
 	return root;
 }
@@ -324,10 +324,10 @@ std::string FactionLimiter::toString() const
 
 JsonNode FactionLimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "FACTION_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(VLC->factions()->getById(faction)->getJsonKey()));
+	root["parameters"].Vector().emplace_back(VLC->factions()->getById(faction)->getJsonKey());
 
 	return root;
 }
@@ -354,11 +354,11 @@ std::string CreatureLevelLimiter::toString() const
 
 JsonNode CreatureLevelLimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "CREATURE_LEVEL_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::intNode(minLevel));
-	root["parameters"].Vector().push_back(JsonUtils::intNode(maxLevel));
+	root["parameters"].Vector().emplace_back(minLevel);
+	root["parameters"].Vector().emplace_back(maxLevel);
 
 	return root;
 }
@@ -392,10 +392,10 @@ std::string CreatureAlignmentLimiter::toString() const
 
 JsonNode CreatureAlignmentLimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "CREATURE_ALIGNMENT_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)]));
+	root["parameters"].Vector().emplace_back(GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)]);
 
 	return root;
 }
@@ -450,8 +450,8 @@ void AggregateLimiter::add(const TLimiterPtr & limiter)
 
 JsonNode AggregateLimiter::toJsonNode() const
 {
-	JsonNode result(JsonNode::JsonType::DATA_VECTOR);
-	result.Vector().push_back(JsonUtils::stringNode(getAggregator()));
+	JsonNode result;
+	result.Vector().emplace_back(getAggregator());
 	for(const auto & l : limiters)
 		result.Vector().push_back(l->toJsonNode());
 	return result;

+ 13 - 13
lib/bonuses/Updaters.cpp

@@ -13,7 +13,7 @@
 #include "Updaters.h"
 #include "Limiters.h"
 
-#include "../json/JsonUtils.h"
+#include "../json/JsonNode.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../CStack.h"
 
@@ -39,7 +39,7 @@ std::string IUpdater::toString() const
 
 JsonNode IUpdater::toJsonNode() const
 {
-	return JsonNode(JsonNode::JsonType::DATA_NULL);
+	return JsonNode();
 }
 
 GrowsWithLevelUpdater::GrowsWithLevelUpdater(int valPer20, int stepSize) : valPer20(valPer20), stepSize(stepSize)
@@ -69,12 +69,12 @@ std::string GrowsWithLevelUpdater::toString() const
 
 JsonNode GrowsWithLevelUpdater::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "GROWS_WITH_LEVEL";
-	root["parameters"].Vector().push_back(JsonUtils::intNode(valPer20));
+	root["parameters"].Vector().emplace_back(valPer20);
 	if(stepSize > 1)
-		root["parameters"].Vector().push_back(JsonUtils::intNode(stepSize));
+		root["parameters"].Vector().emplace_back(stepSize);
 
 	return root;
 }
@@ -98,7 +98,7 @@ std::string TimesHeroLevelUpdater::toString() const
 
 JsonNode TimesHeroLevelUpdater::toJsonNode() const
 {
-	return JsonUtils::stringNode("TIMES_HERO_LEVEL");
+	return JsonNode("TIMES_HERO_LEVEL");
 }
 
 ArmyMovementUpdater::ArmyMovementUpdater():
@@ -141,13 +141,13 @@ std::string ArmyMovementUpdater::toString() const
 
 JsonNode ArmyMovementUpdater::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "ARMY_MOVEMENT";
-	root["parameters"].Vector().push_back(JsonUtils::intNode(base));
-	root["parameters"].Vector().push_back(JsonUtils::intNode(divider));
-	root["parameters"].Vector().push_back(JsonUtils::intNode(multiplier));
-	root["parameters"].Vector().push_back(JsonUtils::intNode(max));
+	root["parameters"].Vector().emplace_back(base);
+	root["parameters"].Vector().emplace_back(divider);
+	root["parameters"].Vector().emplace_back(multiplier);
+	root["parameters"].Vector().emplace_back(max);
 
 	return root;
 }
@@ -183,7 +183,7 @@ std::string TimesStackLevelUpdater::toString() const
 
 JsonNode TimesStackLevelUpdater::toJsonNode() const
 {
-	return JsonUtils::stringNode("TIMES_STACK_LEVEL");
+	return JsonNode("TIMES_STACK_LEVEL");
 }
 
 std::string OwnerUpdater::toString() const
@@ -193,7 +193,7 @@ std::string OwnerUpdater::toString() const
 
 JsonNode OwnerUpdater::toJsonNode() const
 {
-	return JsonUtils::stringNode("BONUS_OWNER_UPDATER");
+	return JsonNode("BONUS_OWNER_UPDATER");
 }
 
 std::shared_ptr<Bonus> OwnerUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const

+ 1 - 1
lib/campaign/CampaignHandler.cpp

@@ -46,7 +46,7 @@ void CampaignHandler::readCampaign(Campaign * ret, const std::vector<ui8> & inpu
 	}
 	else // text format (json)
 	{
-		JsonNode jsonCampaign((const char*)input.data(), input.size());
+		JsonNode jsonCampaign(reinterpret_cast<const std::byte*>(input.data()), input.size());
 		readHeaderFromJson(*ret, jsonCampaign, filename, modName, encoding);
 
 		for(auto & scenario : jsonCampaign["scenarios"].Vector())

+ 2 - 2
lib/filesystem/Filesystem.cpp

@@ -117,7 +117,7 @@ void CFilesystemGenerator::loadJsonMap(const std::string &mountPoint, const Json
 	if (filename)
 	{
 		auto configData = CResourceHandler::get("initial")->load(JsonPath::builtin(URI))->readAll();
-		const JsonNode configInitial(reinterpret_cast<char *>(configData.first.get()), configData.second);
+		const JsonNode configInitial(reinterpret_cast<std::byte *>(configData.first.get()), configData.second);
 		filesystem->addLoader(new CMappedFileLoader(mountPoint, configInitial), false);
 	}
 }
@@ -212,7 +212,7 @@ void CResourceHandler::load(const std::string &fsConfigURI, bool extractArchives
 {
 	auto fsConfigData = get("initial")->load(JsonPath::builtin(fsConfigURI))->readAll();
 
-	const JsonNode fsConfig(reinterpret_cast<char *>(fsConfigData.first.get()), fsConfigData.second);
+	const JsonNode fsConfig(reinterpret_cast<std::byte *>(fsConfigData.first.get()), fsConfigData.second);
 
 	addFilesystem("data", ModScope::scopeBuiltin(), createFileSystem("", fsConfig["filesystem"], extractArchives));
 }

+ 107 - 111
lib/json/JsonBonus.cpp

@@ -13,22 +13,46 @@
 
 #include "JsonValidator.h"
 
-#include "../ScopeGuard.h"
+#include "../CGeneralTextHandler.h"
+#include "../VCMI_Lib.h"
 #include "../bonuses/BonusParams.h"
-#include "../bonuses/Bonus.h"
 #include "../bonuses/Limiters.h"
 #include "../bonuses/Propagators.h"
 #include "../bonuses/Updaters.h"
-#include "../filesystem/Filesystem.h"
-#include "../modding/IdentifierStorage.h"
-#include "../VCMI_Lib.h" //for identifier resolution
-#include "../CGeneralTextHandler.h"
 #include "../constants/StringConstants.h"
-#include "../battle/BattleHex.h"
+#include "../modding/IdentifierStorage.h"
 
-VCMI_LIB_NAMESPACE_BEGIN
+VCMI_LIB_USING_NAMESPACE
+
+template <typename T>
+const T parseByMap(const std::map<std::string, T> & map, const JsonNode * val, const std::string & err)
+{
+	if (!val->isNull())
+	{
+		const std::string & type = val->String();
+		auto it = map.find(type);
+		if (it == map.end())
+		{
+			logMod->error("Error: invalid %s%s.", err, type);
+			return {};
+		}
+		else
+		{
+			return it->second;
+		}
+	}
+	else
+		return {};
+}
 
-static const JsonNode nullNode;
+template <typename T>
+const T parseByMapN(const std::map<std::string, T> & map, const JsonNode * val, const std::string & err)
+{
+	if(val->isNumber())
+		return static_cast<T>(val->Integer());
+	else
+		return parseByMap<T>(map, val, err);
+}
 
 static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const JsonNode & node)
 {
@@ -40,14 +64,14 @@ static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const Jso
 
 	if (node.isNumber()) // Compatibility code for 1.3 or older
 	{
-		logMod->warn("Bonus subtype must be string! (%s)", node.meta);
+		logMod->warn("Bonus subtype must be string! (%s)", node.getModScope());
 		subtype = BonusCustomSubtype(node.Integer());
 		return;
 	}
 
 	if (!node.isString())
 	{
-		logMod->warn("Bonus subtype must be string! (%s)", node.meta);
+		logMod->warn("Bonus subtype must be string! (%s)", node.getModScope());
 		subtype = BonusSubtypeID();
 		return;
 	}
@@ -270,6 +294,77 @@ static void loadBonusSourceInstance(BonusSourceID & sourceInstance, BonusSource
 	}
 }
 
+static BonusParams convertDeprecatedBonus(const JsonNode &ability)
+{
+	if(vstd::contains(deprecatedBonusSet, ability["type"].String()))
+	{
+		logMod->warn("There is deprecated bonus found:\n%s\nTrying to convert...", ability.toString());
+		auto params = BonusParams(ability["type"].String(),
+											ability["subtype"].isString() ? ability["subtype"].String() : "",
+											   ability["subtype"].isNumber() ? ability["subtype"].Integer() : -1);
+		if(params.isConverted)
+		{
+			if(ability["type"].String() == "SECONDARY_SKILL_PREMY" && bonusValueMap.find(ability["valueType"].String())->second == BonusValueType::PERCENT_TO_BASE) //assume secondary skill special
+			{
+				params.valueType = BonusValueType::PERCENT_TO_TARGET_TYPE;
+				params.targetType = BonusSource::SECONDARY_SKILL;
+			}
+
+			logMod->warn("Please, use this bonus:\n%s\nConverted successfully!", params.toJson().toString());
+			return params;
+		}
+		else
+			logMod->error("Cannot convert bonus!\n%s", ability.toString());
+	}
+	BonusParams ret;
+	ret.isConverted = false;
+	return ret;
+}
+
+static TUpdaterPtr parseUpdater(const JsonNode & updaterJson)
+{
+	switch(updaterJson.getType())
+	{
+	case JsonNode::JsonType::DATA_STRING:
+		return parseByMap(bonusUpdaterMap, &updaterJson, "updater type ");
+		break;
+	case JsonNode::JsonType::DATA_STRUCT:
+		if(updaterJson["type"].String() == "GROWS_WITH_LEVEL")
+		{
+			auto updater = std::make_shared<GrowsWithLevelUpdater>();
+			const JsonVector param = updaterJson["parameters"].Vector();
+			updater->valPer20 = static_cast<int>(param[0].Integer());
+			if(param.size() > 1)
+				updater->stepSize = static_cast<int>(param[1].Integer());
+			return updater;
+		}
+		else if (updaterJson["type"].String() == "ARMY_MOVEMENT")
+		{
+			auto updater = std::make_shared<ArmyMovementUpdater>();
+			if(updaterJson["parameters"].isVector())
+			{
+				const auto & param = updaterJson["parameters"].Vector();
+				if(param.size() < 4)
+					logMod->warn("Invalid ARMY_MOVEMENT parameters, using default!");
+				else
+				{
+					updater->base = static_cast<si32>(param.at(0).Integer());
+					updater->divider = static_cast<si32>(param.at(1).Integer());
+					updater->multiplier = static_cast<si32>(param.at(2).Integer());
+					updater->max = static_cast<si32>(param.at(3).Integer());
+				}
+				return updater;
+			}
+		}
+		else
+			logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String());
+		break;
+	}
+	return nullptr;
+}
+
+VCMI_LIB_NAMESPACE_BEGIN
+
 std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonVector & ability_vec)
 {
 	auto b = std::make_shared<Bonus>();
@@ -290,36 +385,6 @@ std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonVector & ability_vec)
 	return b;
 }
 
-template <typename T>
-const T parseByMap(const std::map<std::string, T> & map, const JsonNode * val, const std::string & err)
-{
-	if (!val->isNull())
-	{
-		const std::string & type = val->String();
-		auto it = map.find(type);
-		if (it == map.end())
-		{
-			logMod->error("Error: invalid %s%s.", err, type);
-			return {};
-		}
-		else
-		{
-			return it->second;
-		}
-	}
-	else
-		return {};
-}
-
-template <typename T>
-const T parseByMapN(const std::map<std::string, T> & map, const JsonNode * val, const std::string & err)
-{
-	if(val->isNumber())
-		return static_cast<T>(val->Integer());
-	else
-		return parseByMap<T>(map, val, err);
-}
-
 void JsonUtils::resolveAddInfo(CAddInfo & var, const JsonNode & node)
 {
 	const JsonNode & value = node["addInfo"];
@@ -553,7 +618,7 @@ std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonNode &ability)
 	if (!parseBonus(ability, b.get()))
 	{
 		// caller code can not handle this case and presumes that returned bonus is always valid
-		logGlobal->error("Failed to parse bonus! Json config was %S ", ability.toJson());
+		logGlobal->error("Failed to parse bonus! Json config was %S ", ability.toString());
 		b->type = BonusType::NONE;
 		return b;
 	}
@@ -573,75 +638,6 @@ std::shared_ptr<Bonus> JsonUtils::parseBuildingBonus(const JsonNode & ability, c
 	return b;
 }
 
-static BonusParams convertDeprecatedBonus(const JsonNode &ability)
-{
-	if(vstd::contains(deprecatedBonusSet, ability["type"].String()))
-	{
-		logMod->warn("There is deprecated bonus found:\n%s\nTrying to convert...", ability.toJson());
-		auto params = BonusParams(ability["type"].String(),
-											ability["subtype"].isString() ? ability["subtype"].String() : "",
-											   ability["subtype"].isNumber() ? ability["subtype"].Integer() : -1);
-		if(params.isConverted)
-		{
-			if(ability["type"].String() == "SECONDARY_SKILL_PREMY" && bonusValueMap.find(ability["valueType"].String())->second == BonusValueType::PERCENT_TO_BASE) //assume secondary skill special
-			{
-				params.valueType = BonusValueType::PERCENT_TO_TARGET_TYPE;
-				params.targetType = BonusSource::SECONDARY_SKILL;
-			}
-
-			logMod->warn("Please, use this bonus:\n%s\nConverted successfully!", params.toJson().toJson());
-			return params;
-		}
-		else
-			logMod->error("Cannot convert bonus!\n%s", ability.toJson());
-	}
-	BonusParams ret;
-	ret.isConverted = false;
-	return ret;
-}
-
-static TUpdaterPtr parseUpdater(const JsonNode & updaterJson)
-{
-	switch(updaterJson.getType())
-	{
-	case JsonNode::JsonType::DATA_STRING:
-		return parseByMap(bonusUpdaterMap, &updaterJson, "updater type ");
-		break;
-	case JsonNode::JsonType::DATA_STRUCT:
-		if(updaterJson["type"].String() == "GROWS_WITH_LEVEL")
-		{
-			auto updater = std::make_shared<GrowsWithLevelUpdater>();
-			const JsonVector param = updaterJson["parameters"].Vector();
-			updater->valPer20 = static_cast<int>(param[0].Integer());
-			if(param.size() > 1)
-				updater->stepSize = static_cast<int>(param[1].Integer());
-			return updater;
-		}
-		else if (updaterJson["type"].String() == "ARMY_MOVEMENT")
-		{
-			auto updater = std::make_shared<ArmyMovementUpdater>();
-			if(updaterJson["parameters"].isVector())
-			{
-				const auto & param = updaterJson["parameters"].Vector();
-				if(param.size() < 4)
-					logMod->warn("Invalid ARMY_MOVEMENT parameters, using default!");
-				else
-				{
-					updater->base = static_cast<si32>(param.at(0).Integer());
-					updater->divider = static_cast<si32>(param.at(1).Integer());
-					updater->multiplier = static_cast<si32>(param.at(2).Integer());
-					updater->max = static_cast<si32>(param.at(3).Integer());
-				}
-				return updater;
-			}
-		}
-		else
-			logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String());
-		break;
-	}
-	return nullptr;
-}
-
 bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 {
 	const JsonNode * value = nullptr;

+ 12 - 7
lib/json/JsonBonus.h

@@ -14,15 +14,20 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+struct Bonus;
+class ILimiter;
+class CSelector;
+class CAddInfo;
+
 namespace JsonUtils
 {
-	DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonVector & ability_vec);
-	DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonNode & ability);
-	DLL_LINKAGE std::shared_ptr<Bonus> parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description);
-	DLL_LINKAGE bool parseBonus(const JsonNode & ability, Bonus * placement);
-	DLL_LINKAGE std::shared_ptr<ILimiter> parseLimiter(const JsonNode & limiter);
-	DLL_LINKAGE CSelector parseSelector(const JsonNode &ability);
-	DLL_LINKAGE void resolveAddInfo(CAddInfo & var, const JsonNode & node);
+	std::shared_ptr<Bonus> parseBonus(const JsonVector & ability_vec);
+	std::shared_ptr<Bonus> parseBonus(const JsonNode & ability);
+	std::shared_ptr<Bonus> parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description);
+	bool parseBonus(const JsonNode & ability, Bonus * placement);
+	std::shared_ptr<ILimiter> parseLimiter(const JsonNode & limiter);
+	CSelector parseSelector(const JsonNode &ability);
+	void resolveAddInfo(CAddInfo & var, const JsonNode & node);
 }
 
 VCMI_LIB_NAMESPACE_END

+ 20 - 0
lib/json/JsonFormatException.h

@@ -0,0 +1,20 @@
+/*
+ * JsonFormatException.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE JsonFormatException : public std::runtime_error
+{
+public:
+	using runtime_error::runtime_error;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 153 - 82
lib/json/JsonNode.cpp

@@ -11,12 +11,10 @@
 #include "StdInc.h"
 #include "JsonNode.h"
 
-#include "filesystem/Filesystem.h"
 #include "JsonParser.h"
 #include "JsonWriter.h"
+#include "filesystem/Filesystem.h"
 
-namespace
-{
 // to avoid duplicating const and non-const code
 template<typename Node>
 Node & resolvePointer(Node & in, const std::string & pointer)
@@ -40,68 +38,96 @@ Node & resolvePointer(Node & in, const std::string & pointer)
 
 		auto index = boost::lexical_cast<size_t>(entry);
 
-		if (in.Vector().size() > index)
+		if(in.Vector().size() > index)
 			return in.Vector()[index].resolvePointer(remainer);
 	}
 	return in[entry].resolvePointer(remainer);
 }
-}
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-using namespace JsonDetail;
+static const JsonNode nullNode;
 
 class LibClasses;
 class CModHandler;
 
-static const JsonNode nullNode;
+JsonNode::JsonNode(bool boolean)
+	: data(boolean)
+{
+}
+
+JsonNode::JsonNode(int32_t number)
+	: data(static_cast<int64_t>(number))
+{
+}
+
+JsonNode::JsonNode(uint32_t number)
+	: data(static_cast<int64_t>(number))
+{
+}
+
+JsonNode::JsonNode(int64_t number)
+	: data(number)
+{
+}
+
+JsonNode::JsonNode(double number)
+	: data(number)
+{
+}
 
-JsonNode::JsonNode(JsonType Type)
+JsonNode::JsonNode(const std::string & string)
+	: data(string)
 {
-	setType(Type);
 }
 
-JsonNode::JsonNode(const std::byte *data, size_t datasize)
-	:JsonNode(reinterpret_cast<const char*>(data), datasize)
-{}
+JsonNode::JsonNode(const std::byte * data, size_t datasize)
+	: JsonNode(data, datasize, JsonParsingSettings())
+{
+}
 
-JsonNode::JsonNode(const char *data, size_t datasize)
+JsonNode::JsonNode(const std::byte * data, size_t datasize, const JsonParsingSettings & parserSettings)
 {
-	JsonParser parser(data, datasize);
+	JsonParser parser(data, datasize, parserSettings);
 	*this = parser.parse("<unknown>");
 }
 
 JsonNode::JsonNode(const JsonPath & fileURI)
+	:JsonNode(fileURI, JsonParsingSettings())
+{
+}
+
+JsonNode::JsonNode(const JsonPath & fileURI, const JsonParsingSettings & parserSettings)
 {
 	auto file = CResourceHandler::get()->load(fileURI)->readAll();
 
-	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
+	JsonParser parser(reinterpret_cast<std::byte *>(file.first.get()), file.second, parserSettings);
 	*this = parser.parse(fileURI.getName());
 }
 
-JsonNode::JsonNode(const std::string & idx, const JsonPath & fileURI)
+JsonNode::JsonNode(const JsonPath & fileURI, const std::string & idx)
 {
 	auto file = CResourceHandler::get(idx)->load(fileURI)->readAll();
-	
-	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
+
+	JsonParser parser(reinterpret_cast<std::byte *>(file.first.get()), file.second, JsonParsingSettings());
 	*this = parser.parse(fileURI.getName());
 }
 
-JsonNode::JsonNode(const JsonPath & fileURI, bool &isValidSyntax)
+JsonNode::JsonNode(const JsonPath & fileURI, bool & isValidSyntax)
 {
 	auto file = CResourceHandler::get()->load(fileURI)->readAll();
 
-	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
+	JsonParser parser(reinterpret_cast<std::byte *>(file.first.get()), file.second, JsonParsingSettings());
 	*this = parser.parse(fileURI.getName());
 	isValidSyntax = parser.isValid();
 }
 
-bool JsonNode::operator == (const JsonNode &other) const
+bool JsonNode::operator==(const JsonNode & other) const
 {
 	return data == other.data;
 }
 
-bool JsonNode::operator != (const JsonNode &other) const
+bool JsonNode::operator!=(const JsonNode & other) const
 {
 	return !(*this == other);
 }
@@ -111,25 +137,42 @@ JsonNode::JsonType JsonNode::getType() const
 	return static_cast<JsonType>(data.index());
 }
 
-void JsonNode::setMeta(const std::string & metadata, bool recursive)
+const std::string & JsonNode::getModScope() const
+{
+	return modScope;
+}
+
+void JsonNode::setOverrideFlag(bool value)
+{
+	overrideFlag = value;
+}
+
+bool JsonNode::getOverrideFlag() const
+{
+	return overrideFlag;
+}
+
+void JsonNode::setModScope(const std::string & metadata, bool recursive)
 {
-	meta = metadata;
-	if (recursive)
+	modScope = metadata;
+	if(recursive)
 	{
-		switch (getType())
+		switch(getType())
 		{
-			break; case JsonType::DATA_VECTOR:
+			break;
+			case JsonType::DATA_VECTOR:
 			{
 				for(auto & node : Vector())
 				{
-					node.setMeta(metadata);
+					node.setModScope(metadata);
 				}
 			}
-			break; case JsonType::DATA_STRUCT:
+			break;
+			case JsonType::DATA_STRUCT:
 			{
 				for(auto & node : Struct())
 				{
-					node.second.setMeta(metadata);
+					node.second.setModScope(metadata);
 				}
 			}
 		}
@@ -138,7 +181,7 @@ void JsonNode::setMeta(const std::string & metadata, bool recursive)
 
 void JsonNode::setType(JsonType Type)
 {
-	if (getType() == Type)
+	if(getType() == Type)
 		return;
 
 	//float<->int conversion
@@ -158,13 +201,27 @@ void JsonNode::setType(JsonType Type)
 	//Set new node type
 	switch(Type)
 	{
-		break; case JsonType::DATA_NULL:    data = JsonData();
-		break; case JsonType::DATA_BOOL:    data = JsonData(false);
-		break; case JsonType::DATA_FLOAT:   data = JsonData(static_cast<double>(0.0));
-		break; case JsonType::DATA_STRING:  data = JsonData(std::string());
-		break; case JsonType::DATA_VECTOR:  data = JsonData(JsonVector());
-		break; case JsonType::DATA_STRUCT:  data = JsonData(JsonMap());
-		break; case JsonType::DATA_INTEGER: data = JsonData(static_cast<si64>(0));
+		case JsonType::DATA_NULL:
+			data = JsonData();
+			break;
+		case JsonType::DATA_BOOL:
+			data = JsonData(false);
+			break;
+		case JsonType::DATA_FLOAT:
+			data = JsonData(0.0);
+			break;
+		case JsonType::DATA_STRING:
+			data = JsonData(std::string());
+			break;
+		case JsonType::DATA_VECTOR:
+			data = JsonData(JsonVector());
+			break;
+		case JsonType::DATA_STRUCT:
+			data = JsonData(JsonMap());
+			break;
+		case JsonType::DATA_INTEGER:
+			data = JsonData(static_cast<si64>(0));
+			break;
 	}
 }
 
@@ -197,18 +254,18 @@ bool JsonNode::containsBaseData() const
 {
 	switch(getType())
 	{
-	case JsonType::DATA_NULL:
-		return false;
-	case JsonType::DATA_STRUCT:
-		for(const auto & elem : Struct())
-		{
-			if(elem.second.containsBaseData())
-				return true;
-		}
-		return false;
-	default:
-		//other types (including vector) cannot be extended via merge
-		return true;
+		case JsonType::DATA_NULL:
+			return false;
+		case JsonType::DATA_STRUCT:
+			for(const auto & elem : Struct())
+			{
+				if(elem.second.containsBaseData())
+					return true;
+			}
+			return false;
+		default:
+			//other types (including vector) cannot be extended via merge
+			return true;
 	}
 }
 
@@ -216,14 +273,14 @@ bool JsonNode::isCompact() const
 {
 	switch(getType())
 	{
-	case JsonType::DATA_VECTOR:
-		for(const JsonNode & elem : Vector())
-		{
-			if(!elem.isCompact())
-				return false;
-		}
-		return true;
-	case JsonType::DATA_STRUCT:
+		case JsonType::DATA_VECTOR:
+			for(const JsonNode & elem : Vector())
+			{
+				if(!elem.isCompact())
+					return false;
+			}
+			return true;
+		case JsonType::DATA_STRUCT:
 		{
 			auto propertyCount = Struct().size();
 			if(propertyCount == 0)
@@ -231,9 +288,9 @@ bool JsonNode::isCompact() const
 			else if(propertyCount == 1)
 				return Struct().begin()->second.isCompact();
 		}
-		return false;
-	default:
-		return true;
+			return false;
+		default:
+			return true;
 	}
 }
 
@@ -253,7 +310,7 @@ bool JsonNode::TryBoolFromString(bool & success) const
 
 		if(success)
 			return true;
-		
+
 		success = boolParamStr == "false";
 	}
 	return false;
@@ -300,20 +357,22 @@ JsonMap & JsonNode::Struct()
 	return std::get<JsonMap>(data);
 }
 
-const bool boolDefault = false;
 bool JsonNode::Bool() const
 {
+	static const bool boolDefault = false;
+
 	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_BOOL);
 
-	if (getType() == JsonType::DATA_BOOL)
+	if(getType() == JsonType::DATA_BOOL)
 		return std::get<bool>(data);
 
 	return boolDefault;
 }
 
-const double floatDefault = 0;
 double JsonNode::Float() const
 {
+	static const double floatDefault = 0;
+
 	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT);
 
 	if(getType() == JsonType::DATA_FLOAT)
@@ -325,9 +384,10 @@ double JsonNode::Float() const
 	return floatDefault;
 }
 
-const si64 integerDefault = 0;
 si64 JsonNode::Integer() const
 {
+	static const si64 integerDefault = 0;
+
 	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT);
 
 	if(getType() == JsonType::DATA_INTEGER)
@@ -339,34 +399,37 @@ si64 JsonNode::Integer() const
 	return integerDefault;
 }
 
-const std::string stringDefault = std::string();
 const std::string & JsonNode::String() const
 {
+	static const std::string stringDefault;
+
 	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRING);
 
-	if (getType() == JsonType::DATA_STRING)
+	if(getType() == JsonType::DATA_STRING)
 		return std::get<std::string>(data);
 
 	return stringDefault;
 }
 
-const JsonVector vectorDefault = JsonVector();
 const JsonVector & JsonNode::Vector() const
 {
+	static const JsonVector vectorDefault;
+
 	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_VECTOR);
 
-	if (getType() == JsonType::DATA_VECTOR)
+	if(getType() == JsonType::DATA_VECTOR)
 		return std::get<JsonVector>(data);
 
 	return vectorDefault;
 }
 
-const JsonMap mapDefault = JsonMap();
 const JsonMap & JsonNode::Struct() const
 {
+	static const JsonMap mapDefault;
+
 	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRUCT);
 
-	if (getType() == JsonType::DATA_STRUCT)
+	if(getType() == JsonType::DATA_STRUCT)
 		return std::get<JsonMap>(data);
 
 	return mapDefault;
@@ -380,49 +443,57 @@ JsonNode & JsonNode::operator[](const std::string & child)
 const JsonNode & JsonNode::operator[](const std::string & child) const
 {
 	auto it = Struct().find(child);
-	if (it != Struct().end())
+	if(it != Struct().end())
 		return it->second;
 	return nullNode;
 }
 
 JsonNode & JsonNode::operator[](size_t child)
 {
-	if (child >= Vector().size() )
+	if(child >= Vector().size())
 		Vector().resize(child + 1);
 	return Vector()[child];
 }
 
 const JsonNode & JsonNode::operator[](size_t child) const
 {
-	if (child < Vector().size() )
+	if(child < Vector().size())
 		return Vector()[child];
 
 	return nullNode;
 }
 
-const JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer) const
+const JsonNode & JsonNode::resolvePointer(const std::string & jsonPointer) const
 {
 	return ::resolvePointer(*this, jsonPointer);
 }
 
-JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer)
+JsonNode & JsonNode::resolvePointer(const std::string & jsonPointer)
 {
 	return ::resolvePointer(*this, jsonPointer);
 }
 
-std::vector<std::byte> JsonNode::toBytes(bool compact) const
+std::vector<std::byte> JsonNode::toBytes() const
 {
-	std::string jsonString = toJson(compact);
-	auto dataBegin = reinterpret_cast<const std::byte*>(jsonString.data());
+	std::string jsonString = toString();
+	auto dataBegin = reinterpret_cast<const std::byte *>(jsonString.data());
 	auto dataEnd = dataBegin + jsonString.size();
 	std::vector<std::byte> result(dataBegin, dataEnd);
 	return result;
 }
 
-std::string JsonNode::toJson(bool compact) const
+std::string JsonNode::toCompactString() const
+{
+	std::ostringstream out;
+	JsonWriter writer(out, true);
+	writer.writeNode(*this);
+	return out.str();
+}
+
+std::string JsonNode::toString() const
 {
 	std::ostringstream out;
-	JsonWriter writer(out, compact);
+	JsonWriter writer(out, false);
 	writer.writeNode(*this);
 	return out.str();
 }

+ 107 - 110
lib/json/JsonNode.h

@@ -17,10 +17,23 @@ class JsonNode;
 using JsonMap = std::map<std::string, JsonNode>;
 using JsonVector = std::vector<JsonNode>;
 
-struct Bonus;
-class CSelector;
-class CAddInfo;
-class ILimiter;
+struct DLL_LINKAGE JsonParsingSettings
+{
+	enum class JsonFormatMode
+	{
+		JSON, // strict implementation of json format
+		JSONC, // json format that also allows comments that start from '//'
+		JSON5 // Partial support of 'json5' format
+	};
+
+	JsonFormatMode mode = JsonFormatMode::JSON5;
+
+	/// Maximum depth of elements
+	uint32_t maxDepth = 30;
+
+	/// If set to true, parser will throw on any encountered error
+	bool strict = false;
+};
 
 class DLL_LINKAGE JsonNode
 {
@@ -37,30 +50,44 @@ public:
 	};
 
 private:
-	using JsonData = std::variant<std::monostate, bool, double, std::string, JsonVector, JsonMap, si64>;
+	using JsonData = std::variant<std::monostate, bool, double, std::string, JsonVector, JsonMap, int64_t>;
 
 	JsonData data;
 
+	/// Mod-origin of this particular field
+	std::string modScope;
+
+	bool overrideFlag = false;
+
 public:
-	/// free to use metadata fields
-	std::string meta;
-	// meta-flags like override
-	std::vector<std::string> flags;
-
-	//Create empty node
-	JsonNode(JsonType Type = JsonType::DATA_NULL);
-	//Create tree from Json-formatted input
-	explicit JsonNode(const char * data, size_t datasize);
+	JsonNode() = default;
+
+	/// Create single node with specified value
+	explicit JsonNode(bool boolean);
+	explicit JsonNode(int32_t number);
+	explicit JsonNode(uint32_t number);
+	explicit JsonNode(int64_t number);
+	explicit JsonNode(double number);
+	explicit JsonNode(const std::string & string);
+
+	/// Create tree from Json-formatted input
 	explicit JsonNode(const std::byte * data, size_t datasize);
-	//Create tree from JSON file
+	explicit JsonNode(const std::byte * data, size_t datasize, const JsonParsingSettings & parserSettings);
+
+	/// Create tree from JSON file
 	explicit JsonNode(const JsonPath & fileURI);
-	explicit JsonNode(const std::string & modName, const JsonPath & fileURI);
+	explicit JsonNode(const JsonPath & fileURI, const JsonParsingSettings & parserSettings);
+	explicit JsonNode(const JsonPath & fileURI, const std::string & modName);
 	explicit JsonNode(const JsonPath & fileURI, bool & isValidSyntax);
 
-	bool operator == (const JsonNode &other) const;
-	bool operator != (const JsonNode &other) const;
+	bool operator==(const JsonNode & other) const;
+	bool operator!=(const JsonNode & other) const;
+
+	const std::string & getModScope() const;
+	void setModScope(const std::string & metadata, bool recursive = true);
 
-	void setMeta(const std::string & metadata, bool recursive = true);
+	void setOverrideFlag(bool value);
+	bool getOverrideFlag() const;
 
 	/// Convert node to another type. Converting to nullptr will clear all data
 	void setType(JsonType Type);
@@ -114,121 +141,91 @@ public:
 	const JsonNode & operator[](const std::string & child) const;
 
 	JsonNode & operator[](size_t child);
-	const JsonNode & operator[](size_t  child) const;
+	const JsonNode & operator[](size_t child) const;
 
-	std::string toJson(bool compact = false) const;
-	std::vector<std::byte> toBytes(bool compact = false) const;
+	std::string toCompactString() const;
+	std::string toString() const;
+	std::vector<std::byte> toBytes() const;
 
-	template <typename Handler> void serialize(Handler &h)
+	template<typename Handler>
+	void serialize(Handler & h)
 	{
-		h & meta;
-		h & flags;
+		h & modScope;
+
+		if(h.version >= Handler::Version::JSON_FLAGS)
+		{
+			h & overrideFlag;
+		}
+		else
+		{
+			std::vector<std::string> oldFlags;
+			h & oldFlags;
+		}
 		h & data;
 	}
 };
 
 namespace JsonDetail
 {
-	// conversion helpers for JsonNode::convertTo (partial template function instantiation is illegal in c++)
-
-	template <typename T, int arithm>
-	struct JsonConvImpl;
 
-	template <typename T>
-	struct JsonConvImpl<T, 1>
-	{
-		static T convertImpl(const JsonNode & node)
-		{
-			return T((int)node.Float());
-		}
-	};
-
-	template <typename T>
-	struct JsonConvImpl<T, 0>
-	{
-		static T convertImpl(const JsonNode & node)
-		{
-			return T(node.Float());
-		}
-	};
+inline void convert(bool & value, const JsonNode & node)
+{
+	value = node.Bool();
+}
 
-	template<typename Type>
-	struct JsonConverter
-	{
-		static Type convert(const JsonNode & node)
-		{
-			///this should be triggered only for numeric types and enums
-			static_assert(std::is_arithmetic_v<Type> || std::is_enum_v<Type> || std::is_class_v<Type>, "Unsupported type for JsonNode::convertTo()!");
-			return JsonConvImpl<Type, std::is_enum_v<Type> || std::is_class_v<Type> >::convertImpl(node);
+template<typename T>
+auto convert(T & value, const JsonNode & node) -> std::enable_if_t<std::is_integral_v<T>>
+{
+	value = node.Integer();
+}
 
-		}
-	};
+template<typename T>
+auto convert(T & value, const JsonNode & node) -> std::enable_if_t<std::is_floating_point_v<T>>
+{
+	value = node.Float();
+}
 
-	template<typename Type>
-	struct JsonConverter<std::map<std::string, Type> >
-	{
-		static std::map<std::string, Type> convert(const JsonNode & node)
-		{
-			std::map<std::string, Type> ret;
-			for (const JsonMap::value_type & entry : node.Struct())
-			{
-				ret.insert(entry.first, entry.second.convertTo<Type>());
-			}
-			return ret;
-		}
-	};
+inline void convert(std::string & value, const JsonNode & node)
+{
+	value = node.String();
+}
 
-	template<typename Type>
-	struct JsonConverter<std::set<Type> >
-	{
-		static std::set<Type> convert(const JsonNode & node)
-		{
-			std::set<Type> ret;
-			for(const JsonVector::value_type & entry : node.Vector())
-			{
-				ret.insert(entry.convertTo<Type>());
-			}
-			return ret;
-		}
-	};
+template<typename Type>
+void convert(std::map<std::string, Type> & value, const JsonNode & node)
+{
+	value.clear();
+	for(const JsonMap::value_type & entry : node.Struct())
+		value.insert(entry.first, entry.second.convertTo<Type>());
+}
 
-	template<typename Type>
-	struct JsonConverter<std::vector<Type> >
+template<typename Type>
+void convert(std::set<Type> & value, const JsonNode & node)
+{
+	value.clear();
+	for(const JsonVector::value_type & entry : node.Vector())
 	{
-		static std::vector<Type> convert(const JsonNode & node)
-		{
-			std::vector<Type> ret;
-			for (const JsonVector::value_type & entry: node.Vector())
-			{
-				ret.push_back(entry.convertTo<Type>());
-			}
-			return ret;
-		}
-	};
+		value.insert(entry.convertTo<Type>());
+	}
+}
 
-	template<>
-	struct JsonConverter<std::string>
+template<typename Type>
+void convert(std::vector<Type> & value, const JsonNode & node)
+{
+	value.clear();
+	for(const JsonVector::value_type & entry : node.Vector())
 	{
-		static std::string convert(const JsonNode & node)
-		{
-			return node.String();
-		}
-	};
+		value.push_back(entry.convertTo<Type>());
+	}
+}
 
-	template<>
-	struct JsonConverter<bool>
-	{
-		static bool convert(const JsonNode & node)
-		{
-			return node.Bool();
-		}
-	};
 }
 
 template<typename Type>
 Type JsonNode::convertTo() const
 {
-	return JsonDetail::JsonConverter<Type>::convert(*this);
+	Type result;
+	JsonDetail::convert(result, *this);
+	return result;
 }
 
 VCMI_LIB_NAMESPACE_END

+ 258 - 127
lib/json/JsonParser.cpp

@@ -11,15 +11,19 @@
 #include "StdInc.h"
 #include "JsonParser.h"
 
+#include "../ScopeGuard.h"
 #include "../TextOperations.h"
+#include "JsonFormatException.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-JsonParser::JsonParser(const char * inputString, size_t stringSize):
-	input(inputString, stringSize),
-	lineCount(1),
-	lineStart(0),
-	pos(0)
+JsonParser::JsonParser(const std::byte * inputString, size_t stringSize, const JsonParsingSettings & settings)
+	: settings(settings)
+	, input(reinterpret_cast<const char *>(inputString), stringSize)
+	, lineCount(1)
+	, currentDepth(0)
+	, lineStart(0)
+	, pos(0)
 {
 }
 
@@ -27,24 +31,29 @@ JsonNode JsonParser::parse(const std::string & fileName)
 {
 	JsonNode root;
 
-	if (input.size() == 0)
+	if(input.empty())
 	{
 		error("File is empty", false);
 	}
 	else
 	{
-		if (!TextOperations::isValidUnicodeString(&input[0], input.size()))
+		if(!TextOperations::isValidUnicodeString(input.data(), input.size()))
 			error("Not a valid UTF-8 file", false);
 
+		// If file starts with BOM - skip it
+		uint32_t firstCharacter = TextOperations::getUnicodeCodepoint(input.data(), input.size());
+		if (firstCharacter == 0xFEFF)
+			pos += TextOperations::getUnicodeCharacterSize(input[0]);
+
 		extractValue(root);
 		extractWhitespace(false);
 
 		//Warn if there are any non-whitespace symbols left
-		if (pos < input.size())
+		if(pos < input.size())
 			error("Not all file was parsed!", true);
 	}
 
-	if (!errors.empty())
+	if(!errors.empty())
 	{
 		logMod->warn("File %s is not a valid JSON file!", fileName);
 		logMod->warn(errors);
@@ -59,33 +68,43 @@ bool JsonParser::isValid()
 
 bool JsonParser::extractSeparator()
 {
-	if (!extractWhitespace())
+	if(!extractWhitespace())
 		return false;
 
-	if ( input[pos] !=':')
+	if(input[pos] != ':')
 		return error("Separator expected");
 
 	pos++;
 	return true;
 }
 
-bool JsonParser::extractValue(JsonNode &node)
+bool JsonParser::extractValue(JsonNode & node)
 {
-	if (!extractWhitespace())
+	if(!extractWhitespace())
 		return false;
 
-	switch (input[pos])
+	switch(input[pos])
 	{
-		case '\"': return extractString(node);
-		case 'n' : return extractNull(node);
-		case 't' : return extractTrue(node);
-		case 'f' : return extractFalse(node);
-		case '{' : return extractStruct(node);
-		case '[' : return extractArray(node);
-		case '-' : return extractFloat(node);
+		case '\"':
+		case '\'':
+			return extractString(node);
+		case 'n':
+			return extractNull(node);
+		case 't':
+			return extractTrue(node);
+		case 'f':
+			return extractFalse(node);
+		case '{':
+			return extractStruct(node);
+		case '[':
+			return extractArray(node);
+		case '-':
+		case '+':
+		case '.':
+			return extractFloat(node);
 		default:
 		{
-			if (input[pos] >= '0' && input[pos] <= '9')
+			if(input[pos] >= '0' && input[pos] <= '9')
 				return extractFloat(node);
 			return error("Value expected!");
 		}
@@ -94,88 +113,127 @@ bool JsonParser::extractValue(JsonNode &node)
 
 bool JsonParser::extractWhitespace(bool verbose)
 {
-	while (true)
+	//TODO: JSON5 - C-style multi-line comments
+	//TODO: JSON5 - Additional white space characters are allowed
+
+	while(true)
 	{
 		while(pos < input.size() && static_cast<ui8>(input[pos]) <= ' ')
 		{
-			if (input[pos] == '\n')
+			if(input[pos] == '\n')
 			{
 				lineCount++;
-				lineStart = pos+1;
+				lineStart = pos + 1;
 			}
 			pos++;
 		}
-		if (pos >= input.size() || input[pos] != '/')
+
+		if(pos >= input.size() || input[pos] != '/')
 			break;
 
+		if(settings.mode == JsonParsingSettings::JsonFormatMode::JSON)
+			error("Comments are not permitted in json!", true);
+
 		pos++;
-		if (pos == input.size())
+		if(pos == input.size())
 			break;
-		if (input[pos] == '/')
+		if(input[pos] == '/')
 			pos++;
 		else
 			error("Comments must consist of two slashes!", true);
 
-		while (pos < input.size() && input[pos] != '\n')
+		while(pos < input.size() && input[pos] != '\n')
 			pos++;
 	}
 
-	if (pos >= input.size() && verbose)
+	if(pos >= input.size() && verbose)
 		return error("Unexpected end of file!");
 	return true;
 }
 
-bool JsonParser::extractEscaping(std::string &str)
+bool JsonParser::extractEscaping(std::string & str)
 {
+	// TODO: support unicode escaping:
+	// \u1234
+
 	switch(input[pos])
 	{
-		break; case '\"': str += '\"';
-		break; case '\\': str += '\\';
-		break; case 'b': str += '\b';
-		break; case 'f': str += '\f';
-		break; case 'n': str += '\n';
-		break; case 'r': str += '\r';
-		break; case 't': str += '\t';
-		break; case '/': str += '/';
-		break; default: return error("Unknown escape sequence!", true);
+		case '\"':
+			str += '\"';
+			break;
+		case '\\':
+			str += '\\';
+			break;
+		case 'b':
+			str += '\b';
+			break;
+		case 'f':
+			str += '\f';
+			break;
+		case 'n':
+			str += '\n';
+			break;
+		case 'r':
+			str += '\r';
+			break;
+		case 't':
+			str += '\t';
+			break;
+		case '/':
+			str += '/';
+			break;
+		default:
+			return error("Unknown escape sequence!", true);
 	}
 	return true;
 }
 
-bool JsonParser::extractString(std::string &str)
+bool JsonParser::extractString(std::string & str)
 {
-	if (input[pos] != '\"')
-		return error("String expected!");
+	//TODO: JSON5 - line breaks escaping
+
+	if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
+	{
+		if(input[pos] != '\"')
+			return error("String expected!");
+	}
+	else
+	{
+		if(input[pos] != '\"' && input[pos] != '\'')
+			return error("String expected!");
+	}
+
+	char lineTerminator = input[pos];
 	pos++;
 
 	size_t first = pos;
 
-	while (pos != input.size())
+	while(pos != input.size())
 	{
-		if (input[pos] == '\"') // Correct end of string
+		if(input[pos] == lineTerminator) // Correct end of string
 		{
-			str.append( &input[first], pos-first);
+			str.append(&input[first], pos - first);
 			pos++;
 			return true;
 		}
-		if (input[pos] == '\\') // Escaping
+		if(input[pos] == '\\') // Escaping
 		{
-			str.append( &input[first], pos-first);
+			str.append(&input[first], pos - first);
 			pos++;
-			if (pos == input.size())
+			if(pos == input.size())
 				break;
 			extractEscaping(str);
 			first = pos + 1;
 		}
-		if (input[pos] == '\n') // end-of-line
+		if(input[pos] == '\n') // end-of-line
 		{
-			str.append( &input[first], pos-first);
+			str.append(&input[first], pos - first);
 			return error("Closing quote not found!", true);
 		}
 		if(static_cast<unsigned char>(input[pos]) < ' ') // control character
 		{
-			str.append( &input[first], pos-first);
-			first = pos+1;
+			str.append(&input[first], pos - first);
+			first = pos + 1;
 			error("Illegal character in the string!", true);
 		}
 		pos++;
@@ -183,10 +241,10 @@ bool JsonParser::extractString(std::string &str)
 	return error("Unterminated string!");
 }
 
-bool JsonParser::extractString(JsonNode &node)
+bool JsonParser::extractString(JsonNode & node)
 {
 	std::string str;
-	if (!extractString(str))
+	if(!extractString(str))
 		return false;
 
 	node.setType(JsonNode::JsonType::DATA_STRING);
@@ -194,97 +252,146 @@ bool JsonParser::extractString(JsonNode &node)
 	return true;
 }
 
-bool JsonParser::extractLiteral(const std::string &literal)
+bool JsonParser::extractLiteral(std::string & literal)
 {
-	if (literal.compare(0, literal.size(), &input[pos], literal.size()) != 0)
+	while(pos < input.size())
 	{
-		while (pos < input.size() && ((input[pos]>'a' && input[pos]<'z')
-								   || (input[pos]>'A' && input[pos]<'Z')))
-			pos++;
-		return error("Unknown literal found", true);
+		bool isUpperCase = input[pos] >= 'A' && input[pos] <= 'Z';
+		bool isLowerCase = input[pos] >= 'a' && input[pos] <= 'z';
+		bool isNumber = input[pos] >= '0' && input[pos] <= '9';
+
+		if(!isUpperCase && !isLowerCase && !isNumber)
+			break;
+
+		literal += input[pos];
+		pos++;
+	}
+
+	return true;
+}
+
+bool JsonParser::extractAndCompareLiteral(const std::string & expectedLiteral)
+{
+	std::string literal;
+	if(!extractLiteral(literal))
+		return false;
+
+	if(literal != expectedLiteral)
+	{
+		return error("Expected " + expectedLiteral + ", but unknown literal found", true);
+		return false;
 	}
 
-	pos += literal.size();
 	return true;
 }
 
-bool JsonParser::extractNull(JsonNode &node)
+bool JsonParser::extractNull(JsonNode & node)
 {
-	if (!extractLiteral("null"))
+	if(!extractAndCompareLiteral("null"))
 		return false;
 
 	node.clear();
 	return true;
 }
 
-bool JsonParser::extractTrue(JsonNode &node)
+bool JsonParser::extractTrue(JsonNode & node)
 {
-	if (!extractLiteral("true"))
+	if(!extractAndCompareLiteral("true"))
 		return false;
 
 	node.Bool() = true;
 	return true;
 }
 
-bool JsonParser::extractFalse(JsonNode &node)
+bool JsonParser::extractFalse(JsonNode & node)
 {
-	if (!extractLiteral("false"))
+	if(!extractAndCompareLiteral("false"))
 		return false;
 
 	node.Bool() = false;
 	return true;
 }
 
-bool JsonParser::extractStruct(JsonNode &node)
+bool JsonParser::extractStruct(JsonNode & node)
 {
 	node.setType(JsonNode::JsonType::DATA_STRUCT);
+
+	if(currentDepth > settings.maxDepth)
+		error("Maximum allowed depth of json structure has been reached", true);
+
 	pos++;
+	currentDepth++;
+	auto guard = vstd::makeScopeGuard([this]()
+	{
+		currentDepth--;
+	});
 
-	if (!extractWhitespace())
+	if(!extractWhitespace())
 		return false;
 
 	//Empty struct found
-	if (input[pos] == '}')
+	if(input[pos] == '}')
 	{
 		pos++;
 		return true;
 	}
 
-	while (true)
+	while(true)
 	{
-		if (!extractWhitespace())
+		if(!extractWhitespace())
 			return false;
 
+		bool overrideFlag = false;
 		std::string key;
-		if (!extractString(key))
-			return false;
 
-		// split key string into actual key and meta-flags
-		std::vector<std::string> keyAndFlags;
-		boost::split(keyAndFlags, key, boost::is_any_of("#"));
-		key = keyAndFlags[0];
-		// check for unknown flags - helps with debugging
-		std::vector<std::string> knownFlags = { "override" };
-		for(int i = 1; i < keyAndFlags.size(); i++)
+		if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
+		{
+			if(!extractString(key))
+				return false;
+		}
+		else
+		{
+			if(input[pos] == '\'' || input[pos] == '\"')
+			{
+				if(!extractString(key))
+					return false;
+			}
+			else
+			{
+				if(!extractLiteral(key))
+					return false;
+			}
+		}
+
+		if(key.find('#') != std::string::npos)
 		{
-			if(!vstd::contains(knownFlags, keyAndFlags[i]))
-				error("Encountered unknown flag #" + keyAndFlags[i], true);
+			// split key string into actual key and meta-flags
+			std::vector<std::string> keyAndFlags;
+			boost::split(keyAndFlags, key, boost::is_any_of("#"));
+
+			key = keyAndFlags[0];
+
+			for(int i = 1; i < keyAndFlags.size(); i++)
+			{
+				if(keyAndFlags[i] == "override")
+					overrideFlag = true;
+				else
+					error("Encountered unknown flag #" + keyAndFlags[i], true);
+			}
 		}
 
-		if (node.Struct().find(key) != node.Struct().end())
+		if(node.Struct().find(key) != node.Struct().end())
 			error("Duplicate element encountered!", true);
 
-		if (!extractSeparator())
+		if(!extractSeparator())
 			return false;
 
-		if (!extractElement(node.Struct()[key], '}'))
+		if(!extractElement(node.Struct()[key], '}'))
 			return false;
 
-		// flags from key string belong to referenced element
-		for(int i = 1; i < keyAndFlags.size(); i++)
-			node.Struct()[key].flags.push_back(keyAndFlags[i]);
+		node.Struct()[key].setOverrideFlag(overrideFlag);
 
-		if (input[pos] == '}')
+		if(input[pos] == '}')
 		{
 			pos++;
 			return true;
@@ -292,31 +399,40 @@ bool JsonParser::extractStruct(JsonNode &node)
 	}
 }
 
-bool JsonParser::extractArray(JsonNode &node)
+bool JsonParser::extractArray(JsonNode & node)
 {
+	if(currentDepth > settings.maxDepth)
+		error("Macimum allowed depth of json structure has been reached", true);
+
+	currentDepth++;
+	auto guard = vstd::makeScopeGuard([this]()
+	{
+		currentDepth--;
+	});
+
 	pos++;
 	node.setType(JsonNode::JsonType::DATA_VECTOR);
 
-	if (!extractWhitespace())
+	if(!extractWhitespace())
 		return false;
 
 	//Empty array found
-	if (input[pos] == ']')
+	if(input[pos] == ']')
 	{
 		pos++;
 		return true;
 	}
 
-	while (true)
+	while(true)
 	{
 		//NOTE: currently 50% of time is this vector resizing.
 		//May be useful to use list during parsing and then swap() all items to vector
-		node.Vector().resize(node.Vector().size()+1);
+		node.Vector().resize(node.Vector().size() + 1);
 
-		if (!extractElement(node.Vector().back(), ']'))
+		if(!extractElement(node.Vector().back(), ']'))
 			return false;
 
-		if (input[pos] == ']')
+		if(input[pos] == ']')
 		{
 			pos++;
 			return true;
@@ -324,74 +440,87 @@ bool JsonParser::extractArray(JsonNode &node)
 	}
 }
 
-bool JsonParser::extractElement(JsonNode &node, char terminator)
+bool JsonParser::extractElement(JsonNode & node, char terminator)
 {
-	if (!extractValue(node))
+	if(!extractValue(node))
 		return false;
 
-	if (!extractWhitespace())
+	if(!extractWhitespace())
 		return false;
 
 	bool comma = (input[pos] == ',');
-	if (comma )
+	if(comma)
 	{
 		pos++;
-		if (!extractWhitespace())
+		if(!extractWhitespace())
 			return false;
 	}
 
-	if (input[pos] == terminator)
+	if(input[pos] == terminator)
 	{
-		//FIXME: MOD COMPATIBILITY: Too many of these right now, re-enable later
-		//if (comma)
-			//error("Extra comma found!", true);
+		if(comma && settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
+			error("Extra comma found!", true);
+
 		return true;
 	}
 
-	if (!comma)
+	if(!comma)
 		error("Comma expected!", true);
 
 	return true;
 }
 
-bool JsonParser::extractFloat(JsonNode &node)
+bool JsonParser::extractFloat(JsonNode & node)
 {
+	//TODO: JSON5 - hexacedimal support
+	//TODO: JSON5 - Numbers may be IEEE 754 positive infinity, negative infinity, and NaN (why?)
+
 	assert(input[pos] == '-' || (input[pos] >= '0' && input[pos] <= '9'));
-	bool negative=false;
-	double result=0;
+	bool negative = false;
+	double result = 0;
 	si64 integerPart = 0;
 	bool isFloat = false;
 
-	if (input[pos] == '-')
+	if(input[pos] == '+')
+	{
+		if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
+			error("Positive numbers should not have plus sign!", true);
+		pos++;
+	}
+	else if(input[pos] == '-')
 	{
 		pos++;
 		negative = true;
 	}
 
-	if (input[pos] < '0' || input[pos] > '9')
-		return error("Number expected!");
+	if(input[pos] < '0' || input[pos] > '9')
+	{
+		if(input[pos] != '.' && settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
+			return error("Number expected!");
+	}
 
 	//Extract integer part
-	while (input[pos] >= '0' && input[pos] <= '9')
+	while(input[pos] >= '0' && input[pos] <= '9')
 	{
-		integerPart = integerPart*10+(input[pos]-'0');
+		integerPart = integerPart * 10 + (input[pos] - '0');
 		pos++;
 	}
 
 	result = static_cast<double>(integerPart);
 
-	if (input[pos] == '.')
+	if(input[pos] == '.')
 	{
 		//extract fractional part
 		isFloat = true;
 		pos++;
 		double fractMult = 0.1;
-		if (input[pos] < '0' || input[pos] > '9')
+
+		if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5 && (input[pos] < '0' || input[pos] > '9'))
 			return error("Decimal part expected!");
 
-		while (input[pos] >= '0' && input[pos] <= '9')
+		while(input[pos] >= '0' && input[pos] <= '9')
 		{
-			result = result + fractMult*(input[pos]-'0');
+			result = result + fractMult * (input[pos] - '0');
 			fractMult /= 10;
 			pos++;
 		}
@@ -415,12 +544,12 @@ bool JsonParser::extractFloat(JsonNode &node)
 			pos++;
 		}
 
-		if (input[pos] < '0' || input[pos] > '9')
+		if(input[pos] < '0' || input[pos] > '9')
 			return error("Exponential part expected!");
 
-		while (input[pos] >= '0' && input[pos] <= '9')
+		while(input[pos] >= '0' && input[pos] <= '9')
 		{
-			power = power*10 + (input[pos]-'0');
+			power = power * 10 + (input[pos] - '0');
 			pos++;
 		}
 
@@ -450,13 +579,15 @@ bool JsonParser::extractFloat(JsonNode &node)
 	return true;
 }
 
-bool JsonParser::error(const std::string &message, bool warning)
+bool JsonParser::error(const std::string & message, bool warning)
 {
+	if(settings.strict)
+		throw JsonFormatException(message);
+
 	std::ostringstream stream;
-	std::string type(warning?" warning: ":" error: ");
+	std::string type(warning ? " warning: " : " error: ");
 
-	stream << "At line " << lineCount << ", position "<<pos-lineStart
-		   << type << message <<"\n";
+	stream << "At line " << lineCount << ", position " << pos - lineStart << type << message << "\n";
 	errors += stream.str();
 
 	return warning;

+ 23 - 45
lib/json/JsonParser.h

@@ -13,64 +13,42 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-//Tiny string class that uses const char* as data for speed, members are private
-//for ease of debugging and some compatibility with std::string
-class constString
-{
-	const char *data;
-	const size_t datasize;
-
-public:
-	constString(const char * inputString, size_t stringSize):
-		data(inputString),
-		datasize(stringSize)
-	{
-	}
-
-	inline size_t size() const
-	{
-		return datasize;
-	};
-
-	inline const char& operator[] (size_t position)
-	{
-		assert (position < datasize);
-		return data[position];
-	}
-};
-
 //Internal class for string -> JsonNode conversion
 class JsonParser
 {
-	std::string errors;     // Contains description of all encountered errors
-	constString input;      // Input data
-	ui32 lineCount; // Currently parsed line, starting from 1
-	size_t lineStart;       // Position of current line start
-	size_t pos;             // Current position of parser
+	const JsonParsingSettings settings;
+
+	std::string errors; // Contains description of all encountered errors
+	std::string_view input; // Input data
+	uint32_t lineCount; // Currently parsed line, starting from 1
+	uint32_t currentDepth;
+	size_t lineStart; // Position of current line start
+	size_t pos; // Current position of parser
 
 	//Helpers
-	bool extractEscaping(std::string &str);
-	bool extractLiteral(const std::string &literal);
-	bool extractString(std::string &string);
+	bool extractEscaping(std::string & str);
+	bool extractLiteral(std::string & literal);
+	bool extractAndCompareLiteral(const std::string & expectedLiteral);
+	bool extractString(std::string & string);
 	bool extractWhitespace(bool verbose = true);
 	bool extractSeparator();
-	bool extractElement(JsonNode &node, char terminator);
+	bool extractElement(JsonNode & node, char terminator);
 
 	//Methods for extracting JSON data
-	bool extractArray(JsonNode &node);
-	bool extractFalse(JsonNode &node);
-	bool extractFloat(JsonNode &node);
-	bool extractNull(JsonNode &node);
-	bool extractString(JsonNode &node);
-	bool extractStruct(JsonNode &node);
-	bool extractTrue(JsonNode &node);
-	bool extractValue(JsonNode &node);
+	bool extractArray(JsonNode & node);
+	bool extractFalse(JsonNode & node);
+	bool extractFloat(JsonNode & node);
+	bool extractNull(JsonNode & node);
+	bool extractString(JsonNode & node);
+	bool extractStruct(JsonNode & node);
+	bool extractTrue(JsonNode & node);
+	bool extractValue(JsonNode & node);
 
 	//Add error\warning message to list
-	bool error(const std::string &message, bool warning=false);
+	bool error(const std::string & message, bool warning = false);
 
 public:
-	JsonParser(const char * inputString, size_t stringSize);
+	JsonParser(const std::byte * inputString, size_t stringSize, const JsonParsingSettings & settings);
 
 	/// do actual parsing. filename is name of file that will printed to console if any errors were found
 	JsonNode parse(const std::string & fileName);

+ 4 - 4
lib/json/JsonRandom.cpp

@@ -235,9 +235,9 @@ VCMI_LIB_NAMESPACE_BEGIN
 					filteredAnyOf.insert(subset.begin(), subset.end());
 				}
 
-				vstd::erase_if(filteredTypes, [&](const IdentifierType & value)
+				vstd::erase_if(filteredTypes, [&filteredAnyOf](const IdentifierType & filteredValue)
 				{
-					return filteredAnyOf.count(value) == 0;
+					return filteredAnyOf.count(filteredValue) == 0;
 				});
 			}
 
@@ -321,7 +321,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 		{
 			for(const auto & pair : value.Struct())
 			{
-				PrimarySkill id = decodeKey<PrimarySkill>(pair.second.meta, pair.first, variables);
+				PrimarySkill id = decodeKey<PrimarySkill>(pair.second.getModScope(), pair.first, variables);
 				ret[id.getNum()] += loadValue(pair.second, rng, variables);
 			}
 		}
@@ -357,7 +357,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 		{
 			for(const auto & pair : value.Struct())
 			{
-				SecondarySkill id = decodeKey<SecondarySkill>(pair.second.meta, pair.first, variables);
+				SecondarySkill id = decodeKey<SecondarySkill>(pair.second.getModScope(), pair.first, variables);
 				ret[id] = loadValue(pair.second, rng, variables);
 			}
 		}

+ 3 - 3
lib/json/JsonRandom.h

@@ -9,9 +9,9 @@
  */
 #pragma once
 
+#include "GameCallbackHolder.h"
 #include "GameConstants.h"
 #include "ResourceSet.h"
-#include "GameCallbackHolder.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -23,7 +23,7 @@ struct Bonus;
 struct Component;
 class CStackBasicDescriptor;
 
-class DLL_LINKAGE JsonRandom : public GameCallbackHolder
+class JsonRandom : public GameCallbackHolder
 {
 public:
 	using Variables = std::map<std::string, int>;
@@ -46,7 +46,7 @@ private:
 public:
 	using GameCallbackHolder::GameCallbackHolder;
 
-	struct DLL_LINKAGE RandomStackInfo
+	struct RandomStackInfo
 	{
 		std::vector<const CCreature *> allowedCreatures;
 		si32 minAmount;

+ 12 - 151
lib/json/JsonUtils.cpp

@@ -13,38 +13,12 @@
 
 #include "JsonValidator.h"
 
-#include "../ScopeGuard.h"
-#include "../bonuses/BonusParams.h"
-#include "../bonuses/Bonus.h"
-#include "../bonuses/Limiters.h"
-#include "../bonuses/Propagators.h"
-#include "../bonuses/Updaters.h"
 #include "../filesystem/Filesystem.h"
-#include "../modding/IdentifierStorage.h"
-#include "../VCMI_Lib.h" //for identifier resolution
-#include "../CGeneralTextHandler.h"
-#include "../constants/StringConstants.h"
-#include "../battle/BattleHex.h"
 
-VCMI_LIB_NAMESPACE_BEGIN
+VCMI_LIB_USING_NAMESPACE
 
 static const JsonNode nullNode;
 
-//returns first Key with value equal to given one
-template<class Key, class Val>
-Key reverseMapFirst(const Val & val, const std::map<Key, Val> & map)
-{
-	for(auto it : map)
-	{
-		if(it.second == val)
-		{
-			return it.first;
-		}
-	}
-	assert(0);
-	return "";
-}
-
 static JsonNode getDefaultValue(const JsonNode & schema, std::string fieldName)
 {
 	const JsonNode & fieldProps = schema["properties"][fieldName];
@@ -76,8 +50,8 @@ static void eraseOptionalNodes(JsonNode & node, const JsonNode & schema)
 	for(const auto & entry : schema["required"].Vector())
 		foundEntries.insert(entry.String());
 
-	vstd::erase_if(node.Struct(), [&](const auto & node){
-		return !vstd::contains(foundEntries, node.first);
+	vstd::erase_if(node.Struct(), [&foundEntries](const auto & structEntry){
+		return !vstd::contains(foundEntries, structEntry.first);
 	});
 }
 
@@ -117,6 +91,8 @@ static void maximizeNode(JsonNode & node, const JsonNode & schema)
 	eraseOptionalNodes(node, schema);
 }
 
+VCMI_LIB_NAMESPACE_BEGIN
+
 void JsonUtils::minimize(JsonNode & node, const std::string & schemaName)
 {
 	minimizeNode(node, getSchema(schemaName));
@@ -129,12 +105,13 @@ void JsonUtils::maximize(JsonNode & node, const std::string & schemaName)
 
 bool JsonUtils::validate(const JsonNode & node, const std::string & schemaName, const std::string & dataName)
 {
-	std::string log = Validation::check(schemaName, node);
+	JsonValidator validator;
+	std::string log = validator.check(schemaName, node);
 	if (!log.empty())
 	{
 		logMod->warn("Data in %s is invalid!", dataName);
 		logMod->warn(log);
-		logMod->trace("%s json: %s", dataName, node.toJson(true));
+		logMod->trace("%s json: %s", dataName, node.toCompactString());
 	}
 	return log.empty();
 }
@@ -223,14 +200,14 @@ void JsonUtils::merge(JsonNode & dest, JsonNode & source, bool ignoreOverride, b
 		}
 		case JsonNode::JsonType::DATA_STRUCT:
 		{
-			if(!ignoreOverride && vstd::contains(source.flags, "override"))
+			if(!ignoreOverride && source.getOverrideFlag())
 			{
 				std::swap(dest, source);
 			}
 			else
 			{
 				if (copyMeta)
-					dest.meta = source.meta;
+					dest.setModScope(source.getModScope(), false);
 
 				//recursively merge all entries from struct
 				for(auto & node : source.Struct())
@@ -253,90 +230,6 @@ void JsonUtils::inherit(JsonNode & descendant, const JsonNode & base)
 	std::swap(descendant, inheritedNode);
 }
 
-JsonNode JsonUtils::intersect(const std::vector<JsonNode> & nodes, bool pruneEmpty)
-{
-	if(nodes.empty())
-		return nullNode;
-
-	JsonNode result = nodes[0];
-	for(int i = 1; i < nodes.size(); i++)
-	{
-		if(result.isNull())
-			break;
-		result = JsonUtils::intersect(result, nodes[i], pruneEmpty);
-	}
-	return result;
-}
-
-JsonNode JsonUtils::intersect(const JsonNode & a, const JsonNode & b, bool pruneEmpty)
-{
-	if(a.getType() == JsonNode::JsonType::DATA_STRUCT && b.getType() == JsonNode::JsonType::DATA_STRUCT)
-	{
-		// intersect individual properties
-		JsonNode result(JsonNode::JsonType::DATA_STRUCT);
-		for(const auto & property : a.Struct())
-		{
-			if(vstd::contains(b.Struct(), property.first))
-			{
-				JsonNode propertyIntersect = JsonUtils::intersect(property.second, b.Struct().find(property.first)->second);
-				if(pruneEmpty && !propertyIntersect.containsBaseData())
-					continue;
-				result[property.first] = propertyIntersect;
-			}
-		}
-		return result;
-	}
-	else
-	{
-		// not a struct - same or different, no middle ground
-		if(a == b)
-			return a;
-	}
-	return nullNode;
-}
-
-JsonNode JsonUtils::difference(const JsonNode & node, const JsonNode & base)
-{
-	auto addsInfo = [](JsonNode diff) -> bool
-	{
-		switch(diff.getType())
-		{
-		case JsonNode::JsonType::DATA_NULL:
-			return false;
-		case JsonNode::JsonType::DATA_STRUCT:
-			return !diff.Struct().empty();
-		default:
-			return true;
-		}
-	};
-
-	if(node.getType() == JsonNode::JsonType::DATA_STRUCT && base.getType() == JsonNode::JsonType::DATA_STRUCT)
-	{
-		// subtract individual properties
-		JsonNode result(JsonNode::JsonType::DATA_STRUCT);
-		for(const auto & property : node.Struct())
-		{
-			if(vstd::contains(base.Struct(), property.first))
-			{
-				const JsonNode propertyDifference = JsonUtils::difference(property.second, base.Struct().find(property.first)->second);
-				if(addsInfo(propertyDifference))
-					result[property.first] = propertyDifference;
-			}
-			else
-			{
-				result[property.first] = property.second;
-			}
-		}
-		return result;
-	}
-	else
-	{
-		if(node == base)
-			return nullNode;
-	}
-	return node;
-}
-
 JsonNode JsonUtils::assembleFromFiles(const std::vector<std::string> & files)
 {
 	bool isValid = false;
@@ -365,43 +258,11 @@ JsonNode JsonUtils::assembleFromFiles(const std::string & filename)
 
 	for(auto & loader : CResourceHandler::get()->getResourcesWithName(resID))
 	{
-		// FIXME: some way to make this code more readable
-		auto stream = loader->load(resID);
-		std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
-		stream->read(textData.get(), stream->getSize());
-
-		JsonNode section(reinterpret_cast<char *>(textData.get()), stream->getSize());
+		auto textData = loader->load(resID)->readAll();
+		JsonNode section(reinterpret_cast<std::byte *>(textData.first.get()), textData.second);
 		merge(result, section);
 	}
 	return result;
 }
 
-DLL_LINKAGE JsonNode JsonUtils::boolNode(bool value)
-{
-	JsonNode node;
-	node.Bool() = value;
-	return node;
-}
-
-DLL_LINKAGE JsonNode JsonUtils::floatNode(double value)
-{
-	JsonNode node;
-	node.Float() = value;
-	return node;
-}
-
-DLL_LINKAGE JsonNode JsonUtils::stringNode(const std::string & value)
-{
-	JsonNode node;
-	node.String() = value;
-	return node;
-}
-
-DLL_LINKAGE JsonNode JsonUtils::intNode(si64 value)
-{
-	JsonNode node;
-	node.Integer() = value;
-	return node;
-}
-
 VCMI_LIB_NAMESPACE_END

+ 0 - 22
lib/json/JsonUtils.h

@@ -40,22 +40,6 @@ namespace JsonUtils
 	*/
 	DLL_LINKAGE void inherit(JsonNode & descendant, const JsonNode & base);
 
-	/**
-	 * @brief construct node representing the common structure of input nodes
-	 * @param pruneEmpty - omit common properties whose intersection is empty
-	 * different types: null
-	 * struct: recursive intersect on common properties
-	 * other: input if equal, null otherwise
-	 */
-	DLL_LINKAGE JsonNode intersect(const JsonNode & a, const JsonNode & b, bool pruneEmpty = true);
-	DLL_LINKAGE JsonNode intersect(const std::vector<JsonNode> & nodes, bool pruneEmpty = true);
-
-	/**
-	 * @brief construct node representing the difference "node - base"
-	 * merging difference with base gives node
-	 */
-	DLL_LINKAGE JsonNode difference(const JsonNode & node, const JsonNode & base);
-
 	/**
 	 * @brief generate one Json structure from multiple files
 	 * @param files - list of filenames with parts of json structure
@@ -88,12 +72,6 @@ namespace JsonUtils
 	/// get schema by json URI: vcmi:<name of file in schemas directory>#<entry in file, optional>
 	/// example: schema "vcmi:settings" is used to check user settings
 	DLL_LINKAGE const JsonNode & getSchema(const std::string & URI);
-
-	/// for easy construction of JsonNodes; helps with inserting primitives into vector node
-	DLL_LINKAGE JsonNode boolNode(bool value);
-	DLL_LINKAGE JsonNode floatNode(double value);
-	DLL_LINKAGE JsonNode stringNode(const std::string & value);
-	DLL_LINKAGE JsonNode intNode(si64 value);
 }
 
 VCMI_LIB_NAMESPACE_END

+ 523 - 552
lib/json/JsonValidator.cpp

@@ -21,667 +21,638 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-//TODO: integer support
+static std::string emptyCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	// check is not needed - e.g. incorporated into another check
+	return "";
+}
 
-static const std::unordered_map<std::string, JsonNode::JsonType> stringToType =
+static std::string notImplementedCheck(JsonValidator & validator,
+								const JsonNode & baseSchema,
+								const JsonNode & schema,
+								const JsonNode & data)
 {
-	{"null",   JsonNode::JsonType::DATA_NULL},
-	{"boolean", JsonNode::JsonType::DATA_BOOL},
-	{"number", JsonNode::JsonType::DATA_FLOAT},
-	{"string",  JsonNode::JsonType::DATA_STRING},
-	{"array",  JsonNode::JsonType::DATA_VECTOR},
-	{"object",  JsonNode::JsonType::DATA_STRUCT}
-};
+	return "Not implemented entry in schema";
+}
 
-namespace
+static std::string schemaListCheck(JsonValidator & validator,
+							const JsonNode & baseSchema,
+							const JsonNode & schema,
+							const JsonNode & data,
+							const std::string & errorMsg,
+							const std::function<bool(size_t)> & isValid)
 {
-	namespace Common
+	std::string errors = "<tested schemas>\n";
+	size_t result = 0;
+
+	for(const auto & schemaEntry : schema.Vector())
 	{
-		std::string emptyCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+		std::string error = validator.check(schemaEntry, data);
+		if (error.empty())
 		{
-			// check is not needed - e.g. incorporated into another check
-			return "";
+			result++;
 		}
-
-		std::string notImplementedCheck(Validation::ValidationData & validator,
-										const JsonNode & baseSchema,
-										const JsonNode & schema,
-										const JsonNode & data)
+		else
 		{
-			return "Not implemented entry in schema";
+			errors += error;
+			errors += "<end of schema>\n";
 		}
+	}
+	if (isValid(result))
+		return "";
+	else
+		return validator.makeErrorMessage(errorMsg) + errors;
+}
 
-		std::string schemaListCheck(Validation::ValidationData & validator,
-									const JsonNode & baseSchema,
-									const JsonNode & schema,
-									const JsonNode & data,
-									const std::string & errorMsg,
-									const std::function<bool(size_t)> & isValid)
-		{
-			std::string errors = "<tested schemas>\n";
-			size_t result = 0;
-
-			for(const auto & schemaEntry : schema.Vector())
-			{
-				std::string error = check(schemaEntry, data, validator);
-				if (error.empty())
-				{
-					result++;
-				}
-				else
-				{
-					errors += error;
-					errors += "<end of schema>\n";
-				}
-			}
-			if (isValid(result))
-				return "";
-			else
-				return validator.makeErrorMessage(errorMsg) + errors;
-		}
+static std::string allOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass all schemas", [&schema](size_t count)
+	{
+		return count == schema.Vector().size();
+	});
+}
 
-		std::string allOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass all schemas", [&](size_t count)
-			{
-				return count == schema.Vector().size();
-			});
-		}
+static std::string anyOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass any schema", [](size_t count)
+	{
+		return count > 0;
+	});
+}
 
-		std::string anyOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass any schema", [&](size_t count)
-			{
-				return count > 0;
-			});
-		}
+static std::string oneOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass exactly one schema", [](size_t count)
+	{
+		return count == 1;
+	});
+}
 
-		std::string oneOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass exactly one schema", [&](size_t count)
-			{
-				return count == 1;
-			});
-		}
+static std::string notCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (validator.check(schema, data).empty())
+		return validator.makeErrorMessage("Successful validation against negative check");
+	return "";
+}
 
-		std::string notCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			if (check(schema, data, validator).empty())
-				return validator.makeErrorMessage("Successful validation against negative check");
+static std::string enumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	for(const auto & enumEntry : schema.Vector())
+	{
+		if (data == enumEntry)
 			return "";
-		}
-
-		std::string enumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			for(const auto & enumEntry : schema.Vector())
-			{
-				if (data == enumEntry)
-					return "";
-			}
-			return validator.makeErrorMessage("Key must have one of predefined values");
-		}
-
-		std::string typeCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			const auto & typeName = schema.String();
-			auto it = stringToType.find(typeName);
-			if(it == stringToType.end())
-			{
-				return validator.makeErrorMessage("Unknown type in schema:" + typeName);
-			}
+	}
+	return validator.makeErrorMessage("Key must have one of predefined values");
+}
 
-			JsonNode::JsonType type = it->second;
+static std::string typeCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	static const std::unordered_map<std::string, JsonNode::JsonType> stringToType =
+	{
+		{"null",   JsonNode::JsonType::DATA_NULL},
+		{"boolean", JsonNode::JsonType::DATA_BOOL},
+		{"number", JsonNode::JsonType::DATA_FLOAT},
+		{"integer", JsonNode::JsonType::DATA_INTEGER},
+		{"string",  JsonNode::JsonType::DATA_STRING},
+		{"array",  JsonNode::JsonType::DATA_VECTOR},
+		{"object",  JsonNode::JsonType::DATA_STRUCT}
+	};
+
+	const auto & typeName = schema.String();
+	auto it = stringToType.find(typeName);
+	if(it == stringToType.end())
+	{
+		return validator.makeErrorMessage("Unknown type in schema:" + typeName);
+	}
 
-			//FIXME: hack for integer values
-			if(data.isNumber() && type == JsonNode::JsonType::DATA_FLOAT)
-				return "";
+	JsonNode::JsonType type = it->second;
 
-			if(type != data.getType() && data.getType() != JsonNode::JsonType::DATA_NULL)
-				return validator.makeErrorMessage("Type mismatch! Expected " + schema.String());
-			return "";
-		}
+	// for "number" type both float and integer are allowed
+	if(type == JsonNode::JsonType::DATA_FLOAT && data.isNumber())
+		return "";
 
-		std::string refCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			std::string URI = schema.String();
-			//node must be validated using schema pointed by this reference and not by data here
-			//Local reference. Turn it into more easy to handle remote ref
-			if (boost::algorithm::starts_with(URI, "#"))
-			{
-				const std::string name = validator.usedSchemas.back();
-				const std::string nameClean = name.substr(0, name.find('#'));
-				URI = nameClean + URI;
-			}
-			return check(URI, data, validator);
-		}
+	if(type != data.getType() && data.getType() != JsonNode::JsonType::DATA_NULL)
+		return validator.makeErrorMessage("Type mismatch! Expected " + schema.String());
+	return "";
+}
 
-		std::string formatCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			auto formats = Validation::getKnownFormats();
-			std::string errors;
-			auto checker = formats.find(schema.String());
-			if (checker != formats.end())
-			{
-				if (data.isString())
-				{
-					std::string result = checker->second(data);
-					if (!result.empty())
-						errors += validator.makeErrorMessage(result);
-				}
-				else
-				{
-					errors += validator.makeErrorMessage("Format value must be string: " + schema.String());
-				}
-			}
-			else
-				errors += validator.makeErrorMessage("Unsupported format type: " + schema.String());
-			return errors;
-		}
+static std::string refCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	std::string URI = schema.String();
+	//node must be validated using schema pointed by this reference and not by data here
+	//Local reference. Turn it into more easy to handle remote ref
+	if (boost::algorithm::starts_with(URI, "#"))
+	{
+		const std::string name = validator.usedSchemas.back();
+		const std::string nameClean = name.substr(0, name.find('#'));
+		URI = nameClean + URI;
 	}
+	return validator.check(URI, data);
+}
 
-	namespace String
+static std::string formatCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	auto formats = validator.getKnownFormats();
+	std::string errors;
+	auto checker = formats.find(schema.String());
+	if (checker != formats.end())
 	{
-		std::string maxLengthCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+		if (data.isString())
 		{
-			if (data.String().size() > schema.Float())
-				return validator.makeErrorMessage((boost::format("String is longer than %d symbols") % schema.Float()).str());
-			return "";
+			std::string result = checker->second(data);
+			if (!result.empty())
+				errors += validator.makeErrorMessage(result);
 		}
-
-		std::string minLengthCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+		else
 		{
-			if (data.String().size() < schema.Float())
-				return validator.makeErrorMessage((boost::format("String is shorter than %d symbols") % schema.Float()).str());
-			return "";
+			errors += validator.makeErrorMessage("Format value must be string: " + schema.String());
 		}
 	}
+	else
+		errors += validator.makeErrorMessage("Unsupported format type: " + schema.String());
+	return errors;
+}
+
+static std::string maxLengthCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.String().size() > schema.Float())
+		return validator.makeErrorMessage((boost::format("String is longer than %d symbols") % schema.Float()).str());
+	return "";
+}
+
+static std::string minLengthCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.String().size() < schema.Float())
+		return validator.makeErrorMessage((boost::format("String is shorter than %d symbols") % schema.Float()).str());
+	return "";
+}
+
+static std::string maximumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Float() > schema.Float())
+		return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str());
+	return "";
+}
+
+static std::string minimumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Float() < schema.Float())
+		return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str());
+	return "";
+}
+
+static std::string exclusiveMaximumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Float() >= schema.Float())
+		return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str());
+	return "";
+}
+
+static std::string exclusiveMinimumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Float() <= schema.Float())
+		return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str());
+	return "";
+}
 
-	namespace Number
+static std::string multipleOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	double result = data.Integer() / schema.Integer();
+	if (!vstd::isAlmostEqual(floor(result), result))
+		return validator.makeErrorMessage((boost::format("Value is not divisible by %d") % schema.Float()).str());
+	return "";
+}
+
+static std::string itemEntryCheck(JsonValidator & validator, const JsonVector & items, const JsonNode & schema, size_t index)
+{
+	validator.currentPath.emplace_back();
+	validator.currentPath.back().Float() = static_cast<double>(index);
+	auto onExit = vstd::makeScopeGuard([&validator]()
 	{
+		validator.currentPath.pop_back();
+	});
 
-		std::string maximumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			if (baseSchema["exclusiveMaximum"].Bool())
-			{
-				if (data.Float() >= schema.Float())
-					return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str());
-			}
-			else
-			{
-				if (data.Float() >  schema.Float())
-					return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str());
-			}
-			return "";
-		}
+	if (!schema.isNull())
+		return validator.check(schema, items[index]);
+	return "";
+}
 
-		std::string minimumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+static std::string itemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	std::string errors;
+	for (size_t i=0; i<data.Vector().size(); i++)
+	{
+		if (schema.getType() == JsonNode::JsonType::DATA_VECTOR)
 		{
-			if (baseSchema["exclusiveMinimum"].Bool())
-			{
-				if (data.Float() <= schema.Float())
-					return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str());
-			}
-			else
-			{
-				if (data.Float() <  schema.Float())
-					return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str());
-			}
-			return "";
+			if (schema.Vector().size() > i)
+				errors += itemEntryCheck(validator, data.Vector(), schema.Vector()[i], i);
 		}
-
-		std::string multipleOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+		else
 		{
-			double result = data.Float() / schema.Float();
-			if (!vstd::isAlmostEqual(floor(result), result))
-				return validator.makeErrorMessage((boost::format("Value is not divisible by %d") % schema.Float()).str());
-			return "";
+			errors += itemEntryCheck(validator, data.Vector(), schema, i);
 		}
 	}
+	return errors;
+}
+
+static std::string additionalItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	std::string errors;
+	// "items" is struct or empty (defaults to empty struct) - validation always successful
+	const JsonNode & items = baseSchema["items"];
+	if (items.getType() != JsonNode::JsonType::DATA_VECTOR)
+		return "";
 
-	namespace Vector
+	for (size_t i=items.Vector().size(); i<data.Vector().size(); i++)
 	{
-		std::string itemEntryCheck(Validation::ValidationData & validator, const JsonVector & items, const JsonNode & schema, size_t index)
-		{
-			validator.currentPath.emplace_back();
-			validator.currentPath.back().Float() = static_cast<double>(index);
-			auto onExit = vstd::makeScopeGuard([&]()
-			{
-				validator.currentPath.pop_back();
-			});
+		if (schema.getType() == JsonNode::JsonType::DATA_STRUCT)
+			errors += itemEntryCheck(validator, data.Vector(), schema, i);
+		else if(!schema.isNull() && !schema.Bool())
+			errors += validator.makeErrorMessage("Unknown entry found");
+	}
+	return errors;
+}
 
-			if (!schema.isNull())
-				return check(schema, items[index], validator);
-			return "";
-		}
+static std::string minItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Vector().size() < schema.Float())
+		return validator.makeErrorMessage((boost::format("Length is smaller than %d") % schema.Float()).str());
+	return "";
+}
 
-		std::string itemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			std::string errors;
-			for (size_t i=0; i<data.Vector().size(); i++)
-			{
-				if (schema.getType() == JsonNode::JsonType::DATA_VECTOR)
-				{
-					if (schema.Vector().size() > i)
-						errors += itemEntryCheck(validator, data.Vector(), schema.Vector()[i], i);
-				}
-				else
-				{
-					errors += itemEntryCheck(validator, data.Vector(), schema, i);
-				}
-			}
-			return errors;
-		}
+static std::string maxItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Vector().size() > schema.Float())
+		return validator.makeErrorMessage((boost::format("Length is bigger than %d") % schema.Float()).str());
+	return "";
+}
 
-		std::string additionalItemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+static std::string uniqueItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (schema.Bool())
+	{
+		for (auto itA = schema.Vector().begin(); itA != schema.Vector().end(); itA++)
 		{
-			std::string errors;
-			// "items" is struct or empty (defaults to empty struct) - validation always successful
-			const JsonNode & items = baseSchema["items"];
-			if (items.getType() != JsonNode::JsonType::DATA_VECTOR)
-				return "";
-
-			for (size_t i=items.Vector().size(); i<data.Vector().size(); i++)
+			auto itB = itA;
+			while (++itB != schema.Vector().end())
 			{
-				if (schema.getType() == JsonNode::JsonType::DATA_STRUCT)
-					errors += itemEntryCheck(validator, data.Vector(), schema, i);
-				else if(!schema.isNull() && !schema.Bool())
-					errors += validator.makeErrorMessage("Unknown entry found");
+				if (*itA == *itB)
+					return validator.makeErrorMessage("List must consist from unique items");
 			}
-			return errors;
 		}
+	}
+	return "";
+}
 
-		std::string minItemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			if (data.Vector().size() < schema.Float())
-				return validator.makeErrorMessage((boost::format("Length is smaller than %d") % schema.Float()).str());
-			return "";
-		}
+static std::string maxPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Struct().size() > schema.Float())
+		return validator.makeErrorMessage((boost::format("Number of entries is bigger than %d") % schema.Float()).str());
+	return "";
+}
 
-		std::string maxItemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			if (data.Vector().size() > schema.Float())
-				return validator.makeErrorMessage((boost::format("Length is bigger than %d") % schema.Float()).str());
-			return "";
-		}
+static std::string minPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Struct().size() < schema.Float())
+		return validator.makeErrorMessage((boost::format("Number of entries is less than %d") % schema.Float()).str());
+	return "";
+}
 
-		std::string uniqueItemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+static std::string uniquePropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	for (auto itA = data.Struct().begin(); itA != data.Struct().end(); itA++)
+	{
+		auto itB = itA;
+		while (++itB != data.Struct().end())
 		{
-			if (schema.Bool())
-			{
-				for (auto itA = schema.Vector().begin(); itA != schema.Vector().end(); itA++)
-				{
-					auto itB = itA;
-					while (++itB != schema.Vector().end())
-					{
-						if (*itA == *itB)
-							return validator.makeErrorMessage("List must consist from unique items");
-					}
-				}
-			}
-			return "";
+			if (itA->second == itB->second)
+				return validator.makeErrorMessage("List must consist from unique items");
 		}
 	}
+	return "";
+}
 
-	namespace Struct
+static std::string requiredCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	std::string errors;
+	for(const auto & required : schema.Vector())
 	{
-		std::string maxPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			if (data.Struct().size() > schema.Float())
-				return validator.makeErrorMessage((boost::format("Number of entries is bigger than %d") % schema.Float()).str());
-			return "";
-		}
-
-		std::string minPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			if (data.Struct().size() < schema.Float())
-				return validator.makeErrorMessage((boost::format("Number of entries is less than %d") % schema.Float()).str());
-			return "";
-		}
+		if (data[required.String()].isNull())
+			errors += validator.makeErrorMessage("Required entry " + required.String() + " is missing");
+	}
+	return errors;
+}
 
-		std::string uniquePropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+static std::string dependenciesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	std::string errors;
+	for(const auto & deps : schema.Struct())
+	{
+		if (!data[deps.first].isNull())
 		{
-			for (auto itA = data.Struct().begin(); itA != data.Struct().end(); itA++)
+			if (deps.second.getType() == JsonNode::JsonType::DATA_VECTOR)
 			{
-				auto itB = itA;
-				while (++itB != data.Struct().end())
+				JsonVector depList = deps.second.Vector();
+				for(auto & depEntry : depList)
 				{
-					if (itA->second == itB->second)
-						return validator.makeErrorMessage("List must consist from unique items");
+					if (data[depEntry.String()].isNull())
+						errors += validator.makeErrorMessage("Property " + depEntry.String() + " required for " + deps.first + " is missing");
 				}
 			}
-			return "";
-		}
-
-		std::string requiredCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			std::string errors;
-			for(const auto & required : schema.Vector())
-			{
-				if (data[required.String()].isNull())
-					errors += validator.makeErrorMessage("Required entry " + required.String() + " is missing");
-			}
-			return errors;
-		}
-
-		std::string dependenciesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			std::string errors;
-			for(const auto & deps : schema.Struct())
+			else
 			{
-				if (!data[deps.first].isNull())
-				{
-					if (deps.second.getType() == JsonNode::JsonType::DATA_VECTOR)
-					{
-						JsonVector depList = deps.second.Vector();
-						for(auto & depEntry : depList)
-						{
-							if (data[depEntry.String()].isNull())
-								errors += validator.makeErrorMessage("Property " + depEntry.String() + " required for " + deps.first + " is missing");
-						}
-					}
-					else
-					{
-						if (!check(deps.second, data, validator).empty())
-							errors += validator.makeErrorMessage("Requirements for " + deps.first + " are not fulfilled");
-					}
-				}
+				if (!validator.check(deps.second, data).empty())
+					errors += validator.makeErrorMessage("Requirements for " + deps.first + " are not fulfilled");
 			}
-			return errors;
 		}
+	}
+	return errors;
+}
 
-		std::string propertyEntryCheck(Validation::ValidationData & validator, const JsonNode &node, const JsonNode & schema, const std::string & nodeName)
-		{
-			validator.currentPath.emplace_back();
-			validator.currentPath.back().String() = nodeName;
-			auto onExit = vstd::makeScopeGuard([&]()
-			{
-				validator.currentPath.pop_back();
-			});
+static std::string propertyEntryCheck(JsonValidator & validator, const JsonNode &node, const JsonNode & schema, const std::string & nodeName)
+{
+	validator.currentPath.emplace_back();
+	validator.currentPath.back().String() = nodeName;
+	auto onExit = vstd::makeScopeGuard([&validator]()
+	{
+		validator.currentPath.pop_back();
+	});
 
-			// there is schema specifically for this item
-			if (!schema.isNull())
-				return check(schema, node, validator);
-			return "";
-		}
+	// there is schema specifically for this item
+	if (!schema.isNull())
+		return validator.check(schema, node);
+	return "";
+}
 
-		std::string propertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			std::string errors;
+static std::string propertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	std::string errors;
 
-			for(const auto & entry : data.Struct())
-				errors += propertyEntryCheck(validator, entry.second, schema[entry.first], entry.first);
-			return errors;
-		}
+	for(const auto & entry : data.Struct())
+		errors += propertyEntryCheck(validator, entry.second, schema[entry.first], entry.first);
+	return errors;
+}
 
-		std::string additionalPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+static std::string additionalPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	std::string errors;
+	for(const auto & entry : data.Struct())
+	{
+		if (baseSchema["properties"].Struct().count(entry.first) == 0)
 		{
-			std::string errors;
-			for(const auto & entry : data.Struct())
-			{
-				if (baseSchema["properties"].Struct().count(entry.first) == 0)
-				{
-					// try generic additionalItems schema
-					if (schema.getType() == JsonNode::JsonType::DATA_STRUCT)
-						errors += propertyEntryCheck(validator, entry.second, schema, entry.first);
+			// try generic additionalItems schema
+			if (schema.getType() == JsonNode::JsonType::DATA_STRUCT)
+				errors += propertyEntryCheck(validator, entry.second, schema, entry.first);
 
-					// or, additionalItems field can be bool which indicates if such items are allowed
-					else if(!schema.isNull() && !schema.Bool()) // present and set to false - error
-						errors += validator.makeErrorMessage("Unknown entry found: " + entry.first);
-				}
-			}
-			return errors;
+			// or, additionalItems field can be bool which indicates if such items are allowed
+			else if(!schema.isNull() && !schema.Bool()) // present and set to false - error
+				errors += validator.makeErrorMessage("Unknown entry found: " + entry.first);
 		}
 	}
+	return errors;
+}
 
-	namespace Formats
+static bool testFilePresence(const std::string & scope, const ResourcePath & resource)
+{
+	std::set<std::string> allowedScopes;
+	if(scope != ModScope::scopeBuiltin() && !scope.empty()) // all real mods may have dependencies
 	{
-		bool testFilePresence(const std::string & scope, const ResourcePath & resource)
-		{
-			std::set<std::string> allowedScopes;
-			if(scope != ModScope::scopeBuiltin() && !scope.empty()) // all real mods may have dependencies
-			{
-				//NOTE: recursive dependencies are not allowed at the moment - update code if this changes
-				bool found = true;
-				allowedScopes = VLC->modh->getModDependencies(scope, found);
-
-				if(!found)
-					return false;
+		//NOTE: recursive dependencies are not allowed at the moment - update code if this changes
+		bool found = true;
+		allowedScopes = VLC->modh->getModDependencies(scope, found);
 
-				allowedScopes.insert(ModScope::scopeBuiltin()); // all mods can use H3 files
-			}
-			allowedScopes.insert(scope); // mods can use their own files
-
-			for(const auto & entry : allowedScopes)
-			{
-				if (CResourceHandler::get(entry)->existsResource(resource))
-					return true;
-			}
+		if(!found)
 			return false;
-		}
 
-		#define TEST_FILE(scope, prefix, file, type) \
-			if (testFilePresence(scope, ResourcePath(prefix + file, type))) \
-				return ""
+		allowedScopes.insert(ModScope::scopeBuiltin()); // all mods can use H3 files
+	}
+	allowedScopes.insert(scope); // mods can use their own files
 
-		std::string testAnimation(const std::string & path, const std::string & scope)
-		{
-			TEST_FILE(scope, "Sprites/", path, EResType::ANIMATION);
-			TEST_FILE(scope, "Sprites/", path, EResType::JSON);
-			return "Animation file \"" + path + "\" was not found";
-		}
+	for(const auto & entry : allowedScopes)
+	{
+		if (CResourceHandler::get(entry)->existsResource(resource))
+			return true;
+	}
+	return false;
+}
 
-		std::string textFile(const JsonNode & node)
-		{
-			TEST_FILE(node.meta, "", node.String(), EResType::JSON);
-			return "Text file \"" + node.String() + "\" was not found";
-		}
+#define TEST_FILE(scope, prefix, file, type) \
+	if (testFilePresence(scope, ResourcePath(prefix + file, type))) \
+	return ""
 
-		std::string musicFile(const JsonNode & node)
-		{
-			TEST_FILE(node.meta, "Music/", node.String(), EResType::SOUND);
-			TEST_FILE(node.meta, "", node.String(), EResType::SOUND);
-			return "Music file \"" + node.String() + "\" was not found";
-		}
+static std::string testAnimation(const std::string & path, const std::string & scope)
+{
+	TEST_FILE(scope, "Sprites/", path, EResType::ANIMATION);
+	TEST_FILE(scope, "Sprites/", path, EResType::JSON);
+	return "Animation file \"" + path + "\" was not found";
+}
 
-		std::string soundFile(const JsonNode & node)
-		{
-			TEST_FILE(node.meta, "Sounds/", node.String(), EResType::SOUND);
-			return "Sound file \"" + node.String() + "\" was not found";
-		}
+static std::string textFile(const JsonNode & node)
+{
+	TEST_FILE(node.getModScope(), "", node.String(), EResType::JSON);
+	return "Text file \"" + node.String() + "\" was not found";
+}
 
-		std::string defFile(const JsonNode & node)
-		{
-			return testAnimation(node.String(), node.meta);
-		}
+static std::string musicFile(const JsonNode & node)
+{
+	TEST_FILE(node.getModScope(), "Music/", node.String(), EResType::SOUND);
+	TEST_FILE(node.getModScope(), "", node.String(), EResType::SOUND);
+	return "Music file \"" + node.String() + "\" was not found";
+}
 
-		std::string animationFile(const JsonNode & node)
-		{
-			return testAnimation(node.String(), node.meta);
-		}
+static std::string soundFile(const JsonNode & node)
+{
+	TEST_FILE(node.getModScope(), "Sounds/", node.String(), EResType::SOUND);
+	return "Sound file \"" + node.String() + "\" was not found";
+}
 
-		std::string imageFile(const JsonNode & node)
-		{
-			TEST_FILE(node.meta, "Data/", node.String(), EResType::IMAGE);
-			TEST_FILE(node.meta, "Sprites/", node.String(), EResType::IMAGE);
-			if (node.String().find(':') != std::string::npos)
-				return testAnimation(node.String().substr(0, node.String().find(':')), node.meta);
-			return "Image file \"" + node.String() + "\" was not found";
-		}
+static std::string animationFile(const JsonNode & node)
+{
+	return testAnimation(node.String(), node.getModScope());
+}
 
-		std::string videoFile(const JsonNode & node)
-		{
-			TEST_FILE(node.meta, "Video/", node.String(), EResType::VIDEO);
-			return "Video file \"" + node.String() + "\" was not found";
-		}
+static std::string imageFile(const JsonNode & node)
+{
+	TEST_FILE(node.getModScope(), "Data/", node.String(), EResType::IMAGE);
+	TEST_FILE(node.getModScope(), "Sprites/", node.String(), EResType::IMAGE);
+	if (node.String().find(':') != std::string::npos)
+		return testAnimation(node.String().substr(0, node.String().find(':')), node.getModScope());
+	return "Image file \"" + node.String() + "\" was not found";
+}
 
-		#undef TEST_FILE
-	}
+static std::string videoFile(const JsonNode & node)
+{
+	TEST_FILE(node.getModScope(), "Video/", node.String(), EResType::VIDEO);
+	return "Video file \"" + node.String() + "\" was not found";
+}
+#undef TEST_FILE
 
-	Validation::TValidatorMap createCommonFields()
-	{
-		Validation::TValidatorMap ret;
-
-		ret["format"] =  Common::formatCheck;
-		ret["allOf"] = Common::allOfCheck;
-		ret["anyOf"] = Common::anyOfCheck;
-		ret["oneOf"] = Common::oneOfCheck;
-		ret["enum"]  = Common::enumCheck;
-		ret["type"]  = Common::typeCheck;
-		ret["not"]   = Common::notCheck;
-		ret["$ref"]  = Common::refCheck;
-
-		// fields that don't need implementation
-		ret["title"] = Common::emptyCheck;
-		ret["$schema"] = Common::emptyCheck;
-		ret["default"] = Common::emptyCheck;
-		ret["description"] = Common::emptyCheck;
-		ret["definitions"] = Common::emptyCheck;
-		return ret;
-	}
+JsonValidator::TValidatorMap createCommonFields()
+{
+	JsonValidator::TValidatorMap ret;
+
+	ret["format"] =  formatCheck;
+	ret["allOf"] = allOfCheck;
+	ret["anyOf"] = anyOfCheck;
+	ret["oneOf"] = oneOfCheck;
+	ret["enum"]  = enumCheck;
+	ret["type"]  = typeCheck;
+	ret["not"]   = notCheck;
+	ret["$ref"]  = refCheck;
+
+	// fields that don't need implementation
+	ret["title"] = emptyCheck;
+	ret["$schema"] = emptyCheck;
+	ret["default"] = emptyCheck;
+	ret["defaultIOS"] = emptyCheck;
+	ret["defaultAndroid"] = emptyCheck;
+	ret["defaultWindows"] = emptyCheck;
+	ret["description"] = emptyCheck;
+	ret["definitions"] = emptyCheck;
+
+	// Not implemented
+	ret["propertyNames"] = notImplementedCheck;
+	ret["contains"] = notImplementedCheck;
+	ret["const"] = notImplementedCheck;
+	ret["examples"] = notImplementedCheck;
+
+	return ret;
+}
 
-	Validation::TValidatorMap createStringFields()
-	{
-		Validation::TValidatorMap ret = createCommonFields();
-		ret["maxLength"] = String::maxLengthCheck;
-		ret["minLength"] = String::minLengthCheck;
+JsonValidator::TValidatorMap createStringFields()
+{
+	JsonValidator::TValidatorMap ret = createCommonFields();
+	ret["maxLength"] = maxLengthCheck;
+	ret["minLength"] = minLengthCheck;
 
-		ret["pattern"] = Common::notImplementedCheck;
-		return ret;
-	}
+	ret["pattern"] = notImplementedCheck;
+	return ret;
+}
 
-	Validation::TValidatorMap createNumberFields()
-	{
-		Validation::TValidatorMap ret = createCommonFields();
-		ret["maximum"]    = Number::maximumCheck;
-		ret["minimum"]    = Number::minimumCheck;
-		ret["multipleOf"] = Number::multipleOfCheck;
-
-		ret["exclusiveMaximum"] = Common::emptyCheck;
-		ret["exclusiveMinimum"] = Common::emptyCheck;
-		return ret;
-	}
+JsonValidator::TValidatorMap createNumberFields()
+{
+	JsonValidator::TValidatorMap ret = createCommonFields();
+	ret["maximum"]    = maximumCheck;
+	ret["minimum"]    = minimumCheck;
+	ret["multipleOf"] = multipleOfCheck;
+
+	ret["exclusiveMaximum"] = exclusiveMaximumCheck;
+	ret["exclusiveMinimum"] = exclusiveMinimumCheck;
+	return ret;
+}
 
-	Validation::TValidatorMap createVectorFields()
-	{
-		Validation::TValidatorMap ret = createCommonFields();
-		ret["items"]           = Vector::itemsCheck;
-		ret["minItems"]        = Vector::minItemsCheck;
-		ret["maxItems"]        = Vector::maxItemsCheck;
-		ret["uniqueItems"]     = Vector::uniqueItemsCheck;
-		ret["additionalItems"] = Vector::additionalItemsCheck;
-		return ret;
-	}
+JsonValidator::TValidatorMap createVectorFields()
+{
+	JsonValidator::TValidatorMap ret = createCommonFields();
+	ret["items"]           = itemsCheck;
+	ret["minItems"]        = minItemsCheck;
+	ret["maxItems"]        = maxItemsCheck;
+	ret["uniqueItems"]     = uniqueItemsCheck;
+	ret["additionalItems"] = additionalItemsCheck;
+	return ret;
+}
 
-	Validation::TValidatorMap createStructFields()
-	{
-		Validation::TValidatorMap ret = createCommonFields();
-		ret["additionalProperties"]  = Struct::additionalPropertiesCheck;
-		ret["uniqueProperties"]      = Struct::uniquePropertiesCheck;
-		ret["maxProperties"]         = Struct::maxPropertiesCheck;
-		ret["minProperties"]         = Struct::minPropertiesCheck;
-		ret["dependencies"]          = Struct::dependenciesCheck;
-		ret["properties"]            = Struct::propertiesCheck;
-		ret["required"]              = Struct::requiredCheck;
-
-		ret["patternProperties"] = Common::notImplementedCheck;
-		return ret;
-	}
+JsonValidator::TValidatorMap createStructFields()
+{
+	JsonValidator::TValidatorMap ret = createCommonFields();
+	ret["additionalProperties"]  = additionalPropertiesCheck;
+	ret["uniqueProperties"]      = uniquePropertiesCheck;
+	ret["maxProperties"]         = maxPropertiesCheck;
+	ret["minProperties"]         = minPropertiesCheck;
+	ret["dependencies"]          = dependenciesCheck;
+	ret["properties"]            = propertiesCheck;
+	ret["required"]              = requiredCheck;
+
+	ret["patternProperties"] = notImplementedCheck;
+	return ret;
+}
 
-	Validation::TFormatMap createFormatMap()
-	{
-		Validation::TFormatMap ret;
-		ret["textFile"]      = Formats::textFile;
-		ret["musicFile"]     = Formats::musicFile;
-		ret["soundFile"]     = Formats::soundFile;
-		ret["defFile"]       = Formats::defFile;
-		ret["animationFile"] = Formats::animationFile;
-		ret["imageFile"]     = Formats::imageFile;
-		ret["videoFile"]     = Formats::videoFile;
-
-		return ret;
-	}
+JsonValidator::TFormatMap createFormatMap()
+{
+	JsonValidator::TFormatMap ret;
+	ret["textFile"]      = textFile;
+	ret["musicFile"]     = musicFile;
+	ret["soundFile"]     = soundFile;
+	ret["animationFile"] = animationFile;
+	ret["imageFile"]     = imageFile;
+	ret["videoFile"]     = videoFile;
+
+	//TODO:
+	// uri-reference
+	// uri-template
+	// json-pointer
+
+	return ret;
 }
 
-namespace Validation
+std::string JsonValidator::makeErrorMessage(const std::string &message)
 {
-	std::string ValidationData::makeErrorMessage(const std::string &message)
+	std::string errors;
+	errors += "At ";
+	if (!currentPath.empty())
 	{
-		std::string errors;
-		errors += "At ";
-		if (!currentPath.empty())
+		for(const JsonNode &path : currentPath)
 		{
-			for(const JsonNode &path : currentPath)
-			{
-				errors += "/";
-				if (path.getType() == JsonNode::JsonType::DATA_STRING)
-					errors += path.String();
-				else
-					errors += std::to_string(static_cast<unsigned>(path.Float()));
-			}
+			errors += "/";
+			if (path.getType() == JsonNode::JsonType::DATA_STRING)
+				errors += path.String();
+			else
+				errors += std::to_string(static_cast<unsigned>(path.Float()));
 		}
-		else
-			errors += "<root>";
-		errors += "\n\t Error: " + message + "\n";
-		return errors;
-	}
-
-	std::string check(const std::string & schemaName, const JsonNode & data)
-	{
-		ValidationData validator;
-		return check(schemaName, data, validator);
 	}
+	else
+		errors += "<root>";
+	errors += "\n\t Error: " + message + "\n";
+	return errors;
+}
 
-	std::string check(const std::string & schemaName, const JsonNode & data, ValidationData & validator)
+std::string JsonValidator::check(const std::string & schemaName, const JsonNode & data)
+{
+	usedSchemas.push_back(schemaName);
+	auto onscopeExit = vstd::makeScopeGuard([this]()
 	{
-		validator.usedSchemas.push_back(schemaName);
-		auto onscopeExit = vstd::makeScopeGuard([&]()
-		{
-			validator.usedSchemas.pop_back();
-		});
-		return check(JsonUtils::getSchema(schemaName), data, validator);
-	}
+		usedSchemas.pop_back();
+	});
+	return check(JsonUtils::getSchema(schemaName), data);
+}
 
-	std::string check(const JsonNode & schema, const JsonNode & data, ValidationData & validator)
+std::string JsonValidator::check(const JsonNode & schema, const JsonNode & data)
+{
+	const TValidatorMap & knownFields = getKnownFieldsFor(data.getType());
+	std::string errors;
+	for(const auto & entry : schema.Struct())
 	{
-		const TValidatorMap & knownFields = getKnownFieldsFor(data.getType());
-		std::string errors;
-		for(const auto & entry : schema.Struct())
-		{
-			auto checker = knownFields.find(entry.first);
-			if (checker != knownFields.end())
-				errors += checker->second(validator, schema, entry.second, data);
-			//else
-			//	errors += validator.makeErrorMessage("Unknown entry in schema " + entry.first);
-		}
-		return errors;
+		auto checker = knownFields.find(entry.first);
+		if (checker != knownFields.end())
+			errors += checker->second(*this, schema, entry.second, data);
 	}
+	return errors;
+}
 
-	const TValidatorMap & getKnownFieldsFor(JsonNode::JsonType type)
-	{
-		static const TValidatorMap commonFields = createCommonFields();
-		static const TValidatorMap numberFields = createNumberFields();
-		static const TValidatorMap stringFields = createStringFields();
-		static const TValidatorMap vectorFields = createVectorFields();
-		static const TValidatorMap structFields = createStructFields();
-
-		switch (type)
-		{
-			case JsonNode::JsonType::DATA_FLOAT:
-			case JsonNode::JsonType::DATA_INTEGER:
-				return numberFields;
-			case JsonNode::JsonType::DATA_STRING: return stringFields;
-			case JsonNode::JsonType::DATA_VECTOR: return vectorFields;
-			case JsonNode::JsonType::DATA_STRUCT: return structFields;
-			default: return commonFields;
-		}
-	}
+const JsonValidator::TValidatorMap & JsonValidator::getKnownFieldsFor(JsonNode::JsonType type)
+{
+	static const TValidatorMap commonFields = createCommonFields();
+	static const TValidatorMap numberFields = createNumberFields();
+	static const TValidatorMap stringFields = createStringFields();
+	static const TValidatorMap vectorFields = createVectorFields();
+	static const TValidatorMap structFields = createStructFields();
 
-	const TFormatMap & getKnownFormats()
+	switch (type)
 	{
-		static TFormatMap knownFormats = createFormatMap();
-		return knownFormats;
+		case JsonNode::JsonType::DATA_FLOAT:
+		case JsonNode::JsonType::DATA_INTEGER:
+			return numberFields;
+		case JsonNode::JsonType::DATA_STRING: return stringFields;
+		case JsonNode::JsonType::DATA_VECTOR: return vectorFields;
+		case JsonNode::JsonType::DATA_STRUCT: return structFields;
+		default: return commonFields;
 	}
+}
 
-} // Validation namespace
+const JsonValidator::TFormatMap & JsonValidator::getKnownFormats()
+{
+	static const TFormatMap knownFormats = createFormatMap();
+	return knownFormats;
+}
 
 VCMI_LIB_NAMESPACE_END

+ 13 - 19
lib/json/JsonValidator.h

@@ -13,28 +13,23 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-
-//Internal class for Json validation. Mostly compilant with json-schema v4 draft
-namespace Validation
+/// Class for Json validation. Mostly compilant with json-schema v6 draf
+struct JsonValidator
 {
-	/// struct used to pass data around during validation
-	struct ValidationData
-	{
-		/// path from root node to current one.
-		/// JsonNode is used as variant - either string (name of node) or as float (index in list)
-		std::vector<JsonNode> currentPath;
+	/// path from root node to current one.
+	/// JsonNode is used as variant - either string (name of node) or as float (index in list)
+	std::vector<JsonNode> currentPath;
 
-		/// Stack of used schemas. Last schema is the one used currently.
-		/// May contain multiple items in case if remote references were found
-		std::vector<std::string> usedSchemas;
+	/// Stack of used schemas. Last schema is the one used currently.
+	/// May contain multiple items in case if remote references were found
+	std::vector<std::string> usedSchemas;
 
-		/// generates error message
-		std::string makeErrorMessage(const std::string &message);
-	};
+	/// generates error message
+	std::string makeErrorMessage(const std::string &message);
 
 	using TFormatValidator = std::function<std::string(const JsonNode &)>;
 	using TFormatMap = std::unordered_map<std::string, TFormatValidator>;
-	using TFieldValidator = std::function<std::string(ValidationData &, const JsonNode &, const JsonNode &, const JsonNode &)>;
+	using TFieldValidator = std::function<std::string(JsonValidator &, const JsonNode &, const JsonNode &, const JsonNode &)>;
 	using TValidatorMap = std::unordered_map<std::string, TFieldValidator>;
 
 	/// map of known fields in schema
@@ -42,8 +37,7 @@ namespace Validation
 	const TFormatMap & getKnownFormats();
 
 	std::string check(const std::string & schemaName, const JsonNode & data);
-	std::string check(const std::string & schemaName, const JsonNode & data, ValidationData & validator);
-	std::string check(const JsonNode & schema, const JsonNode & data, ValidationData & validator);
-}
+	std::string check(const JsonNode & schema, const JsonNode & data);
+};
 
 VCMI_LIB_NAMESPACE_END

+ 37 - 37
lib/json/JsonWriter.cpp

@@ -16,31 +16,29 @@ VCMI_LIB_NAMESPACE_BEGIN
 template<typename Iterator>
 void JsonWriter::writeContainer(Iterator begin, Iterator end)
 {
-	if (begin == end)
+	if(begin == end)
 		return;
 
 	prefix += '\t';
 
 	writeEntry(begin++);
 
-	while (begin != end)
+	while(begin != end)
 	{
 		out << (compactMode ? ", " : ",\n");
 		writeEntry(begin++);
 	}
 
 	out << (compactMode ? "" : "\n");
-	prefix.resize(prefix.size()-1);
+	prefix.resize(prefix.size() - 1);
 }
 
 void JsonWriter::writeEntry(JsonMap::const_iterator entry)
 {
 	if(!compactMode)
 	{
-		if (!entry->second.meta.empty())
-			out << prefix << " // " << entry->second.meta << "\n";
-		if(!entry->second.flags.empty())
-			out << prefix << " // flags: " << boost::algorithm::join(entry->second.flags, ", ") << "\n";
+		if(!entry->second.getModScope().empty())
+			out << prefix << " // " << entry->second.getModScope() << "\n";
 		out << prefix;
 	}
 	writeString(entry->first);
@@ -52,30 +50,25 @@ void JsonWriter::writeEntry(JsonVector::const_iterator entry)
 {
 	if(!compactMode)
 	{
-		if (!entry->meta.empty())
-			out << prefix << " // " << entry->meta << "\n";
-		if(!entry->flags.empty())
-			out << prefix << " // flags: " << boost::algorithm::join(entry->flags, ", ") << "\n";
+		if(!entry->getModScope().empty())
+			out << prefix << " // " << entry->getModScope() << "\n";
 		out << prefix;
 	}
 	writeNode(*entry);
 }
 
-void JsonWriter::writeString(const std::string &string)
+void JsonWriter::writeString(const std::string & string)
 {
-	static const std::string escaped = "\"\\\b\f\n\r\t/";
+	static const std::string escaped = "\"\\\b\f\n\r\t";
+	static const std::array escapedCode = {'\"', '\\', 'b', 'f', 'n', 'r', 't'};
 
-	static const std::array<char, 8> escaped_code = {'\"', '\\', 'b', 'f', 'n', 'r', 't', '/'};
-
-	out <<'\"';
+	out << '\"';
 	size_t pos = 0;
 	size_t start = 0;
-	for (; pos<string.size(); pos++)
+	for(; pos < string.size(); pos++)
 	{
 		//we need to check if special character was been already escaped
-		if((string[pos] == '\\')
-			&& (pos+1 < string.size())
-			&& (std::find(escaped_code.begin(), escaped_code.end(), string[pos+1]) != escaped_code.end()) )
+		if((string[pos] == '\\') && (pos + 1 < string.size()) && (std::find(escapedCode.begin(), escapedCode.end(), string[pos + 1]) != escapedCode.end()))
 		{
 			pos++; //write unchanged, next simbol also checked
 		}
@@ -83,20 +76,19 @@ void JsonWriter::writeString(const std::string &string)
 		{
 			size_t escapedPos = escaped.find(string[pos]);
 
-			if (escapedPos != std::string::npos)
+			if(escapedPos != std::string::npos)
 			{
-				out.write(string.data()+start, pos - start);
-				out << '\\' << escaped_code[escapedPos];
-				start = pos+1;
+				out.write(string.data() + start, pos - start);
+				out << '\\' << escapedCode[escapedPos];
+				start = pos + 1;
 			}
 		}
-
 	}
-	out.write(string.data()+start, pos - start);
-	out <<'\"';
+	out.write(string.data() + start, pos - start);
+	out << '\"';
 }
 
-void JsonWriter::writeNode(const JsonNode &node)
+void JsonWriter::writeNode(const JsonNode & node)
 {
 	bool originalMode = compactMode;
 	if(compact && !compactMode && node.isCompact())
@@ -104,40 +96,48 @@ void JsonWriter::writeNode(const JsonNode &node)
 
 	switch(node.getType())
 	{
-		break; case JsonNode::JsonType::DATA_NULL:
+		case JsonNode::JsonType::DATA_NULL:
 			out << "null";
+			break;
 
-		break; case JsonNode::JsonType::DATA_BOOL:
-			if (node.Bool())
+		case JsonNode::JsonType::DATA_BOOL:
+			if(node.Bool())
 				out << "true";
 			else
 				out << "false";
+			break;
 
-		break; case JsonNode::JsonType::DATA_FLOAT:
+		case JsonNode::JsonType::DATA_FLOAT:
 			out << node.Float();
+			break;
 
-		break; case JsonNode::JsonType::DATA_STRING:
+		case JsonNode::JsonType::DATA_STRING:
 			writeString(node.String());
+			break;
 
-		break; case JsonNode::JsonType::DATA_VECTOR:
+		case JsonNode::JsonType::DATA_VECTOR:
 			out << "[" << (compactMode ? " " : "\n");
 			writeContainer(node.Vector().begin(), node.Vector().end());
 			out << (compactMode ? " " : prefix) << "]";
+			break;
 
-		break; case JsonNode::JsonType::DATA_STRUCT:
+		case JsonNode::JsonType::DATA_STRUCT:
 			out << "{" << (compactMode ? " " : "\n");
 			writeContainer(node.Struct().begin(), node.Struct().end());
 			out << (compactMode ? " " : prefix) << "}";
+			break;
 
-		break; case JsonNode::JsonType::DATA_INTEGER:
+		case JsonNode::JsonType::DATA_INTEGER:
 			out << node.Integer();
+			break;
 	}
 
 	compactMode = originalMode;
 }
 
 JsonWriter::JsonWriter(std::ostream & output, bool compact)
-	: out(output), compact(compact)
+	: out(output)
+	, compact(compact)
 {
 }
 

+ 1 - 0
lib/json/JsonWriter.h

@@ -22,6 +22,7 @@ class JsonWriter
 	bool compact;
 	//tracks whether we are currently using single-line format
 	bool compactMode = false;
+
 public:
 	template<typename Iterator>
 	void writeContainer(Iterator begin, Iterator end);

+ 1 - 1
lib/mapObjectConstructors/CBankInstanceConstructor.cpp

@@ -27,7 +27,7 @@ void CBankInstanceConstructor::initTypeData(const JsonNode & input)
 	if (input.Struct().count("name") == 0)
 		logMod->warn("Bank %s missing name!", getJsonKey());
 
-	VLC->generaltexth->registerString(input.meta, getNameTextID(), input["name"].String());
+	VLC->generaltexth->registerString(input.getModScope(), getNameTextID(), input["name"].String());
 
 	levels = input["levels"].Vector();
 	bankResetDuration = static_cast<si32>(input["resetDuration"].Float());

+ 5 - 5
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -259,21 +259,21 @@ std::unique_ptr<ObjectClass> CObjectClassesHandler::loadFromJson(const std::stri
 	{
 		if (!subData.second["index"].isNull())
 		{
-			const std::string & subMeta = subData.second["index"].meta;
+			const std::string & subMeta = subData.second["index"].getModScope();
 
 			if ( subMeta == "core")
 			{
 				size_t subIndex = subData.second["index"].Integer();
-				loadSubObject(subData.second.meta, subData.first, subData.second, obj.get(), subIndex);
+				loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get(), subIndex);
 			}
 			else
 			{
 				logMod->error("Object %s:%s.%s - attempt to load object with preset index! This option is reserved for built-in mod", subMeta, name, subData.first );
-				loadSubObject(subData.second.meta, subData.first, subData.second, obj.get());
+				loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get());
 			}
 		}
 		else
-			loadSubObject(subData.second.meta, subData.first, subData.second, obj.get());
+			loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get());
 	}
 
 	if (obj->id == MapObjectID::MONOLITH_TWO_WAY)
@@ -306,7 +306,7 @@ void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNo
 		objects.at(ID.getNum())->objects.resize(subID.getNum()+1);
 
 	JsonUtils::inherit(config, objects.at(ID.getNum())->base);
-	loadSubObject(config.meta, identifier, config, objects.at(ID.getNum()).get(), subID.getNum());
+	loadSubObject(config.getModScope(), identifier, config, objects.at(ID.getNum()).get(), subID.getNum());
 }
 
 void CObjectClassesHandler::removeSubObject(MapObjectID ID, MapObjectSubID subID)

+ 1 - 1
lib/mapObjectConstructors/CRewardableConstructor.cpp

@@ -22,7 +22,7 @@ void CRewardableConstructor::initTypeData(const JsonNode & config)
 	blockVisit = config["blockedVisitable"].Bool();
 
 	if (!config["name"].isNull())
-		VLC->generaltexth->registerString( config.meta, getNameTextID(), config["name"].String());
+		VLC->generaltexth->registerString( config.getModScope(), getNameTextID(), config["name"].String());
 	
 }
 

+ 1 - 1
lib/mapObjectConstructors/CommonConstructors.cpp

@@ -67,7 +67,7 @@ void CTownInstanceConstructor::initTypeData(const JsonNode & input)
 
 	// change scope of "filters" to scope of object that is being loaded
 	// since this filters require to resolve building ID's
-	filtersJson.setMeta(input["faction"].meta);
+	filtersJson.setModScope(input["faction"].getModScope());
 }
 
 void CTownInstanceConstructor::afterLoadFinalization()

+ 1 - 1
lib/mapObjectConstructors/DwellingInstanceConstructor.cpp

@@ -29,7 +29,7 @@ void DwellingInstanceConstructor::initTypeData(const JsonNode & input)
 	if (input.Struct().count("name") == 0)
 		logMod->warn("Dwelling %s missing name!", getJsonKey());
 
-	VLC->generaltexth->registerString( input.meta, getNameTextID(), input["name"].String());
+	VLC->generaltexth->registerString( input.getModScope(), getNameTextID(), input["name"].String());
 
 	const JsonVector & levels = input["creatures"].Vector();
 	const auto totalLevels = levels.size();

+ 3 - 6
lib/mapObjects/MiscObjects.cpp

@@ -220,13 +220,10 @@ void CGMine::serializeJsonOptions(JsonSerializeFormat & handler)
 	{
 		if(handler.saving)
 		{
-			JsonNode node(JsonNode::JsonType::DATA_VECTOR);
+			JsonNode node;
 			for(const auto & resID : abandonedMineResources)
-			{
-				JsonNode one(JsonNode::JsonType::DATA_STRING);
-				one.String() = GameConstants::RESOURCE_NAMES[resID.getNum()];
-				node.Vector().push_back(one);
-			}
+				node.Vector().emplace_back(GameConstants::RESOURCE_NAMES[resID.getNum()]);
+
 			handler.serializeRaw("possibleResources", node, std::nullopt);
 		}
 		else

+ 3 - 9
lib/mapObjects/ObjectTemplate.cpp

@@ -354,11 +354,7 @@ void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const
 			JsonVector & data = node["allowedTerrains"].Vector();
 
 			for(auto type : allowedTerrains)
-			{
-				JsonNode value(JsonNode::JsonType::DATA_STRING);
-				value.String() = VLC->terrainTypeHandler->getById(type)->getJsonKey();
-				data.push_back(value);
-			}
+				data.emplace_back(VLC->terrainTypeHandler->getById(type)->getJsonKey());
 		}
 	}
 
@@ -398,13 +394,11 @@ void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const
 
 	for(size_t i=0; i < height; i++)
 	{
-		JsonNode lineNode(JsonNode::JsonType::DATA_STRING);
-
-		std::string & line = lineNode.String();
+		std::string line;
 		line.resize(width);
 		for(size_t j=0; j < width; j++)
 			line[j] = tileToChar(usedTiles[height - 1 - i][width - 1 - j]);
-		mask.push_back(lineNode);
+		mask.emplace_back(line);
 	}
 
 	if(printPriority != 0)

+ 1 - 1
lib/mapping/CMapService.cpp

@@ -171,7 +171,7 @@ static JsonNode loadPatches(const std::string & path)
 	for (auto & entry : node.Struct())
 		JsonUtils::validate(entry.second, "vcmi:mapHeader", "patch for " + entry.first);
 
-	node.setMeta(ModScope::scopeMap());
+	node.setModScope(ModScope::scopeMap());
 	return node;
 }
 

+ 5 - 5
lib/mapping/MapFormatH3M.cpp

@@ -1153,7 +1153,7 @@ CGObjectInstance * CMapLoaderH3M::readWitchHut(const int3 & position, std::share
 				variable["anyOf"].Vector() = anyOfList;
 			}
 
-			variable.setMeta(ModScope::scopeGame()); // list may include skills from all mods
+			variable.setModScope(ModScope::scopeGame()); // list may include skills from all mods
 			rewardable->configuration.presetVariable("secondarySkill", "gainedSkill", variable);
 		}
 		else
@@ -1189,7 +1189,7 @@ CGObjectInstance * CMapLoaderH3M::readScholar(const int3 & position, std::shared
 				JsonNode variable;
 				JsonNode dice;
 				variable.String() = NPrimarySkill::names[bonusID];
-				variable.setMeta(ModScope::scopeGame());
+				variable.setModScope(ModScope::scopeGame());
 				dice.Integer() = 80;
 				rewardable->configuration.presetVariable("primarySkill", "gainedStat", variable);
 				rewardable->configuration.presetVariable("dice", "0", dice);
@@ -1200,7 +1200,7 @@ CGObjectInstance * CMapLoaderH3M::readScholar(const int3 & position, std::shared
 				JsonNode variable;
 				JsonNode dice;
 				variable.String() = VLC->skills()->getByIndex(bonusID)->getJsonKey();
-				variable.setMeta(ModScope::scopeGame());
+				variable.setModScope(ModScope::scopeGame());
 				dice.Integer() = 50;
 				rewardable->configuration.presetVariable("secondarySkill", "gainedSkill", variable);
 				rewardable->configuration.presetVariable("dice", "0", dice);
@@ -1211,7 +1211,7 @@ CGObjectInstance * CMapLoaderH3M::readScholar(const int3 & position, std::shared
 				JsonNode variable;
 				JsonNode dice;
 				variable.String() = VLC->spells()->getByIndex(bonusID)->getJsonKey();
-				variable.setMeta(ModScope::scopeGame());
+				variable.setModScope(ModScope::scopeGame());
 				dice.Integer() = 20;
 				rewardable->configuration.presetVariable("spell", "gainedSpell", variable);
 				rewardable->configuration.presetVariable("dice", "0", dice);
@@ -1356,7 +1356,7 @@ CGObjectInstance * CMapLoaderH3M::readShrine(const int3 & position, std::shared_
 		{
 			JsonNode variable;
 			variable.String() = VLC->spells()->getById(spell)->getJsonKey();
-			variable.setMeta(ModScope::scopeGame()); // list may include spells from all mods
+			variable.setModScope(ModScope::scopeGame()); // list may include spells from all mods
 			rewardable->configuration.presetVariable("spell", "gainedSpell", variable);
 		}
 	}

+ 10 - 13
lib/mapping/MapFormatJson.cpp

@@ -545,13 +545,10 @@ void CMapFormatJson::writeTeams(JsonSerializer & handler)
 
 		for(const std::set<PlayerColor> & teamData : teamsData)
 		{
-			JsonNode team(JsonNode::JsonType::DATA_VECTOR);
+			JsonNode team;
 			for(const PlayerColor & player : teamData)
-			{
-				JsonNode member(JsonNode::JsonType::DATA_STRING);
-				member.String() = GameConstants::PLAYER_COLOR_NAMES[player.getNum()];
-				team.Vector().push_back(std::move(member));
-			}
+				team.Vector().emplace_back(GameConstants::PLAYER_COLOR_NAMES[player.getNum()]);
+
 			dest.Vector().push_back(std::move(team));
 		}
 		handler.serializeRaw("teams", dest, std::nullopt);
@@ -586,7 +583,7 @@ void CMapFormatJson::readTriggeredEvent(TriggeredEvent & event, const JsonNode &
 
 void CMapFormatJson::writeTriggeredEvents(JsonSerializer & handler)
 {
-	JsonNode triggeredEvents(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode triggeredEvents;
 
 	for(const auto & event : mapHeader->triggeredEvents)
 		writeTriggeredEvent(event, triggeredEvents[event.identifier]);
@@ -657,7 +654,7 @@ void CMapFormatJson::writeDisposedHeroes(JsonSerializeFormat & handler)
 
 		auto definition = definitions->enterStruct(type);
 
-		JsonNode players(JsonNode::JsonType::DATA_VECTOR);
+		JsonNode players;
 		definition->serializeIdArray("availableFor", hero.players);
 	}
 }
@@ -812,7 +809,7 @@ JsonNode CMapLoaderJson::getFromArchive(const std::string & archiveFilename)
 
 	auto data = loader.load(resource)->readAll();
 
-	JsonNode res(reinterpret_cast<char*>(data.first.get()), data.second);
+	JsonNode res(reinterpret_cast<const std::byte*>(data.first.get()), data.second);
 
 	return res;
 }
@@ -1049,7 +1046,7 @@ void CMapLoaderJson::MapObjectLoader::construct()
 	if(typeName.empty())
 	{
 		logGlobal->error("Object type missing");
-		logGlobal->debug(configuration.toJson());
+		logGlobal->debug(configuration.toString());
 		return;
 	}
 
@@ -1069,7 +1066,7 @@ void CMapLoaderJson::MapObjectLoader::construct()
 	else if(subtypeName.empty())
 	{
 		logGlobal->error("Object subtype missing");
-		logGlobal->debug(configuration.toJson());
+		logGlobal->debug(configuration.toString());
 		return;
 	}
 
@@ -1329,7 +1326,7 @@ void CMapSaverJson::writeTerrain()
 void CMapSaverJson::writeObjects()
 {
 	logGlobal->trace("Saving objects");
-	JsonNode data(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode data;
 
 	JsonSerializer handler(mapObjectResolver.get(), data);
 
@@ -1343,7 +1340,7 @@ void CMapSaverJson::writeObjects()
 
 	if(map->grailPos.valid())
 	{
-		JsonNode grail(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode grail;
 		grail["type"].String() = "grail";
 
 		grail["x"].Float() = map->grailPos.x;

+ 5 - 5
lib/mapping/MapIdentifiersH3M.cpp

@@ -28,7 +28,7 @@ void MapIdentifiersH3M::loadMapping(std::map<IdentifierID, IdentifierID> & resul
 	for (auto entry : mapping.Struct())
 	{
 		IdentifierID sourceID (entry.second.Integer());
-		IdentifierID targetID (*VLC->identifiers()->getIdentifier(entry.second.meta, identifierName, entry.first));
+		IdentifierID targetID (*VLC->identifiers()->getIdentifier(entry.second.getModScope(), identifierName, entry.first));
 
 		result[sourceID] = targetID;
 	}
@@ -41,13 +41,13 @@ void MapIdentifiersH3M::loadMapping(const JsonNode & mapping)
 
 	for (auto entryFaction : mapping["buildings"].Struct())
 	{
-		FactionID factionID (*VLC->identifiers()->getIdentifier(entryFaction.second.meta, "faction", entryFaction.first));
+		FactionID factionID (*VLC->identifiers()->getIdentifier(entryFaction.second.getModScope(), "faction", entryFaction.first));
 		auto buildingMap = entryFaction.second;
 
 		for (auto entryBuilding : buildingMap.Struct())
 		{
 			BuildingID sourceID (entryBuilding.second.Integer());
-			BuildingID targetID (*VLC->identifiers()->getIdentifier(entryBuilding.second.meta, "building." + VLC->factions()->getById(factionID)->getJsonKey(), entryBuilding.first));
+			BuildingID targetID (*VLC->identifiers()->getIdentifier(entryBuilding.second.getModScope(), "building." + VLC->factions()->getById(factionID)->getJsonKey(), entryBuilding.first));
 
 			mappingFactionBuilding[factionID][sourceID] = targetID;
 		}
@@ -70,7 +70,7 @@ void MapIdentifiersH3M::loadMapping(const JsonNode & mapping)
 		{
 			for (auto entryInner : entryOuter.second.Struct())
 			{
-				auto handler = VLC->objtypeh->getHandlerFor( entryInner.second.meta, entryOuter.first, entryInner.first);
+				auto handler = VLC->objtypeh->getHandlerFor( entryInner.second.getModScope(), entryOuter.first, entryInner.first);
 
 				auto entryValues = entryInner.second.Vector();
 				ObjectTypeIdentifier h3mID{Obj(entryValues[0].Integer()), int32_t(entryValues[1].Integer())};
@@ -80,7 +80,7 @@ void MapIdentifiersH3M::loadMapping(const JsonNode & mapping)
 		}
 		else
 		{
-			auto handler = VLC->objtypeh->getHandlerFor( entryOuter.second.meta, entryOuter.first, entryOuter.first);
+			auto handler = VLC->objtypeh->getHandlerFor( entryOuter.second.getModScope(), entryOuter.first, entryOuter.first);
 
 			auto entryValues = entryOuter.second.Vector();
 			ObjectTypeIdentifier h3mID{Obj(entryValues[0].Integer()), int32_t(entryValues[1].Integer())};

+ 5 - 2
lib/modding/CModHandler.cpp

@@ -226,7 +226,10 @@ void CModHandler::loadOneMod(std::string modName, const std::string & parent, co
 
 	if(CResourceHandler::get("initial")->existsResource(CModInfo::getModFile(modFullName)))
 	{
-		CModInfo mod(modFullName, modSettings[modName], JsonNode(CModInfo::getModFile(modFullName)));
+		JsonParsingSettings settings;
+		settings.mode = JsonParsingSettings::JsonFormatMode::JSON; // TODO: remove once Android launcher with its strict parser is gone
+
+		CModInfo mod(modFullName, modSettings[modName], JsonNode(CModInfo::getModFile(modFullName), settings));
 		if (!parent.empty()) // this is submod, add parent to dependencies
 			mod.dependencies.insert(parent);
 
@@ -512,7 +515,7 @@ void CModHandler::afterLoad(bool onlyEssential)
 	if(!onlyEssential)
 	{
 		std::fstream file(CResourceHandler::get()->getResourceName(ResourcePath("config/modSettings.json"))->c_str(), std::ofstream::out | std::ofstream::trunc);
-		file << modSettings.toJson();
+		file << modSettings.toString();
 	}
 }
 

+ 1 - 1
lib/modding/CModInfo.cpp

@@ -18,7 +18,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 static JsonNode addMeta(JsonNode config, const std::string & meta)
 {
-	config.setMeta(meta);
+	config.setModScope(meta);
 	return config;
 }
 

+ 5 - 5
lib/modding/ContentTypeHandler.cpp

@@ -45,7 +45,7 @@ ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, const std::string
 {
 	for(auto & node : originalData)
 	{
-		node.setMeta(ModScope::scopeBuiltin());
+		node.setModScope(ModScope::scopeBuiltin());
 	}
 }
 
@@ -53,7 +53,7 @@ bool ContentTypeHandler::preloadModData(const std::string & modName, const std::
 {
 	bool result = false;
 	JsonNode data = JsonUtils::assembleFromFiles(fileList, result);
-	data.setMeta(modName);
+	data.setModScope(modName);
 
 	ModInfo & modInfo = modData[modName];
 
@@ -104,7 +104,7 @@ bool ContentTypeHandler::loadMod(const std::string & modName, bool validate)
 		const std::string & name = entry.first;
 		JsonNode & data = entry.second;
 
-		if (data.meta != modName)
+		if (data.getModScope() != modName)
 		{
 			// in this scenario, entire object record comes from another mod
 			// normally, this is used to "patch" object from another mod (which is legal)
@@ -112,7 +112,7 @@ bool ContentTypeHandler::loadMod(const std::string & modName, bool validate)
 			// - another mod attempts to add object into this mod (technically can be supported, but might lead to weird edge cases)
 			// - another mod attempts to edit object from this mod that no longer exist - DANGER since such patch likely has very incomplete data
 			// so emit warning and skip such case
-			logMod->warn("Mod '%s' attempts to edit object '%s' of type '%s' from mod '%s' but no such object exist!", data.meta, name, objectName, modName);
+			logMod->warn("Mod '%s' attempts to edit object '%s' of type '%s' from mod '%s' but no such object exist!", data.getModScope(), name, objectName, modName);
 			continue;
 		}
 
@@ -163,7 +163,7 @@ void ContentTypeHandler::afterLoadFinalization()
 		if (data.second.modData.isNull())
 		{
 			for (auto node : data.second.patches.Struct())
-				logMod->warn("Mod '%s' have added patch for object '%s' from mod '%s', but this mod was not loaded or has no new objects.", node.second.meta, node.first, data.first);
+				logMod->warn("Mod '%s' have added patch for object '%s' from mod '%s', but this mod was not loaded or has no new objects.", node.second.getModScope(), node.first, data.first);
 		}
 
 		for(auto & otherMod : modData)

+ 5 - 5
lib/modding/IdentifierStorage.cpp

@@ -180,12 +180,12 @@ void CIdentifierStorage::requestIdentifier(const std::string & scope, const std:
 
 void CIdentifierStorage::requestIdentifier(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const
 {
-	requestIdentifier(ObjectCallback::fromNameAndType(name.meta, type, name.String(), callback, false));
+	requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, false));
 }
 
 void CIdentifierStorage::requestIdentifier(const JsonNode & name, const std::function<void(si32)> & callback) const
 {
-	requestIdentifier(ObjectCallback::fromNameWithType(name.meta, name.String(), callback, false));
+	requestIdentifier(ObjectCallback::fromNameWithType(name.getModScope(), name.String(), callback, false));
 }
 
 void CIdentifierStorage::tryRequestIdentifier(const std::string & scope, const std::string & type, const std::string & name, const std::function<void(si32)> & callback) const
@@ -195,7 +195,7 @@ void CIdentifierStorage::tryRequestIdentifier(const std::string & scope, const s
 
 void CIdentifierStorage::tryRequestIdentifier(const std::string & type, const JsonNode & name, const std::function<void(si32)> & callback) const
 {
-	requestIdentifier(ObjectCallback::fromNameAndType(name.meta, type, name.String(), callback, true));
+	requestIdentifier(ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), callback, true));
 }
 
 std::optional<si32> CIdentifierStorage::getIdentifier(const std::string & scope, const std::string & type, const std::string & name, bool silent) const
@@ -210,7 +210,7 @@ std::optional<si32> CIdentifierStorage::getIdentifier(const std::string & type,
 {
 	assert(state != ELoadingState::LOADING);
 
-	auto options = ObjectCallback::fromNameAndType(name.meta, type, name.String(), std::function<void(si32)>(), silent);
+	auto options = ObjectCallback::fromNameAndType(name.getModScope(), type, name.String(), std::function<void(si32)>(), silent);
 
 	return getIdentifierImpl(options, silent);
 }
@@ -219,7 +219,7 @@ std::optional<si32> CIdentifierStorage::getIdentifier(const JsonNode & name, boo
 {
 	assert(state != ELoadingState::LOADING);
 
-	auto options = ObjectCallback::fromNameWithType(name.meta, name.String(), std::function<void(si32)>(), silent);
+	auto options = ObjectCallback::fromNameWithType(name.getModScope(), name.String(), std::function<void(si32)>(), silent);
 	return getIdentifierImpl(options, silent);
 }
 

+ 3 - 3
lib/rewardable/Info.cpp

@@ -75,7 +75,7 @@ void Rewardable::Info::init(const JsonNode & objectConfig, const std::string & o
 
 	auto loadString = [&](const JsonNode & entry, const TextIdentifier & textID){
 		if (entry.isString() && !entry.String().empty() && entry.String()[0] != '@')
-			VLC->generaltexth->registerString(entry.meta, textID, entry.String());
+			VLC->generaltexth->registerString(entry.getModScope(), textID, entry.String());
 	};
 
 	parameters = objectConfig;
@@ -201,8 +201,8 @@ void Rewardable::Info::configureReward(Rewardable::Configuration & object, CRand
 
 	for ( auto node : source["changeCreatures"].Struct() )
 	{
-		CreatureID from(VLC->identifiers()->getIdentifier(node.second.meta, "creature", node.first).value());
-		CreatureID dest(VLC->identifiers()->getIdentifier(node.second.meta, "creature", node.second.String()).value());
+		CreatureID from(VLC->identifiers()->getIdentifier(node.second.getModScope(), "creature", node.first).value());
+		CreatureID dest(VLC->identifiers()->getIdentifier(node.second.getModScope(), "creature", node.second.String()).value());
 
 		reward.extraComponents.emplace_back(ComponentType::CREATURE, dest);
 

+ 3 - 2
lib/serializer/ESerializationVersion.h

@@ -35,7 +35,8 @@ enum class ESerializationVersion : int32_t
 	RELEASE_143, // 832 +text container in campaigns, +starting hero in RMG options
 	HAS_EXTRA_OPTIONS, // 833 +extra options struct as part of startinfo
 	DESTROYED_OBJECTS, // 834 +list of objects destroyed by player
-	CAMPAIGN_MAP_TRANSLATIONS,
+	CAMPAIGN_MAP_TRANSLATIONS, // 835 +campaigns include translations for its maps
+	JSON_FLAGS, // 836 json uses new format for flags
 
-	CURRENT = CAMPAIGN_MAP_TRANSLATIONS
+	CURRENT = JSON_FLAGS
 };

+ 1 - 1
lib/serializer/JsonDeserializer.cpp

@@ -43,7 +43,7 @@ void JsonDeserializer::serializeInternal(const std::string & fieldName, si32 & v
 		if(rawId < 0) //may be, user has installed the mod into another directory...
 		{
 			auto internalId = vstd::splitStringToPair(identifier, ':').second;
-			auto currentScope = getCurrent().meta;
+			auto currentScope = getCurrent().getModScope();
 			auto actualId = currentScope.length() > 0 ? currentScope + ":" + internalId : internalId;
 
 			rawId = decoder(actualId);

+ 3 - 15
lib/serializer/JsonSerializer.cpp

@@ -60,11 +60,7 @@ void JsonSerializer::serializeInternal(const std::string & fieldName, std::vecto
 	data.reserve(value.size());
 
 	for(const si32 rawId : value)
-	{
-		JsonNode jsonElement(JsonNode::JsonType::DATA_STRING);
-		jsonElement.String() = encoder(rawId);
-		data.push_back(std::move(jsonElement));
-	}
+		data.emplace_back(rawId);
 }
 
 void JsonSerializer::serializeInternal(const std::string & fieldName, std::vector<std::string> & value)
@@ -76,11 +72,7 @@ void JsonSerializer::serializeInternal(const std::string & fieldName, std::vecto
 	data.reserve(value.size());
 
 	for(const auto & rawId : value)
-	{
-		JsonNode jsonElement(JsonNode::JsonType::DATA_STRING);
-		jsonElement.String() = rawId;
-		data.push_back(std::move(jsonElement));
-	}
+		data.emplace_back(rawId);
 }
 
 void JsonSerializer::serializeInternal(std::string & value)
@@ -183,11 +175,7 @@ void JsonSerializer::writeLICPartBuffer(const std::string & fieldName, const std
 		auto & target = currentObject->operator[](fieldName)[partName].Vector();
 
 		for(auto & s : buffer)
-		{
-			JsonNode val(JsonNode::JsonType::DATA_STRING);
-			std::swap(val.String(), s);
-			target.push_back(std::move(val));
-		}
+			target.emplace_back(s);
 	}
 }
 

+ 4 - 4
lib/spells/CSpellHandler.cpp

@@ -582,7 +582,7 @@ std::vector<JsonNode> CSpellHandler::loadLegacyData()
 	{
 		do
 		{
-			JsonNode lineNode(JsonNode::JsonType::DATA_STRUCT);
+			JsonNode lineNode;
 
 			const auto id = legacyData.size();
 
@@ -719,7 +719,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 	{
 		const int chance = static_cast<int>(node.second.Integer());
 
-		VLC->identifiers()->requestIdentifier(node.second.meta, "faction", node.first, [=](si32 factionID)
+		VLC->identifiers()->requestIdentifier(node.second.getModScope(), "faction", node.first, [=](si32 factionID)
 		{
 			spell->probabilities[FactionID(factionID)] = chance;
 		});
@@ -742,7 +742,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 	{
 		if(counteredSpell.second.Bool())
 		{
-			VLC->identifiers()->requestIdentifier(counteredSpell.second.meta, "spell", counteredSpell.first, [=](si32 id)
+			VLC->identifiers()->requestIdentifier(counteredSpell.second.getModScope(), "spell", counteredSpell.first, [=](si32 id)
 			{
 				spell->counteredSpells.emplace_back(id);
 			});
@@ -833,7 +833,7 @@ CSpell * CSpellHandler::loadFromJson(const std::string & scope, const JsonNode &
 		{
 			logMod->warn("Spell %s has old target condition format. Expected configuration: ", spell->getNameTranslated());
 			spell->targetCondition = spell->convertTargetCondition(immunities, absoluteImmunities, limiters, absoluteLimiters);
-			logMod->warn("\n\"targetCondition\" : %s", spell->targetCondition.toJson());
+			logMod->warn("\n\"targetCondition\" : %s", spell->targetCondition.toString());
 		}
 	}
 	else

+ 1 - 1
lib/spells/TargetCondition.cpp

@@ -544,7 +544,7 @@ void TargetCondition::loadConditions(const JsonNode & source, bool exclusive, bo
 
 			ModUtility::parseIdentifier(keyValue.first, scope, type, identifier);
 
-			item = itemFactory->createConfigurable(keyValue.second.meta, type, identifier);
+			item = itemFactory->createConfigurable(keyValue.second.getModScope(), type, identifier);
 		}
 
 		if(item)

+ 2 - 0
lib/spells/effects/Moat.h

@@ -14,6 +14,8 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+struct Bonus;
+
 namespace spells
 {
 namespace effects

+ 1 - 1
lobby/LobbyServer.cpp

@@ -58,7 +58,7 @@ NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID) c
 
 void LobbyServer::sendMessage(const NetworkConnectionPtr & target, const JsonNode & json)
 {
-	target->sendPacket(json.toBytes(true));
+	target->sendPacket(json.toBytes());
 }
 
 void LobbyServer::sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie)

+ 4 - 4
mapeditor/Animation.cpp

@@ -599,7 +599,7 @@ void Animation::init()
 		std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
 		stream->read(textData.get(), stream->getSize());
 
-		const JsonNode config((char*)textData.get(), stream->getSize());
+		const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize());
 
 		initFromJson(config);
 	}
@@ -610,7 +610,7 @@ void Animation::initFromJson(const JsonNode & config)
 	std::string basepath;
 	basepath = config["basepath"].String();
 
-	JsonNode base(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode base;
 	base["margins"] = config["margins"];
 	base["width"] = config["width"];
 	base["height"] = config["height"];
@@ -622,7 +622,7 @@ void Animation::initFromJson(const JsonNode & config)
 
 		for(const JsonNode & frame : group["frames"].Vector())
 		{
-			JsonNode toAdd(JsonNode::JsonType::DATA_STRUCT);
+			JsonNode toAdd;
 			JsonUtils::inherit(toAdd, base);
 			toAdd["file"].String() = basepath + frame.String();
 			source[groupID].push_back(toAdd);
@@ -637,7 +637,7 @@ void Animation::initFromJson(const JsonNode & config)
 		if (source[group].size() <= frame)
 			source[group].resize(frame+1);
 
-		JsonNode toAdd(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode toAdd;
 		JsonUtils::inherit(toAdd, base);
 		toAdd["file"].String() = basepath + node["file"].String();
 		source[group][frame] = toAdd;

+ 2 - 2
mapeditor/jsonutils.cpp

@@ -96,7 +96,7 @@ QVariant JsonFromFile(QString filename)
 	}
 	else
 	{
-		JsonNode node(data.data(), data.size());
+		JsonNode node(reinterpret_cast<const std::byte*>(data.data()), data.size());
 		return toVariant(node);
 	}
 }
@@ -122,7 +122,7 @@ JsonNode toJson(QVariant object)
 void JsonToFile(QString filename, QVariant object)
 {
 	std::fstream file(qstringToPath(filename).c_str(), std::ios::out | std::ios_base::binary);
-	file << toJson(object).toJson();
+	file << toJson(object).toString();
 }
 
 }

+ 1 - 1
mapeditor/mainwindow.cpp

@@ -181,7 +181,7 @@ MainWindow::MainWindow(QWidget* parent) :
 
 	// Initialize logging based on settings
 	logConfig->configure();
-	logGlobal->debug("settings = %s", settings.toJsonNode().toJson());
+	logGlobal->debug("settings = %s", settings.toJsonNode().toString());
 
 	// Some basic data validation to produce better error messages in cases of incorrect install
 	auto testFile = [](std::string filename, std::string message) -> bool

+ 3 - 3
mapeditor/mapsettings/translations.cpp

@@ -24,7 +24,7 @@ void Translations::cleanupRemovedItems(CMap & map)
 	
 	for(auto & translations : map.translations.Struct())
 	{
-		auto updateTranslations = JsonNode(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode updateTranslations;
 		for(auto & s : translations.second.Struct())
 		{
 			for(auto part : QString::fromStdString(s.first).split('.'))
@@ -44,7 +44,7 @@ void Translations::cleanupRemovedItems(CMap & map, const std::string & pattern)
 {
 	for(auto & translations : map.translations.Struct())
 	{
-		auto updateTranslations = JsonNode(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode updateTranslations;
 		for(auto & s : translations.second.Struct())
 		{
 			if(s.first.find(pattern) == std::string::npos)
@@ -171,7 +171,7 @@ void Translations::on_supportedCheck_toggled(bool checked)
 		}
 		ui->translationsTable->blockSignals(true);
 		ui->translationsTable->setRowCount(0);
-		translation = JsonNode(JsonNode::JsonType::DATA_NULL);
+		translation.clear();
 		ui->translationsTable->blockSignals(false);
 		ui->translationsTable->setEnabled(false);
 	}

+ 2 - 2
scripting/lua/LuaScriptingContext.cpp

@@ -347,8 +347,8 @@ void LuaContext::pop(JsonNode & value)
 		break;
 	case LUA_TTABLE:
 		{
-			JsonNode asVector(JsonNode::JsonType::DATA_VECTOR);
-			JsonNode asStruct(JsonNode::JsonType::DATA_STRUCT);
+			JsonNode asVector;
+			JsonNode asStruct;
 
 			lua_pushnil(L);  /* first key */
 

+ 8 - 8
scripting/lua/LuaSpellEffect.cpp

@@ -76,7 +76,7 @@ bool LuaSpellEffect::applicable(Problem & problem, const Mechanics * m) const
 	if(response.getType() != JsonNode::JsonType::DATA_BOOL)
 	{
 		logMod->error("Invalid API response from script %s.", script->getName());
-		logMod->debug(response.toJson(true));
+		logMod->debug(response.toCompactString());
 		return false;
 	}
 	return response.Bool();
@@ -98,12 +98,12 @@ bool LuaSpellEffect::applicable(Problem & problem, const Mechanics * m, const Ef
 	for(const auto & dest : target)
 	{
 		JsonNode targetData;
-		targetData.Vector().push_back(JsonUtils::intNode(dest.hexValue.hex));
+		targetData.Vector().emplace_back(dest.hexValue.hex);
 
 		if(dest.unitValue)
-			targetData.Vector().push_back(JsonUtils::intNode(dest.unitValue->unitId()));
+			targetData.Vector().emplace_back(dest.unitValue->unitId());
 		else
-			targetData.Vector().push_back(JsonUtils::intNode(-1));
+			targetData.Vector().emplace_back(-1);
 
 		requestP.Vector().push_back(targetData);
 	}
@@ -116,7 +116,7 @@ bool LuaSpellEffect::applicable(Problem & problem, const Mechanics * m, const Ef
 	if(response.getType() != JsonNode::JsonType::DATA_BOOL)
 	{
 		logMod->error("Invalid API response from script %s.", script->getName());
-		logMod->debug(response.toJson(true));
+		logMod->debug(response.toCompactString());
 		return false;
 	}
 	return response.Bool();
@@ -141,12 +141,12 @@ void LuaSpellEffect::apply(ServerCallback * server, const Mechanics * m, const E
 	for(const auto & dest : target)
 	{
 		JsonNode targetData;
-		targetData.Vector().push_back(JsonUtils::intNode(dest.hexValue.hex));
+		targetData.Vector().emplace_back(dest.hexValue.hex);
 
 		if(dest.unitValue)
-			targetData.Vector().push_back(JsonUtils::intNode(dest.unitValue->unitId()));
+			targetData.Vector().emplace_back(dest.unitValue->unitId());
 		else
-			targetData.Vector().push_back(JsonUtils::intNode(-1));
+			targetData.Vector().emplace_back(-1);
 
 		requestP.Vector().push_back(targetData);
 	}

+ 2 - 2
scripting/lua/LuaStack.cpp

@@ -183,8 +183,8 @@ bool LuaStack::tryGet(int position, JsonNode & value)
 		return tryGet(position, value.String());
 	case LUA_TTABLE:
 		{
-			JsonNode asVector(JsonNode::JsonType::DATA_VECTOR);
-			JsonNode asStruct(JsonNode::JsonType::DATA_STRUCT);
+			JsonNode asVector;
+			JsonNode asStruct;
 
 			lua_pushnil(L);  /* first key */
 

+ 3 - 3
server/GlobalLobbyProcessor.cpp

@@ -45,7 +45,7 @@ void GlobalLobbyProcessor::onDisconnected(const std::shared_ptr<INetworkConnecti
 					JsonNode message;
 					message["type"].String() = "leaveGameRoom";
 					message["accountID"].String() = proxy.first;
-					controlConnection->sendPacket(message.toBytes(true));
+					controlConnection->sendPacket(message.toBytes());
 					break;
 				}
 			}
@@ -122,7 +122,7 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr<INetwor
 		toSend["gameRoomID"].String() = owner.uuid;
 		toSend["accountID"] = settings["lobby"]["accountID"];
 		toSend["accountCookie"] = settings["lobby"]["accountCookie"];
-		connection->sendPacket(toSend.toBytes(true));
+		connection->sendPacket(toSend.toBytes());
 	}
 	else
 	{
@@ -137,7 +137,7 @@ void GlobalLobbyProcessor::onConnectionEstablished(const std::shared_ptr<INetwor
 		toSend["gameRoomID"].String() = owner.uuid;
 		toSend["guestAccountID"].String() = guestAccountID;
 		toSend["accountCookie"] = settings["lobby"]["accountCookie"];
-		connection->sendPacket(toSend.toBytes(true));
+		connection->sendPacket(toSend.toBytes());
 
 		proxyConnections[guestAccountID] = connection;
 		owner.onNewConnection(connection);

+ 1 - 1
test/JsonComparer.cpp

@@ -165,7 +165,7 @@ void JsonComparer::checkEqualJson(const JsonNode & actual, const JsonNode & expe
 	}
 	else
 	{
-		check(false, "type mismatch. \n expected:\n"+expected.toJson(true)+"\n actual:\n" +actual.toJson(true));
+		check(false, "type mismatch. \n expected:\n"+expected.toCompactString()+"\n actual:\n" +actual.toCompactString());
 	}
 }
 

+ 3 - 3
test/entity/CCreatureTest.cpp

@@ -49,7 +49,7 @@ TEST_F(CCreatureTest, RegistersIcons)
 
 TEST_F(CCreatureTest, DISABLED_JsonUpdate)
 {
-	JsonNode data(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode data;
 
 	JsonNode & config = data["config"];
 	config["cost"]["gold"].Integer() = 750;
@@ -106,7 +106,7 @@ TEST_F(CCreatureTest, DISABLED_JsonUpdate)
 
 TEST_F(CCreatureTest, DISABLED_JsonAddBonus)
 {
-	JsonNode data(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode data;
 
 	auto b = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::BLOCKS_RETALIATION, BonusSource::CREATURE_ABILITY, 17, BonusSourceID(CreatureID(42)), BonusSubtypeID(CreatureID(43)), BonusValueType::BASE_NUMBER);
 
@@ -132,7 +132,7 @@ TEST_F(CCreatureTest, DISABLED_JsonAddBonus)
 
 TEST_F(CCreatureTest, DISABLED_JsonRemoveBonus)
 {
-	JsonNode data(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode data;
 
 	auto b1 = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::BLOCKS_RETALIATION, BonusSource::CREATURE_ABILITY, 17, BonusSourceID(CreatureID(42)), BonusSubtypeID(CreatureID(43)), BonusValueType::BASE_NUMBER);
 	subject->addNewBonus(b1);

+ 1 - 1
test/game/CGameStateTest.cpp

@@ -420,6 +420,6 @@ TEST_F(CGameStateTest, updateEntity)
 
 	JsonNode actual;
 	EXPECT_CALL(services, updateEntity(Eq(Metatype::CREATURE), Eq(424242), _)).WillOnce(SaveArg<2>(&actual));
-	gameState->updateEntity(Metatype::CREATURE, 424242, JsonUtils::stringNode("TEST"));
+	gameState->updateEntity(Metatype::CREATURE, 424242, JsonNode("TEST"));
 	EXPECT_EQ(actual.String(), "TEST");
 }

+ 2 - 2
test/map/CMapFormatTest.cpp

@@ -95,14 +95,14 @@ static JsonNode getFromArchive(CZipLoader & archive, const std::string & archive
 
 	auto data = archive.load(resource)->readAll();
 
-	JsonNode res(reinterpret_cast<char*>(data.first.get()), data.second);
+	JsonNode res(reinterpret_cast<const std::byte *>(data.first.get()), data.second);
 
 	return res;
 }
 
 static void addToArchive(CZipSaver & saver, const JsonNode & data, const std::string & filename)
 {
-	auto s = data.toJson();
+	auto s = data.toString();
 	std::unique_ptr<COutputStream> stream = saver.addFile(filename);
 
 	if(stream->write((const ui8*)s.c_str(), s.size()) != s.size())

+ 1 - 1
test/mock/mock_MapService.cpp

@@ -84,7 +84,7 @@ void MapServiceMock::saveMap(const std::unique_ptr<CMap> & map, boost::filesyste
 
 void MapServiceMock::addToArchive(CZipSaver & saver, const JsonNode & data, const std::string & filename)
 {
-	auto s = data.toJson();
+	auto s = data.toString();
 	std::unique_ptr<COutputStream> stream = saver.addFile(filename);
 
 	if(stream->write((const ui8*)s.c_str(), s.size()) != s.size())

+ 12 - 12
test/scripting/LuaSpellEffectAPITest.cpp

@@ -46,7 +46,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplicableOnExpert)
 
 	JsonNode ret = context->callGlobal("applicable", params);
 
-	JsonNode expected = JsonUtils::boolNode(true);
+	JsonNode expected(true);
 
 	JsonComparer cmp(false);
 	cmp.compare("applicable result", ret, expected);
@@ -65,7 +65,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_NotApplicableOnAdvanced)
 
 	JsonNode ret = context->callGlobal("applicable", params);
 
-	JsonNode expected = JsonUtils::boolNode(false);
+	JsonNode expected(false);
 
 	JsonComparer cmp(false);
 	cmp.compare("applicable result", ret, expected);
@@ -84,8 +84,8 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplicableOnLeftSideOfField)
 	BattleHex hex(2,2);
 
 	JsonNode first;
-	first.Vector().push_back(JsonUtils::intNode(hex.hex));
-	first.Vector().push_back(JsonNode());
+	first.Vector().emplace_back(hex.hex);
+	first.Vector().emplace_back();
 
 	JsonNode targets;
 	targets.Vector().push_back(first);
@@ -94,7 +94,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplicableOnLeftSideOfField)
 
 	JsonNode ret = context->callGlobal("applicableTarget", params);
 
-	JsonNode expected = JsonUtils::boolNode(true);
+	JsonNode expected(true);
 
 	JsonComparer cmp(false);
 	cmp.compare("applicable result", ret, expected);
@@ -113,8 +113,8 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_NotApplicableOnRightSideOfField)
 	BattleHex hex(11,2);
 
 	JsonNode first;
-	first.Vector().push_back(JsonUtils::intNode(hex.hex));
-	first.Vector().push_back(JsonUtils::intNode(-1));
+	first.Vector().emplace_back(hex.hex);
+	first.Vector().emplace_back(-1);
 
 	JsonNode targets;
 	targets.Vector().push_back(first);
@@ -123,7 +123,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_NotApplicableOnRightSideOfField)
 
 	JsonNode ret = context->callGlobal("applicableTarget", params);
 
-	JsonNode expected = JsonUtils::boolNode(false);
+	JsonNode expected(false);
 
 	JsonComparer cmp(false);
 	cmp.compare("applicable result", ret, expected);
@@ -138,14 +138,14 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplyMoveUnit)
 	BattleHex hex1(11,2);
 
 	JsonNode unit;
-	unit.Vector().push_back(JsonUtils::intNode(hex1.hex));
-	unit.Vector().push_back(JsonUtils::intNode(42));
+	unit.Vector().emplace_back(hex1.hex);
+	unit.Vector().emplace_back(42);
 
 	BattleHex hex2(5,4);
 
 	JsonNode destination;
-	destination.Vector().push_back(JsonUtils::intNode(hex2.hex));
-	destination.Vector().push_back(JsonUtils::intNode(-1));
+	destination.Vector().emplace_back(hex2.hex);
+	destination.Vector().emplace_back(-1);
 
 	JsonNode targets;
 	targets.Vector().push_back(unit);

+ 9 - 9
test/scripting/LuaSpellEffectTest.cpp

@@ -91,7 +91,7 @@ public:
 
 	JsonNode saveRequest(const std::string &, const JsonNode & parameters)
 	{
-		JsonNode response = JsonUtils::boolNode(true);
+		JsonNode response(true);
 
 		request = parameters;
 		return response;
@@ -99,7 +99,7 @@ public:
 
 	JsonNode saveRequest2(ServerCallback *, const std::string &, const JsonNode & parameters)
 	{
-		JsonNode response = JsonUtils::boolNode(true);
+		JsonNode response(true);
 
 		request = parameters;
 		return response;
@@ -123,7 +123,7 @@ TEST_F(LuaSpellEffectTest, ApplicableRedirected)
 {
 	setDefaultExpectations();
 
-	JsonNode response = JsonUtils::boolNode(true);
+	JsonNode response(true);
 
 	EXPECT_CALL(*contextMock, callGlobal(Eq("applicable"),_)).WillOnce(Return(response));//TODO: check call parameter
 
@@ -154,12 +154,12 @@ TEST_F(LuaSpellEffectTest, ApplicableTargetRedirected)
 
 
 	JsonNode first;
-	first.Vector().push_back(JsonUtils::intNode(hex1.hex));
-	first.Vector().push_back(JsonUtils::intNode(id1));
+	first.Vector().emplace_back(hex1.hex);
+	first.Vector().emplace_back(id1);
 
 	JsonNode second;
-	second.Vector().push_back(JsonUtils::intNode(hex2.hex));
-	second.Vector().push_back(JsonUtils::intNode(-1));
+	second.Vector().emplace_back(hex2.hex);
+	second.Vector().emplace_back(-1);
 
 	JsonNode targets;
 	targets.Vector().push_back(first);
@@ -193,8 +193,8 @@ TEST_F(LuaSpellEffectTest, ApplyRedirected)
 	subject->apply(&serverMock, &mechanicsMock, target);
 
 	JsonNode first;
-	first.Vector().push_back(JsonUtils::intNode(hex1.hex));
-	first.Vector().push_back(JsonUtils::intNode(id1));
+	first.Vector().emplace_back(hex1.hex);
+	first.Vector().emplace_back(id1);
 
 	JsonNode targets;
 	targets.Vector().push_back(first);

+ 1 - 1
test/scripting/ScriptFixture.cpp

@@ -23,7 +23,7 @@ ScriptFixture::~ScriptFixture() = default;
 
 void ScriptFixture::loadScriptFromFile(const std::string & path)
 {
-	JsonNode scriptConfig(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode scriptConfig;
 	scriptConfig["source"].String() = path;
 	loadScript(scriptConfig);
 }

+ 1 - 1
test/spells/TargetConditionTest.cpp

@@ -122,7 +122,7 @@ TEST_F(TargetConditionTest, SerializesCorrectly)
 
 	EXPECT_CALL(factoryMock, createConfigurable(Eq(""), Eq("bonus"), Eq("UNDEAD"))).WillOnce(Return(normalItem));
 
-	JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode config;
 	config["noneOf"]["bonus.NON_LIVING"].String() = "normal";
 	config["anyOf"]["bonus.SIEGE_WEAPON"].String() = "absolute";
 	config["allOf"]["bonus.UNDEAD"].String() = "normal";

+ 1 - 1
test/spells/effects/CatapultTest.cpp

@@ -116,7 +116,7 @@ private:
 TEST_F(CatapultApplyTest, DISABLED_DamageToIntactPart)
 {
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["targetsToAttack"].Integer() = 1;
 		config["chanceToNormalHit"].Integer() = 100;
 		EffectFixture::setupEffect(config);

+ 1 - 1
test/spells/effects/CloneTest.cpp

@@ -40,7 +40,7 @@ protected:
 TEST_F(CloneTest, ApplicableToValidTarget)
 {
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["maxTier"].Integer() = 7;
 		EffectFixture::setupEffect(config);
 	}

+ 2 - 2
test/spells/effects/DamageTest.cpp

@@ -147,7 +147,7 @@ TEST_F(DamageApplyTest, DISABLED_DoesDamageByPercent)
 	using namespace ::battle;
 
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["killByPercentage"].Bool() = true;
 		EffectFixture::setupEffect(config);
 	}
@@ -192,7 +192,7 @@ TEST_F(DamageApplyTest, DISABLED_DoesDamageByCount)
 	using namespace ::battle;
 
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["killByCount"].Bool() = true;
 		EffectFixture::setupEffect(config);
 	}

+ 3 - 3
test/spells/effects/DispelTest.cpp

@@ -68,7 +68,7 @@ class DispelTest : public DispelFixture
 TEST_F(DispelTest, DISABLED_ApplicableToAliveUnitWithTimedEffect)
 {
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["dispelNegative"].Bool() = true;
 		EffectFixture::setupEffect(config);
 	}
@@ -94,7 +94,7 @@ TEST_F(DispelTest, DISABLED_ApplicableToAliveUnitWithTimedEffect)
 TEST_F(DispelTest, DISABLED_IgnoresOwnEffects)
 {
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["dispelPositive"].Bool() = true;
 		config["dispelNegative"].Bool() = true;
 		config["dispelNeutral"].Bool() = true;
@@ -165,7 +165,7 @@ public:
 TEST_F(DispelApplyTest, DISABLED_RemovesEffects)
 {
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["dispelPositive"].Bool() = true;
 		config["dispelNegative"].Bool() = true;
 		config["dispelNeutral"].Bool() = true;

+ 8 - 8
test/spells/effects/HealTest.cpp

@@ -77,7 +77,7 @@ TEST_F(HealTest, ApplicableToWoundedUnit)
 TEST_F(HealTest, ApplicableIfActuallyResurrects)
 {
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["healLevel"].String() = "resurrect";
 		config["minFullUnits"].Integer() = 5;
 		EffectFixture::setupEffect(config);
@@ -104,7 +104,7 @@ TEST_F(HealTest, ApplicableIfActuallyResurrects)
 TEST_F(HealTest, NotApplicableIfNotEnoughCasualties)
 {
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["healLevel"].String() = "resurrect";
 		config["minFullUnits"].Integer() = 1;
 		EffectFixture::setupEffect(config);
@@ -130,7 +130,7 @@ TEST_F(HealTest, NotApplicableIfNotEnoughCasualties)
 TEST_F(HealTest, NotApplicableIfResurrectsLessThanRequired)
 {
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["healLevel"].String() = "resurrect";
 		config["minFullUnits"].Integer() = 5;
 		EffectFixture::setupEffect(config);
@@ -156,7 +156,7 @@ TEST_F(HealTest, NotApplicableIfResurrectsLessThanRequired)
 TEST_F(HealTest, ApplicableToDeadUnit)
 {
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["healLevel"].String() = "resurrect";
 		EffectFixture::setupEffect(config);
 	}
@@ -187,7 +187,7 @@ TEST_F(HealTest, ApplicableToDeadUnit)
 TEST_F(HealTest, DISABLED_NotApplicableIfDeadUnitIsBlocked)
 {
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["healLevel"].String() = "resurrect";
 		EffectFixture::setupEffect(config);
 	}
@@ -224,7 +224,7 @@ TEST_F(HealTest, DISABLED_NotApplicableIfDeadUnitIsBlocked)
 TEST_F(HealTest, DISABLED_ApplicableWithAnotherDeadUnitInSamePosition)
 {
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["healLevel"].String() = "resurrect";
 		EffectFixture::setupEffect(config);
 	}
@@ -261,7 +261,7 @@ TEST_F(HealTest, DISABLED_ApplicableWithAnotherDeadUnitInSamePosition)
 TEST_F(HealTest, NotApplicableIfEffectValueTooLow)
 {
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["minFullUnits"].Integer() = 1;
 		EffectFixture::setupEffect(config);
 	}
@@ -328,7 +328,7 @@ protected:
 TEST_P(HealApplyTest, DISABLED_Heals)
 {
 	{
-		JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode config;
 		config["healLevel"].String() = HEAL_LEVEL_MAP.at(static_cast<size_t>(healLevel));
 		config["healPower"].String() = HEAL_POWER_MAP.at(static_cast<size_t>(healPower));
 		EffectFixture::setupEffect(config);

+ 2 - 2
test/spells/effects/SacrificeTest.cpp

@@ -39,7 +39,7 @@ protected:
 		EffectFixture::setUp();
 
 		{
-			JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+			JsonNode config;
 			config["healLevel"].String() = "resurrect";
 			EffectFixture::setupEffect(config);
 		}
@@ -146,7 +146,7 @@ protected:
 		EffectFixture::setUp();
 
 		{
-			JsonNode config(JsonNode::JsonType::DATA_STRUCT);
+			JsonNode config;
 			config["healLevel"].String() = "resurrect";
 			config["healPower"].String() = "permanent";
 			EffectFixture::setupEffect(config);

+ 2 - 2
test/spells/effects/SummonTest.cpp

@@ -73,7 +73,7 @@ protected:
 
 		toSummon = creature1;
 
-		JsonNode options(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode options;
 		options["id"].String() = "airElemental";
 		options["exclusive"].Bool() = exclusive;
 		options["summonSameUnit"].Bool() = summonSameUnit;
@@ -202,7 +202,7 @@ protected:
 		permanent = ::testing::get<0>(GetParam());
 		summonByHealth = ::testing::get<1>(GetParam());
 
-		JsonNode options(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode options;
 		options["id"].String() = "airElemental";
 		options["permanent"].Bool() = permanent;
 		options["summonByHealth"].Bool() = summonByHealth;

+ 2 - 2
test/spells/effects/TimedTest.cpp

@@ -77,11 +77,11 @@ TEST_P(TimedApplyTest, DISABLED_ChangesBonuses)
 	Bonus testBonus2(BonusDuration::N_TURNS, BonusType::PRIMARY_SKILL, BonusSource::OTHER, 3, BonusSourceID(), BonusSubtypeID(PrimarySkill::KNOWLEDGE));
 	testBonus2.turnsRemain = 4;
 
-	JsonNode options(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode options;
 	options["cumulative"].Bool() = cumulative;
 	options["bonus"]["test1"] = testBonus1.toJsonNode();
 	options["bonus"]["test2"] = testBonus2.toJsonNode();
-	options.setMeta(ModScope::scopeBuiltin());
+	options.setModScope(ModScope::scopeBuiltin());
 	setupEffect(options);
 
 	const uint32_t unitId = 42;