瀏覽代碼

Merge pull request #3616 from IvanSavenko/json_split

Move & split files related to json
Ivan Savenko 1 年之前
父節點
當前提交
3ea29a9656
共有 100 個文件被更改,包括 1812 次插入1649 次删除
  1. 0 1
      client/CMusicHandler.cpp
  2. 0 1
      client/CPlayerInterface.cpp
  3. 2 1
      client/gui/InterfaceObjectConfigurable.cpp
  4. 1 1
      client/gui/InterfaceObjectConfigurable.h
  5. 0 1
      client/mainmenu/CMainMenu.cpp
  6. 1 1
      client/mainmenu/CMainMenu.h
  7. 1 1
      client/render/CAnimation.cpp
  8. 1 1
      client/render/ColorFilter.cpp
  9. 3 2
      client/render/Colors.cpp
  10. 1 1
      client/render/Graphics.cpp
  11. 1 1
      client/renderSDL/CBitmapHanFont.cpp
  12. 1 1
      client/renderSDL/CTrueTypeFont.cpp
  13. 1 1
      client/renderSDL/SDLImage.cpp
  14. 1 0
      client/renderSDL/ScreenHandler.cpp
  15. 2 0
      launcher/jsonutils.cpp
  16. 2 1
      launcher/jsonutils.h
  17. 0 1
      launcher/modManager/cmodlist.cpp
  18. 0 1
      lib/BasicTypes.cpp
  19. 1 1
      lib/BattleFieldHandler.cpp
  20. 1 1
      lib/CArtHandler.cpp
  21. 1 1
      lib/CBonusTypeHandler.cpp
  22. 4 3
      lib/CConfigHandler.cpp
  23. 1 1
      lib/CConfigHandler.h
  24. 1 0
      lib/CCreatureHandler.cpp
  25. 0 1
      lib/CCreatureHandler.h
  26. 2 1
      lib/CHeroHandler.cpp
  27. 17 7
      lib/CMakeLists.txt
  28. 2 3
      lib/CSkillHandler.cpp
  29. 0 1
      lib/CStack.h
  30. 1 1
      lib/CTownHandler.cpp
  31. 1 1
      lib/GameSettings.cpp
  32. 1 2
      lib/LogicalExpression.h
  33. 1 1
      lib/ObstacleHandler.cpp
  34. 0 1
      lib/ResourceSet.cpp
  35. 1 1
      lib/RiverHandler.cpp
  36. 1 1
      lib/RoadHandler.cpp
  37. 1 1
      lib/ScriptHandler.h
  38. 1 1
      lib/TerrainHandler.cpp
  39. 1 0
      lib/bonuses/Bonus.cpp
  40. 2 5
      lib/bonuses/BonusEnum.cpp
  41. 1 2
      lib/bonuses/BonusList.cpp
  42. 1 1
      lib/bonuses/BonusParams.h
  43. 1 0
      lib/bonuses/Limiters.cpp
  44. 2 1
      lib/bonuses/Updaters.cpp
  45. 0 1
      lib/campaign/CampaignState.cpp
  46. 1 1
      lib/filesystem/AdapterLoaders.cpp
  47. 1 1
      lib/filesystem/Filesystem.cpp
  48. 0 1
      lib/filesystem/ResourcePath.cpp
  49. 2 0
      lib/gameState/CGameState.cpp
  50. 21 805
      lib/json/JsonBonus.cpp
  51. 28 0
      lib/json/JsonBonus.h
  52. 430 0
      lib/json/JsonNode.cpp
  53. 2 93
      lib/json/JsonNode.h
  54. 465 0
      lib/json/JsonParser.cpp
  55. 2 53
      lib/json/JsonParser.h
  56. 16 15
      lib/json/JsonRandom.cpp
  57. 0 0
      lib/json/JsonRandom.h
  58. 407 0
      lib/json/JsonUtils.cpp
  59. 99 0
      lib/json/JsonUtils.h
  60. 9 590
      lib/json/JsonValidator.cpp
  61. 49 0
      lib/json/JsonValidator.h
  62. 144 0
      lib/json/JsonWriter.cpp
  63. 35 0
      lib/json/JsonWriter.h
  64. 2 1
      lib/mapObjectConstructors/AObjectTypeHandler.cpp
  65. 1 1
      lib/mapObjectConstructors/AObjectTypeHandler.h
  66. 1 1
      lib/mapObjectConstructors/CBankInstanceConstructor.cpp
  67. 1 0
      lib/mapObjectConstructors/CBankInstanceConstructor.h
  68. 1 1
      lib/mapObjectConstructors/CObjectClassesHandler.cpp
  69. 2 1
      lib/mapObjectConstructors/CObjectClassesHandler.h
  70. 1 1
      lib/mapObjectConstructors/CommonConstructors.cpp
  71. 1 1
      lib/mapObjectConstructors/DwellingInstanceConstructor.cpp
  72. 2 0
      lib/mapObjectConstructors/DwellingInstanceConstructor.h
  73. 1 0
      lib/mapObjectConstructors/HillFortInstanceConstructor.h
  74. 1 0
      lib/mapObjectConstructors/ShipyardInstanceConstructor.h
  75. 1 0
      lib/mapObjects/CGHeroInstance.cpp
  76. 1 1
      lib/mapObjects/CObjectHandler.cpp
  77. 0 1
      lib/mapObjects/ObjectTemplate.cpp
  78. 1 0
      lib/mapping/CMapHeader.cpp
  79. 2 0
      lib/mapping/CMapHeader.h
  80. 1 0
      lib/mapping/CMapService.cpp
  81. 0 1
      lib/mapping/MapEditUtils.cpp
  82. 2 2
      lib/mapping/MapFormatJson.cpp
  83. 0 1
      lib/mapping/MapFormatJson.h
  84. 0 1
      lib/mapping/MapIdentifiersH3M.cpp
  85. 1 0
      lib/modding/CModHandler.cpp
  86. 1 1
      lib/modding/CModInfo.h
  87. 1 0
      lib/modding/ContentTypeHandler.cpp
  88. 1 1
      lib/modding/ContentTypeHandler.h
  89. 0 1
      lib/modding/IdentifierStorage.cpp
  90. 1 1
      lib/networkPacks/BattleChanges.h
  91. 2 2
      lib/networkPacks/EntityChanges.h
  92. 1 1
      lib/rewardable/Info.cpp
  93. 1 1
      lib/rewardable/Info.h
  94. 0 2
      lib/serializer/JsonDeserializer.cpp
  95. 0 2
      lib/serializer/JsonSerializeFormat.cpp
  96. 2 1
      lib/serializer/JsonSerializeFormat.h
  97. 0 2
      lib/serializer/JsonSerializer.cpp
  98. 1 2
      lib/serializer/JsonUpdater.cpp
  99. 2 1
      lib/spells/CSpellHandler.cpp
  100. 1 1
      lib/spells/CSpellHandler.h

+ 0 - 1
client/CMusicHandler.cpp

@@ -17,7 +17,6 @@
 #include "eventsSDL/InputHandler.h"
 #include "gui/CGuiHandler.h"
 
-#include "../lib/JsonNode.h"
 #include "../lib/GameConstants.h"
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/constants/StringConstants.h"

+ 0 - 1
client/CPlayerInterface.cpp

@@ -75,7 +75,6 @@
 #include "../lib/CTownHandler.h"
 #include "../lib/CondSh.h"
 #include "../lib/GameConstants.h"
-#include "../lib/JsonNode.h"
 #include "../lib/RoadHandler.h"
 #include "../lib/StartInfo.h"
 #include "../lib/TerrainHandler.h"

+ 2 - 1
client/gui/InterfaceObjectConfigurable.cpp

@@ -29,7 +29,8 @@
 #include "../windows/GUIClasses.h"
 #include "../windows/InfoWindows.h"
 
-#include "../../lib//constants/StringConstants.h"
+#include "../../lib/constants/StringConstants.h"
+#include "../../lib/json/JsonUtils.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/filesystem/ResourcePath.h"
 

+ 1 - 1
client/gui/InterfaceObjectConfigurable.h

@@ -14,7 +14,7 @@
 #include "TextAlignment.h"
 #include "../render/EFont.h"
 
-#include "../../lib/JsonNode.h"
+#include "../../lib/json/JsonNode.h"
 
 class CPicture;
 class CLabel;

+ 0 - 1
client/mainmenu/CMainMenu.cpp

@@ -45,7 +45,6 @@
 #include "../../CCallback.h"
 
 #include "../../lib/CGeneralTextHandler.h"
-#include "../../lib/JsonNode.h"
 #include "../../lib/campaign/CampaignHandler.h"
 #include "../../lib/serializer/CTypeList.h"
 #include "../../lib/filesystem/Filesystem.h"

+ 1 - 1
client/mainmenu/CMainMenu.h

@@ -10,7 +10,7 @@
 #pragma once
 
 #include "../windows/CWindowObject.h"
-#include "../../lib/JsonNode.h"
+#include "../../lib/json/JsonNode.h"
 #include "../../lib/LoadProgress.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 1
client/render/CAnimation.cpp

@@ -14,7 +14,7 @@
 
 #include "Graphics.h"
 #include "../../lib/filesystem/Filesystem.h"
-#include "../../lib/JsonNode.h"
+#include "../../lib/json/JsonUtils.h"
 #include "../renderSDL/SDLImage.h"
 
 std::shared_ptr<IImage> CAnimation::getFromExtraDef(std::string filename)

+ 1 - 1
client/render/ColorFilter.cpp

@@ -10,8 +10,8 @@
 #include "StdInc.h"
 #include "ColorFilter.h"
 
-#include "../../lib/JsonNode.h"
 #include "../../lib/Color.h"
+#include "../../lib/json/JsonNode.h"
 
 ColorRGBA ColorFilter::shiftColor(const ColorRGBA & in) const
 {

+ 3 - 2
client/render/Colors.cpp

@@ -10,7 +10,8 @@
 
 #include "StdInc.h"
 #include "Colors.h"
-#include "../../lib/JsonNode.h"
+
+#include "../../lib/json/JsonNode.h"
 
 const ColorRGBA Colors::YELLOW = { 229, 215, 123, ColorRGBA::ALPHA_OPAQUE };
 const ColorRGBA Colors::WHITE = { 255, 243, 222, ColorRGBA::ALPHA_OPAQUE };
@@ -48,4 +49,4 @@ std::optional<ColorRGBA> Colors::parseColor(std::string text)
 	}
 
 	return std::nullopt;
-}
+}

+ 1 - 1
client/render/Graphics.cpp

@@ -29,13 +29,13 @@
 
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/CBinaryReader.h"
+#include "../../lib/json/JsonNode.h"
 #include "../lib/modding/CModHandler.h"
 #include "../lib/modding/ModScope.h"
 #include "CGameInfo.h"
 #include "../lib/VCMI_Lib.h"
 #include "../CCallback.h"
 #include "../lib/CGeneralTextHandler.h"
-#include "../lib/JsonNode.h"
 #include "../lib/vcmi_endian.h"
 #include "../lib/CStopWatch.h"
 #include "../lib/CHeroHandler.h"

+ 1 - 1
client/renderSDL/CBitmapHanFont.cpp

@@ -13,8 +13,8 @@
 #include "CBitmapFont.h"
 #include "SDL_Extensions.h"
 
-#include "../../lib/JsonNode.h"
 #include "../../lib/filesystem/Filesystem.h"
+#include "../../lib/json/JsonNode.h"
 #include "../../lib/TextOperations.h"
 #include "../../lib/Rect.h"
 

+ 1 - 1
client/renderSDL/CTrueTypeFont.cpp

@@ -15,8 +15,8 @@
 #include "../render/Colors.h"
 #include "../renderSDL/SDL_Extensions.h"
 
-#include "../../lib/JsonNode.h"
 #include "../../lib/TextOperations.h"
+#include "../../lib/json/JsonNode.h"
 #include "../../lib/filesystem/Filesystem.h"
 
 #include <SDL_ttf.h>

+ 1 - 1
client/renderSDL/SDLImage.cpp

@@ -18,7 +18,7 @@
 #include "../render/CDefFile.h"
 #include "../render/Graphics.h"
 
-#include "../../lib/JsonNode.h"
+#include "../../lib/json/JsonNode.h"
 
 #include <SDL_surface.h>
 

+ 1 - 0
client/renderSDL/ScreenHandler.cpp

@@ -12,6 +12,7 @@
 #include "ScreenHandler.h"
 
 #include "../../lib/CConfigHandler.h"
+#include "../../lib/constants/StringConstants.h"
 #include "../gui/CGuiHandler.h"
 #include "../eventsSDL/NotificationHandler.h"
 #include "../gui/WindowHandler.h"

+ 2 - 0
launcher/jsonutils.cpp

@@ -10,6 +10,8 @@
 #include "StdInc.h"
 #include "jsonutils.h"
 
+#include "../lib/json/JsonNode.h"
+
 static QVariantMap JsonToMap(const JsonMap & json)
 {
 	QVariantMap map;

+ 2 - 1
launcher/jsonutils.h

@@ -10,10 +10,11 @@
 #pragma once
 
 #include <QVariant>
-#include "../lib/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+class JsonNode;
+
 namespace JsonUtils
 {
 QVariant toVariant(const JsonNode & node);

+ 0 - 1
launcher/modManager/cmodlist.cpp

@@ -11,7 +11,6 @@
 #include "cmodlist.h"
 
 #include "../lib/CConfigHandler.h"
-#include "../../lib/JsonNode.h"
 #include "../../lib/filesystem/CFileInputStream.h"
 #include "../../lib/GameConstants.h"
 #include "../../lib/modding/CModVersion.h"

+ 0 - 1
lib/BasicTypes.cpp

@@ -13,7 +13,6 @@
 #include "VCMI_Lib.h"
 #include "GameConstants.h"
 #include "GameSettings.h"
-#include "JsonNode.h"
 #include "bonuses/BonusList.h"
 #include "bonuses/Bonus.h"
 #include "bonuses/IBonusBearer.h"

+ 1 - 1
lib/BattleFieldHandler.cpp

@@ -11,7 +11,7 @@
 
 #include <vcmi/Entity.h>
 #include "BattleFieldHandler.h"
-#include "JsonNode.h"
+#include "json/JsonBonus.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/CArtHandler.cpp

@@ -15,7 +15,7 @@
 #include "GameSettings.h"
 #include "mapObjects/MapObjects.h"
 #include "constants/StringConstants.h"
-
+#include "json/JsonBonus.h"
 #include "mapObjectConstructors/AObjectTypeHandler.h"
 #include "mapObjectConstructors/CObjectClassesHandler.h"
 #include "serializer/JsonSerializeFormat.h"

+ 1 - 1
lib/CBonusTypeHandler.cpp

@@ -13,12 +13,12 @@
 
 #include "CBonusTypeHandler.h"
 
-#include "JsonNode.h"
 #include "filesystem/Filesystem.h"
 
 #include "GameConstants.h"
 #include "CCreatureHandler.h"
 #include "CGeneralTextHandler.h"
+#include "json/JsonUtils.h"
 #include "spells/CSpellHandler.h"
 
 template class std::vector<VCMI_LIB_WRAP_NAMESPACE(CBonusType)>;

+ 4 - 3
lib/CConfigHandler.cpp

@@ -10,9 +10,10 @@
 #include "StdInc.h"
 #include "CConfigHandler.h"
 
-#include "../lib/filesystem/Filesystem.h"
-#include "../lib/GameConstants.h"
-#include "../lib/VCMIDirs.h"
+#include "filesystem/Filesystem.h"
+#include "GameConstants.h"
+#include "VCMIDirs.h"
+#include "json/JsonUtils.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/CConfigHandler.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "../lib/JsonNode.h"
+#include "json/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 0
lib/CCreatureHandler.cpp

@@ -20,6 +20,7 @@
 #include "constants/StringConstants.h"
 #include "bonuses/Limiters.h"
 #include "bonuses/Updaters.h"
+#include "json/JsonBonus.h"
 #include "serializer/JsonDeserializer.h"
 #include "serializer/JsonUpdater.h"
 #include "mapObjectConstructors/AObjectTypeHandler.h"

+ 0 - 1
lib/CCreatureHandler.h

@@ -14,7 +14,6 @@
 #include "ConstTransitivePtr.h"
 #include "ResourceSet.h"
 #include "GameConstants.h"
-#include "JsonNode.h"
 #include "IHandlerBase.h"
 #include "Color.h"
 #include "filesystem/ResourcePath.h"

+ 2 - 1
lib/CHeroHandler.cpp

@@ -13,7 +13,6 @@
 #include "CGeneralTextHandler.h"
 #include "filesystem/Filesystem.h"
 #include "VCMI_Lib.h"
-#include "JsonNode.h"
 #include "constants/StringConstants.h"
 #include "battle/BattleHex.h"
 #include "CCreatureHandler.h"
@@ -24,6 +23,8 @@
 #include "BattleFieldHandler.h"
 #include "bonuses/Limiters.h"
 #include "bonuses/Updaters.h"
+#include "json/JsonBonus.h"
+#include "json/JsonUtils.h"
 #include "mapObjectConstructors/AObjectTypeHandler.h"
 #include "mapObjectConstructors/CObjectClassesHandler.h"
 #include "modding/IdentifierStorage.h"

+ 17 - 7
lib/CMakeLists.txt

@@ -61,6 +61,14 @@ set(lib_SRCS
 	filesystem/MinizipExtensions.cpp
 	filesystem/ResourcePath.cpp
 
+	json/JsonBonus.cpp
+	json/JsonNode.cpp
+	json/JsonParser.cpp
+	json/JsonRandom.cpp
+	json/JsonUtils.cpp
+	json/JsonValidator.cpp
+	json/JsonWriter.cpp
+
 	gameState/CGameState.cpp
 	gameState/CGameStateCampaign.cpp
 	gameState/InfoAboutArmy.cpp
@@ -245,9 +253,6 @@ set(lib_SRCS
 	GameSettings.cpp
 	IGameCallback.cpp
 	IHandlerBase.cpp
-	JsonDetail.cpp
-	JsonNode.cpp
-	JsonRandom.cpp
 	LoadProgress.cpp
 	LogicalExpression.cpp
 	MetaString.cpp
@@ -399,6 +404,14 @@ set(lib_HEADERS
 	filesystem/MinizipExtensions.h
 	filesystem/ResourcePath.h
 
+	json/JsonBonus.h
+	json/JsonNode.h
+	json/JsonParser.h
+	json/JsonRandom.h
+	json/JsonUtils.h
+	json/JsonValidator.h
+	json/JsonWriter.h
+
 	gameState/CGameState.h
 	gameState/CGameStateCampaign.h
 	gameState/EVictoryLossCheckResult.h
@@ -636,9 +649,6 @@ set(lib_HEADERS
 	IGameEventsReceiver.h
 	IHandlerBase.h
 	int3.h
-	JsonDetail.h
-	JsonNode.h
-	JsonRandom.h
 	Languages.h
 	LoadProgress.h
 	LogicalExpression.h
@@ -772,4 +782,4 @@ if(APPLE_IOS AND NOT USING_CONAN)
 		endif()
 		install(${INSTALL_TYPE} ${LINKED_LIB_REAL} LIBRARY DESTINATION ${LIB_DIR})
 	endforeach()
-endif()
+endif()

+ 2 - 3
lib/CSkillHandler.cpp

@@ -16,12 +16,11 @@
 
 #include "CGeneralTextHandler.h"
 #include "filesystem/Filesystem.h"
+#include "json/JsonBonus.h"
+#include "json/JsonUtils.h"
 #include "modding/IdentifierStorage.h"
 #include "modding/ModUtility.h"
 #include "modding/ModScope.h"
-
-#include "JsonNode.h"
-
 #include "constants/StringConstants.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 0 - 1
lib/CStack.h

@@ -9,7 +9,6 @@
  */
 
 #pragma once
-#include "JsonNode.h"
 #include "bonuses/Bonus.h"
 #include "bonuses/CBonusSystemNode.h"
 #include "CCreatureHandler.h" //todo: remove

+ 1 - 1
lib/CTownHandler.cpp

@@ -12,7 +12,6 @@
 
 #include "VCMI_Lib.h"
 #include "CGeneralTextHandler.h"
-#include "JsonNode.h"
 #include "constants/StringConstants.h"
 #include "CCreatureHandler.h"
 #include "CHeroHandler.h"
@@ -23,6 +22,7 @@
 #include "filesystem/Filesystem.h"
 #include "bonuses/Bonus.h"
 #include "bonuses/Propagators.h"
+#include "json/JsonBonus.h"
 #include "ResourceSet.h"
 #include "mapObjectConstructors/AObjectTypeHandler.h"
 #include "mapObjectConstructors/CObjectClassesHandler.h"

+ 1 - 1
lib/GameSettings.cpp

@@ -9,7 +9,7 @@
  */
 #include "StdInc.h"
 #include "GameSettings.h"
-#include "JsonNode.h"
+#include "json/JsonUtils.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 2
lib/LogicalExpression.h

@@ -9,8 +9,7 @@
  */
 #pragma once
 
-//FIXME: move some of code into .cpp to avoid this include?
-#include "JsonNode.h"
+#include "json/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/ObstacleHandler.cpp

@@ -10,8 +10,8 @@
 #include "StdInc.h"
 #include "ObstacleHandler.h"
 #include "BattleFieldHandler.h"
+#include "json/JsonNode.h"
 #include "modding/IdentifierStorage.h"
-#include "JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 0 - 1
lib/ResourceSet.cpp

@@ -12,7 +12,6 @@
 #include "GameConstants.h"
 #include "ResourceSet.h"
 #include "constants/StringConstants.h"
-#include "JsonNode.h"
 #include "serializer/JsonSerializeFormat.h"
 #include "mapObjects/CObjectHandler.h"
 #include "VCMI_Lib.h"

+ 1 - 1
lib/RiverHandler.cpp

@@ -12,7 +12,7 @@
 #include "RiverHandler.h"
 #include "CGeneralTextHandler.h"
 #include "GameSettings.h"
-#include "JsonNode.h"
+#include "json/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/RoadHandler.cpp

@@ -12,7 +12,7 @@
 #include "RoadHandler.h"
 #include "CGeneralTextHandler.h"
 #include "GameSettings.h"
-#include "JsonNode.h"
+#include "json/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/ScriptHandler.h

@@ -13,7 +13,7 @@
 #if SCRIPTING_ENABLED
 #include <vcmi/scripting/Service.h>
 #include "IHandlerBase.h"
-#include "JsonNode.h"
+#include "json/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/TerrainHandler.cpp

@@ -12,7 +12,7 @@
 #include "TerrainHandler.h"
 #include "CGeneralTextHandler.h"
 #include "GameSettings.h"
-#include "JsonNode.h"
+#include "json/JsonNode.h"
 #include "modding/IdentifierStorage.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 0
lib/bonuses/Bonus.cpp

@@ -26,6 +26,7 @@
 #include "../TerrainHandler.h"
 #include "../constants/StringConstants.h"
 #include "../battle/BattleInfo.h"
+#include "../json/JsonUtils.h"
 #include "../modding/ModUtility.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 2 - 5
lib/bonuses/BonusEnum.cpp

@@ -7,13 +7,10 @@
  * Full text of license available in license.txt file, in main folder
  *
  */
-
-
 #include "StdInc.h"
 
 #include "BonusEnum.h"
-
-#include "../JsonNode.h"
+#include "../json/JsonUtils.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -82,4 +79,4 @@ namespace BonusDuration
 	}
 }
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 1 - 2
lib/bonuses/BonusList.cpp

@@ -10,8 +10,7 @@
 
 #include "StdInc.h"
 #include "CBonusSystemNode.h"
-
-#include "../JsonNode.h"
+#include "../json/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/bonuses/BonusParams.h

@@ -12,7 +12,7 @@
 #include "Bonus.h"
 
 #include "../GameConstants.h"
-#include "../JsonNode.h"
+#include "../json/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 0
lib/bonuses/Limiters.cpp

@@ -24,6 +24,7 @@
 #include "../TerrainHandler.h"
 #include "../constants/StringConstants.h"
 #include "../battle/BattleInfo.h"
+#include "../json/JsonUtils.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 2 - 1
lib/bonuses/Updaters.cpp

@@ -13,6 +13,7 @@
 #include "Updaters.h"
 #include "Limiters.h"
 
+#include "../json/JsonUtils.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../CStack.h"
 
@@ -208,4 +209,4 @@ std::shared_ptr<Bonus> OwnerUpdater::createUpdatedBonus(const std::shared_ptr<Bo
 	return updated;
 }
 
-VCMI_LIB_NAMESPACE_END
+VCMI_LIB_NAMESPACE_END

+ 0 - 1
lib/campaign/CampaignState.cpp

@@ -10,7 +10,6 @@
 #include "StdInc.h"
 #include "CampaignState.h"
 
-#include "../JsonNode.h"
 #include "../Point.h"
 #include "../filesystem/ResourcePath.h"
 #include "../VCMI_Lib.h"

+ 1 - 1
lib/filesystem/AdapterLoaders.cpp

@@ -10,8 +10,8 @@
 #include "StdInc.h"
 #include "AdapterLoaders.h"
 
-#include "../JsonNode.h"
 #include "Filesystem.h"
+#include "../json/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/filesystem/Filesystem.cpp

@@ -16,10 +16,10 @@
 #include "CZipLoader.h"
 
 //For filesystem initialization
-#include "../JsonNode.h"
 #include "../GameConstants.h"
 #include "../VCMIDirs.h"
 #include "../CStopWatch.h"
+#include "../json/JsonNode.h"
 #include "../modding/ModScope.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 0 - 1
lib/filesystem/ResourcePath.cpp

@@ -11,7 +11,6 @@
 #include "ResourcePath.h"
 #include "FileInfo.h"
 
-#include "../JsonNode.h"
 #include "../serializer/JsonDeserializer.h"
 #include "../serializer/JsonSerializer.h"
 

+ 2 - 0
lib/gameState/CGameState.cpp

@@ -31,6 +31,8 @@
 #include "../campaign/CampaignState.h"
 #include "../constants/StringConstants.h"
 #include "../filesystem/ResourcePath.h"
+#include "../json/JsonBonus.h"
+#include "../json/JsonUtils.h"
 #include "../mapObjectConstructors/AObjectTypeHandler.h"
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
 #include "../mapObjectConstructors/DwellingInstanceConstructor.h"

+ 21 - 805
lib/JsonNode.cpp → lib/json/JsonBonus.cpp

@@ -1,5 +1,5 @@
 /*
- * JsonNode.cpp, part of VCMI engine
+ * JsonUtils.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -9,437 +9,27 @@
  */
 
 #include "StdInc.h"
-#include "JsonNode.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 "JsonDetail.h"
-#include "constants/StringConstants.h"
-#include "battle/BattleHex.h"
-
-namespace
-{
-// to avoid duplicating const and non-const code
-template<typename Node>
-Node & resolvePointer(Node & in, const std::string & pointer)
-{
-	if(pointer.empty())
-		return in;
-	assert(pointer[0] == '/');
-
-	size_t splitPos = pointer.find('/', 1);
-
-	std::string entry = pointer.substr(1, splitPos - 1);
-	std::string remainer = splitPos == std::string::npos ? "" : pointer.substr(splitPos);
-
-	if(in.getType() == VCMI_LIB_WRAP_NAMESPACE(JsonNode)::JsonType::DATA_VECTOR)
-	{
-		if(entry.find_first_not_of("0123456789") != std::string::npos) // non-numbers in string
-			throw std::runtime_error("Invalid Json pointer");
-
-		if(entry.size() > 1 && entry[0] == '0') // leading zeros are not allowed
-			throw std::runtime_error("Invalid Json pointer");
-
-		auto index = boost::lexical_cast<size_t>(entry);
-
-		if (in.Vector().size() > index)
-			return in.Vector()[index].resolvePointer(remainer);
-	}
-	return in[entry].resolvePointer(remainer);
-}
-}
+#include "JsonBonus.h"
+
+#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
 
-using namespace JsonDetail;
-
-class LibClasses;
-class CModHandler;
-
 static const JsonNode nullNode;
 
-JsonNode::JsonNode(JsonType Type)
-{
-	setType(Type);
-}
-
-JsonNode::JsonNode(const std::byte *data, size_t datasize)
-	:JsonNode(reinterpret_cast<const char*>(data), datasize)
-{}
-
-JsonNode::JsonNode(const char *data, size_t datasize)
-{
-	JsonParser parser(data, datasize);
-	*this = parser.parse("<unknown>");
-}
-
-JsonNode::JsonNode(const JsonPath & fileURI)
-{
-	auto file = CResourceHandler::get()->load(fileURI)->readAll();
-
-	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
-	*this = parser.parse(fileURI.getName());
-}
-
-JsonNode::JsonNode(const std::string & idx, const JsonPath & fileURI)
-{
-	auto file = CResourceHandler::get(idx)->load(fileURI)->readAll();
-	
-	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
-	*this = parser.parse(fileURI.getName());
-}
-
-JsonNode::JsonNode(const JsonPath & fileURI, bool &isValidSyntax)
-{
-	auto file = CResourceHandler::get()->load(fileURI)->readAll();
-
-	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
-	*this = parser.parse(fileURI.getName());
-	isValidSyntax = parser.isValid();
-}
-
-bool JsonNode::operator == (const JsonNode &other) const
-{
-	return data == other.data;
-}
-
-bool JsonNode::operator != (const JsonNode &other) const
-{
-	return !(*this == other);
-}
-
-JsonNode::JsonType JsonNode::getType() const
-{
-	return static_cast<JsonType>(data.index());
-}
-
-void JsonNode::setMeta(const std::string & metadata, bool recursive)
-{
-	meta = metadata;
-	if (recursive)
-	{
-		switch (getType())
-		{
-			break; case JsonType::DATA_VECTOR:
-			{
-				for(auto & node : Vector())
-				{
-					node.setMeta(metadata);
-				}
-			}
-			break; case JsonType::DATA_STRUCT:
-			{
-				for(auto & node : Struct())
-				{
-					node.second.setMeta(metadata);
-				}
-			}
-		}
-	}
-}
-
-void JsonNode::setType(JsonType Type)
-{
-	if (getType() == Type)
-		return;
-
-	//float<->int conversion
-	if(getType() == JsonType::DATA_FLOAT && Type == JsonType::DATA_INTEGER)
-	{
-		si64 converted = static_cast<si64>(std::get<double>(data));
-		data = JsonData(converted);
-		return;
-	}
-	else if(getType() == JsonType::DATA_INTEGER && Type == JsonType::DATA_FLOAT)
-	{
-		double converted = static_cast<double>(std::get<si64>(data));
-		data = JsonData(converted);
-		return;
-	}
-
-	//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));
-	}
-}
-
-bool JsonNode::isNull() const
-{
-	return getType() == JsonType::DATA_NULL;
-}
-
-bool JsonNode::isNumber() const
-{
-	return getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT;
-}
-
-bool JsonNode::isString() const
-{
-	return getType() == JsonType::DATA_STRING;
-}
-
-bool JsonNode::isVector() const
-{
-	return getType() == JsonType::DATA_VECTOR;
-}
-
-bool JsonNode::isStruct() const
-{
-	return getType() == JsonType::DATA_STRUCT;
-}
-
-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;
-	}
-}
-
-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:
-		{
-			auto propertyCount = Struct().size();
-			if(propertyCount == 0)
-				return true;
-			else if(propertyCount == 1)
-				return Struct().begin()->second.isCompact();
-		}
-		return false;
-	default:
-		return true;
-	}
-}
-
-bool JsonNode::TryBoolFromString(bool & success) const
-{
-	success = true;
-	if(getType() == JsonNode::JsonType::DATA_BOOL)
-		return Bool();
-
-	success = getType() == JsonNode::JsonType::DATA_STRING;
-	if(success)
-	{
-		auto boolParamStr = String();
-		boost::algorithm::trim(boolParamStr);
-		boost::algorithm::to_lower(boolParamStr);
-		success = boolParamStr == "true";
-
-		if(success)
-			return true;
-		
-		success = boolParamStr == "false";
-	}
-	return false;
-}
-
-void JsonNode::clear()
-{
-	setType(JsonType::DATA_NULL);
-}
-
-bool & JsonNode::Bool()
-{
-	setType(JsonType::DATA_BOOL);
-	return std::get<bool>(data);
-}
-
-double & JsonNode::Float()
-{
-	setType(JsonType::DATA_FLOAT);
-	return std::get<double>(data);
-}
-
-si64 & JsonNode::Integer()
-{
-	setType(JsonType::DATA_INTEGER);
-	return std::get<si64>(data);
-}
-
-std::string & JsonNode::String()
-{
-	setType(JsonType::DATA_STRING);
-	return std::get<std::string>(data);
-}
-
-JsonVector & JsonNode::Vector()
-{
-	setType(JsonType::DATA_VECTOR);
-	return std::get<JsonVector>(data);
-}
-
-JsonMap & JsonNode::Struct()
-{
-	setType(JsonType::DATA_STRUCT);
-	return std::get<JsonMap>(data);
-}
-
-const bool boolDefault = false;
-bool JsonNode::Bool() const
-{
-	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_BOOL);
-
-	if (getType() == JsonType::DATA_BOOL)
-		return std::get<bool>(data);
-
-	return boolDefault;
-}
-
-const double floatDefault = 0;
-double JsonNode::Float() const
-{
-	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT);
-
-	if(getType() == JsonType::DATA_FLOAT)
-		return std::get<double>(data);
-
-	if(getType() == JsonType::DATA_INTEGER)
-		return static_cast<double>(std::get<si64>(data));
-
-	return floatDefault;
-}
-
-const si64 integerDefault = 0;
-si64 JsonNode::Integer() const
-{
-	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT);
-
-	if(getType() == JsonType::DATA_INTEGER)
-		return std::get<si64>(data);
-
-	if(getType() == JsonType::DATA_FLOAT)
-		return static_cast<si64>(std::get<double>(data));
-
-	return integerDefault;
-}
-
-const std::string stringDefault = std::string();
-const std::string & JsonNode::String() const
-{
-	assert(getType() == JsonType::DATA_NULL || 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
-{
-	assert(getType() == JsonType::DATA_NULL || 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
-{
-	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRUCT);
-
-	if (getType() == JsonType::DATA_STRUCT)
-		return std::get<JsonMap>(data);
-
-	return mapDefault;
-}
-
-JsonNode & JsonNode::operator[](const std::string & child)
-{
-	return Struct()[child];
-}
-
-const JsonNode & JsonNode::operator[](const std::string & child) const
-{
-	auto it = Struct().find(child);
-	if (it != Struct().end())
-		return it->second;
-	return nullNode;
-}
-
-JsonNode & JsonNode::operator[](size_t child)
-{
-	if (child >= Vector().size() )
-		Vector().resize(child + 1);
-	return Vector()[child];
-}
-
-const JsonNode & JsonNode::operator[](size_t child) const
-{
-	if (child < Vector().size() )
-		return Vector()[child];
-
-	return nullNode;
-}
-
-const JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer) const
-{
-	return ::resolvePointer(*this, jsonPointer);
-}
-
-JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer)
-{
-	return ::resolvePointer(*this, jsonPointer);
-}
-
-std::vector<std::byte> JsonNode::toBytes(bool compact) const
-{
-	std::string jsonString = toJson(compact);
-	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::ostringstream out;
-	JsonWriter writer(out, compact);
-	writer.writeNode(*this);
-	return out.str();
-}
-
-///JsonUtils
-
 static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const JsonNode & node)
 {
 	if (node.isNull())
@@ -1145,7 +735,7 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 	if (!value->isNull())
 	{
 		//ALL_CREATURES old propagator compatibility
-		if(value->String() == "ALL_CREATURES") 
+		if(value->String() == "ALL_CREATURES")
 		{
 			logMod->warn("ALL_CREATURES propagator is deprecated. Use GLOBAL_EFFECT propagator with CREATURES_ONLY limiter");
 			b->addLimiter(std::make_shared<CreatureLevelLimiter>());
@@ -1182,7 +772,7 @@ CSelector JsonUtils::parseSelector(const JsonNode & ability)
 		CSelector base = Selector::none;
 		for(const auto & andN : value->Vector())
 			base = base.Or(parseSelector(andN));
-		
+
 		ret = ret.And(base);
 	}
 
@@ -1192,7 +782,7 @@ CSelector JsonUtils::parseSelector(const JsonNode & ability)
 		CSelector base = Selector::none;
 		for(const auto & andN : value->Vector())
 			base = base.Or(parseSelector(andN));
-		
+
 		ret = ret.And(base.Not());
 	}
 
@@ -1237,7 +827,7 @@ CSelector JsonUtils::parseSelector(const JsonNode & ability)
 	else if(src)
 		ret = ret.And(Selector::sourceTypeSel(*src));
 
-	
+
 	value = &ability["targetSourceType"];
 	if(value->isString())
 	{
@@ -1276,378 +866,4 @@ CSelector JsonUtils::parseSelector(const JsonNode & ability)
 	return ret;
 }
 
-//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];
-
-#if defined(VCMI_IOS)
-	if (!fieldProps["defaultIOS"].isNull())
-		return fieldProps["defaultIOS"];
-#elif defined(VCMI_ANDROID)
-	if (!fieldProps["defaultAndroid"].isNull())
-		return fieldProps["defaultAndroid"];
-#elif defined(VCMI_WINDOWS)
-	if (!fieldProps["defaultWindows"].isNull())
-		return fieldProps["defaultWindows"];
-#endif
-
-#if !defined(VCMI_MOBILE)
-	if (!fieldProps["defaultDesktop"].isNull())
-		return fieldProps["defaultDesktop"];
-#endif
-	return fieldProps["default"];
-}
-
-static void eraseOptionalNodes(JsonNode & node, const JsonNode & schema)
-{
-	assert(schema["type"].String() == "object");
-
-	std::set<std::string> foundEntries;
-
-	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);
-	});
-}
-
-static void minimizeNode(JsonNode & node, const JsonNode & schema)
-{
-	if (schema["type"].String() != "object")
-		return;
-
-	for(const auto & entry : schema["required"].Vector())
-	{
-		const std::string & name = entry.String();
-		minimizeNode(node[name], schema["properties"][name]);
-
-		if (vstd::contains(node.Struct(), name) && node[name] == getDefaultValue(schema, name))
-			node.Struct().erase(name);
-	}
-	eraseOptionalNodes(node, schema);
-}
-
-static void maximizeNode(JsonNode & node, const JsonNode & schema)
-{
-	// "required" entry can only be found in object/struct
-	if (schema["type"].String() != "object")
-		return;
-
-	// check all required entries that have default version
-	for(const auto & entry : schema["required"].Vector())
-	{
-		const std::string & name = entry.String();
-
-		if (node[name].isNull() && !getDefaultValue(schema, name).isNull())
-			node[name] = getDefaultValue(schema, name);
-
-		maximizeNode(node[name], schema["properties"][name]);
-	}
-
-	eraseOptionalNodes(node, schema);
-}
-
-void JsonUtils::minimize(JsonNode & node, const std::string & schemaName)
-{
-	minimizeNode(node, getSchema(schemaName));
-}
-
-void JsonUtils::maximize(JsonNode & node, const std::string & schemaName)
-{
-	maximizeNode(node, getSchema(schemaName));
-}
-
-bool JsonUtils::validate(const JsonNode & node, const std::string & schemaName, const std::string & dataName)
-{
-	std::string log = Validation::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));
-	}
-	return log.empty();
-}
-
-const JsonNode & getSchemaByName(const std::string & name)
-{
-	// cached schemas to avoid loading json data multiple times
-	static std::map<std::string, JsonNode> loadedSchemas;
-
-	if (vstd::contains(loadedSchemas, name))
-		return loadedSchemas[name];
-
-	auto filename = JsonPath::builtin("config/schemas/" + name);
-
-	if (CResourceHandler::get()->existsResource(filename))
-	{
-		loadedSchemas[name] = JsonNode(filename);
-		return loadedSchemas[name];
-	}
-
-	logMod->error("Error: missing schema with name %s!", name);
-	assert(0);
-	return nullNode;
-}
-
-const JsonNode & JsonUtils::getSchema(const std::string & URI)
-{
-	size_t posColon = URI.find(':');
-	size_t posHash  = URI.find('#');
-	std::string filename;
-	if(posColon == std::string::npos)
-	{
-		filename = URI.substr(0, posHash);
-	}
-	else
-	{
-		std::string protocolName = URI.substr(0, posColon);
-		filename = URI.substr(posColon + 1, posHash - posColon - 1) + ".json";
-		if(protocolName != "vcmi")
-		{
-			logMod->error("Error: unsupported URI protocol for schema: %s", URI);
-			return nullNode;
-		}
-	}
-
-	// check if json pointer if present (section after hash in string)
-	if(posHash == std::string::npos || posHash == URI.size() - 1)
-	{
-		auto const & result = getSchemaByName(filename);
-		if (result.isNull())
-			logMod->error("Error: missing schema %s", URI);
-		return result;
-	}
-	else
-	{
-		auto const & result = getSchemaByName(filename).resolvePointer(URI.substr(posHash + 1));
-		if (result.isNull())
-			logMod->error("Error: missing schema %s", URI);
-		return result;
-	}
-}
-
-void JsonUtils::merge(JsonNode & dest, JsonNode & source, bool ignoreOverride, bool copyMeta)
-{
-	if (dest.getType() == JsonNode::JsonType::DATA_NULL)
-	{
-		std::swap(dest, source);
-		return;
-	}
-
-	switch (source.getType())
-	{
-		case JsonNode::JsonType::DATA_NULL:
-		{
-			dest.clear();
-			break;
-		}
-		case JsonNode::JsonType::DATA_BOOL:
-		case JsonNode::JsonType::DATA_FLOAT:
-		case JsonNode::JsonType::DATA_INTEGER:
-		case JsonNode::JsonType::DATA_STRING:
-		case JsonNode::JsonType::DATA_VECTOR:
-		{
-			std::swap(dest, source);
-			break;
-		}
-		case JsonNode::JsonType::DATA_STRUCT:
-		{
-			if(!ignoreOverride && vstd::contains(source.flags, "override"))
-			{
-				std::swap(dest, source);
-			}
-			else
-			{
-				if (copyMeta)
-					dest.meta = source.meta;
-
-				//recursively merge all entries from struct
-				for(auto & node : source.Struct())
-					merge(dest[node.first], node.second, ignoreOverride);
-			}
-		}
-	}
-}
-
-void JsonUtils::mergeCopy(JsonNode & dest, JsonNode source, bool ignoreOverride, bool copyMeta)
-{
-	// uses copy created in stack to safely merge two nodes
-	merge(dest, source, ignoreOverride, copyMeta);
-}
-
-void JsonUtils::inherit(JsonNode & descendant, const JsonNode & base)
-{
-	JsonNode inheritedNode(base);
-	merge(inheritedNode, descendant, true, true);
-	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;
-	return assembleFromFiles(files, isValid);
-}
-
-JsonNode JsonUtils::assembleFromFiles(const std::vector<std::string> & files, bool & isValid)
-{
-	isValid = true;
-	JsonNode result;
-
-	for(const auto & file : files)
-	{
-		bool isValidFile = false;
-		JsonNode section(JsonPath::builtinTODO(file), isValidFile);
-		merge(result, section);
-		isValid |= isValidFile;
-	}
-	return result;
-}
-
-JsonNode JsonUtils::assembleFromFiles(const std::string & filename)
-{
-	JsonNode result;
-	JsonPath resID = JsonPath::builtinTODO(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());
-		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

+ 28 - 0
lib/json/JsonBonus.h

@@ -0,0 +1,28 @@
+/*
+ * JsonBonus.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
+
+#include "JsonNode.h"
+#include "../GameConstants.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+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);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 430 - 0
lib/json/JsonNode.cpp

@@ -0,0 +1,430 @@
+/*
+ * JsonNode.cpp, 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
+ *
+ */
+
+#include "StdInc.h"
+#include "JsonNode.h"
+
+#include "filesystem/Filesystem.h"
+#include "JsonParser.h"
+#include "JsonWriter.h"
+
+namespace
+{
+// to avoid duplicating const and non-const code
+template<typename Node>
+Node & resolvePointer(Node & in, const std::string & pointer)
+{
+	if(pointer.empty())
+		return in;
+	assert(pointer[0] == '/');
+
+	size_t splitPos = pointer.find('/', 1);
+
+	std::string entry = pointer.substr(1, splitPos - 1);
+	std::string remainer = splitPos == std::string::npos ? "" : pointer.substr(splitPos);
+
+	if(in.getType() == VCMI_LIB_WRAP_NAMESPACE(JsonNode)::JsonType::DATA_VECTOR)
+	{
+		if(entry.find_first_not_of("0123456789") != std::string::npos) // non-numbers in string
+			throw std::runtime_error("Invalid Json pointer");
+
+		if(entry.size() > 1 && entry[0] == '0') // leading zeros are not allowed
+			throw std::runtime_error("Invalid Json pointer");
+
+		auto index = boost::lexical_cast<size_t>(entry);
+
+		if (in.Vector().size() > index)
+			return in.Vector()[index].resolvePointer(remainer);
+	}
+	return in[entry].resolvePointer(remainer);
+}
+}
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+using namespace JsonDetail;
+
+class LibClasses;
+class CModHandler;
+
+static const JsonNode nullNode;
+
+JsonNode::JsonNode(JsonType Type)
+{
+	setType(Type);
+}
+
+JsonNode::JsonNode(const std::byte *data, size_t datasize)
+	:JsonNode(reinterpret_cast<const char*>(data), datasize)
+{}
+
+JsonNode::JsonNode(const char *data, size_t datasize)
+{
+	JsonParser parser(data, datasize);
+	*this = parser.parse("<unknown>");
+}
+
+JsonNode::JsonNode(const JsonPath & fileURI)
+{
+	auto file = CResourceHandler::get()->load(fileURI)->readAll();
+
+	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
+	*this = parser.parse(fileURI.getName());
+}
+
+JsonNode::JsonNode(const std::string & idx, const JsonPath & fileURI)
+{
+	auto file = CResourceHandler::get(idx)->load(fileURI)->readAll();
+	
+	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
+	*this = parser.parse(fileURI.getName());
+}
+
+JsonNode::JsonNode(const JsonPath & fileURI, bool &isValidSyntax)
+{
+	auto file = CResourceHandler::get()->load(fileURI)->readAll();
+
+	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
+	*this = parser.parse(fileURI.getName());
+	isValidSyntax = parser.isValid();
+}
+
+bool JsonNode::operator == (const JsonNode &other) const
+{
+	return data == other.data;
+}
+
+bool JsonNode::operator != (const JsonNode &other) const
+{
+	return !(*this == other);
+}
+
+JsonNode::JsonType JsonNode::getType() const
+{
+	return static_cast<JsonType>(data.index());
+}
+
+void JsonNode::setMeta(const std::string & metadata, bool recursive)
+{
+	meta = metadata;
+	if (recursive)
+	{
+		switch (getType())
+		{
+			break; case JsonType::DATA_VECTOR:
+			{
+				for(auto & node : Vector())
+				{
+					node.setMeta(metadata);
+				}
+			}
+			break; case JsonType::DATA_STRUCT:
+			{
+				for(auto & node : Struct())
+				{
+					node.second.setMeta(metadata);
+				}
+			}
+		}
+	}
+}
+
+void JsonNode::setType(JsonType Type)
+{
+	if (getType() == Type)
+		return;
+
+	//float<->int conversion
+	if(getType() == JsonType::DATA_FLOAT && Type == JsonType::DATA_INTEGER)
+	{
+		si64 converted = static_cast<si64>(std::get<double>(data));
+		data = JsonData(converted);
+		return;
+	}
+	else if(getType() == JsonType::DATA_INTEGER && Type == JsonType::DATA_FLOAT)
+	{
+		double converted = static_cast<double>(std::get<si64>(data));
+		data = JsonData(converted);
+		return;
+	}
+
+	//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));
+	}
+}
+
+bool JsonNode::isNull() const
+{
+	return getType() == JsonType::DATA_NULL;
+}
+
+bool JsonNode::isNumber() const
+{
+	return getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT;
+}
+
+bool JsonNode::isString() const
+{
+	return getType() == JsonType::DATA_STRING;
+}
+
+bool JsonNode::isVector() const
+{
+	return getType() == JsonType::DATA_VECTOR;
+}
+
+bool JsonNode::isStruct() const
+{
+	return getType() == JsonType::DATA_STRUCT;
+}
+
+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;
+	}
+}
+
+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:
+		{
+			auto propertyCount = Struct().size();
+			if(propertyCount == 0)
+				return true;
+			else if(propertyCount == 1)
+				return Struct().begin()->second.isCompact();
+		}
+		return false;
+	default:
+		return true;
+	}
+}
+
+bool JsonNode::TryBoolFromString(bool & success) const
+{
+	success = true;
+	if(getType() == JsonNode::JsonType::DATA_BOOL)
+		return Bool();
+
+	success = getType() == JsonNode::JsonType::DATA_STRING;
+	if(success)
+	{
+		auto boolParamStr = String();
+		boost::algorithm::trim(boolParamStr);
+		boost::algorithm::to_lower(boolParamStr);
+		success = boolParamStr == "true";
+
+		if(success)
+			return true;
+		
+		success = boolParamStr == "false";
+	}
+	return false;
+}
+
+void JsonNode::clear()
+{
+	setType(JsonType::DATA_NULL);
+}
+
+bool & JsonNode::Bool()
+{
+	setType(JsonType::DATA_BOOL);
+	return std::get<bool>(data);
+}
+
+double & JsonNode::Float()
+{
+	setType(JsonType::DATA_FLOAT);
+	return std::get<double>(data);
+}
+
+si64 & JsonNode::Integer()
+{
+	setType(JsonType::DATA_INTEGER);
+	return std::get<si64>(data);
+}
+
+std::string & JsonNode::String()
+{
+	setType(JsonType::DATA_STRING);
+	return std::get<std::string>(data);
+}
+
+JsonVector & JsonNode::Vector()
+{
+	setType(JsonType::DATA_VECTOR);
+	return std::get<JsonVector>(data);
+}
+
+JsonMap & JsonNode::Struct()
+{
+	setType(JsonType::DATA_STRUCT);
+	return std::get<JsonMap>(data);
+}
+
+const bool boolDefault = false;
+bool JsonNode::Bool() const
+{
+	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_BOOL);
+
+	if (getType() == JsonType::DATA_BOOL)
+		return std::get<bool>(data);
+
+	return boolDefault;
+}
+
+const double floatDefault = 0;
+double JsonNode::Float() const
+{
+	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT);
+
+	if(getType() == JsonType::DATA_FLOAT)
+		return std::get<double>(data);
+
+	if(getType() == JsonType::DATA_INTEGER)
+		return static_cast<double>(std::get<si64>(data));
+
+	return floatDefault;
+}
+
+const si64 integerDefault = 0;
+si64 JsonNode::Integer() const
+{
+	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT);
+
+	if(getType() == JsonType::DATA_INTEGER)
+		return std::get<si64>(data);
+
+	if(getType() == JsonType::DATA_FLOAT)
+		return static_cast<si64>(std::get<double>(data));
+
+	return integerDefault;
+}
+
+const std::string stringDefault = std::string();
+const std::string & JsonNode::String() const
+{
+	assert(getType() == JsonType::DATA_NULL || 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
+{
+	assert(getType() == JsonType::DATA_NULL || 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
+{
+	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRUCT);
+
+	if (getType() == JsonType::DATA_STRUCT)
+		return std::get<JsonMap>(data);
+
+	return mapDefault;
+}
+
+JsonNode & JsonNode::operator[](const std::string & child)
+{
+	return Struct()[child];
+}
+
+const JsonNode & JsonNode::operator[](const std::string & child) const
+{
+	auto it = Struct().find(child);
+	if (it != Struct().end())
+		return it->second;
+	return nullNode;
+}
+
+JsonNode & JsonNode::operator[](size_t child)
+{
+	if (child >= Vector().size() )
+		Vector().resize(child + 1);
+	return Vector()[child];
+}
+
+const JsonNode & JsonNode::operator[](size_t child) const
+{
+	if (child < Vector().size() )
+		return Vector()[child];
+
+	return nullNode;
+}
+
+const JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer) const
+{
+	return ::resolvePointer(*this, jsonPointer);
+}
+
+JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer)
+{
+	return ::resolvePointer(*this, jsonPointer);
+}
+
+std::vector<std::byte> JsonNode::toBytes(bool compact) const
+{
+	std::string jsonString = toJson(compact);
+	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::ostringstream out;
+	JsonWriter writer(out, compact);
+	writer.writeNode(*this);
+	return out.str();
+}
+
+VCMI_LIB_NAMESPACE_END

+ 2 - 93
lib/JsonNode.h → lib/json/JsonNode.h

@@ -8,8 +8,8 @@
  *
  */
 #pragma once
-#include "GameConstants.h"
-#include "filesystem/ResourcePath.h"
+
+#include "../filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -127,97 +127,6 @@ public:
 	}
 };
 
-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);
-
-	/**
-	 * @brief recursively merges source into dest, replacing identical fields
-	 * struct : recursively calls this function
-	 * arrays : each entry will be merged recursively
-	 * values : value in source will replace value in dest
-	 * null   : if value in source is present but set to null it will delete entry in dest
-	 * @note this function will destroy data in source
-	 */
-	DLL_LINKAGE void merge(JsonNode & dest, JsonNode & source, bool ignoreOverride = false, bool copyMeta = false);
-
-	/**
-	 * @brief recursively merges source into dest, replacing identical fields
-	 * struct : recursively calls this function
-	 * arrays : each entry will be merged recursively
-	 * values : value in source will replace value in dest
-	 * null   : if value in source is present but set to null it will delete entry in dest
-	 * @note this function will preserve data stored in source by creating copy
-	 */
-	DLL_LINKAGE void mergeCopy(JsonNode & dest, JsonNode source, bool ignoreOverride = false, bool copyMeta = false);
-
-	/** @brief recursively merges descendant into copy of base node
-	* Result emulates inheritance semantic
-	*/
-	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
-	 */
-	DLL_LINKAGE JsonNode assembleFromFiles(const std::vector<std::string> & files);
-	DLL_LINKAGE JsonNode assembleFromFiles(const std::vector<std::string> & files, bool & isValid);
-
-	/// This version loads all files with same name (overridden by mods)
-	DLL_LINKAGE JsonNode assembleFromFiles(const std::string & filename);
-
-	/**
-	 * @brief removes all nodes that are identical to default entry in schema
-	 * @param node - JsonNode to minimize
-	 * @param schemaName - name of schema to use
-	 * @note for minimizing data must be valid against given schema
-	 */
-	DLL_LINKAGE void minimize(JsonNode & node, const std::string & schemaName);
-	/// opposed to minimize, adds all missing, required entries that have default value
-	DLL_LINKAGE void maximize(JsonNode & node, const std::string & schemaName);
-
-	/**
-	* @brief validate node against specified schema
-	* @param node - JsonNode to check
-	* @param schemaName - name of schema to use
-	* @param dataName - some way to identify data (printed in console in case of errors)
-	* @returns true if data in node fully compilant with schema
-	*/
-	DLL_LINKAGE bool validate(const JsonNode & node, const std::string & schemaName, const std::string & dataName);
-
-	/// 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);
-}
-
 namespace JsonDetail
 {
 	// conversion helpers for JsonNode::convertTo (partial template function instantiation is illegal in c++)

+ 465 - 0
lib/json/JsonParser.cpp

@@ -0,0 +1,465 @@
+/*
+ * JsonParser.cpp, 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
+ *
+ */
+
+#include "StdInc.h"
+#include "JsonParser.h"
+
+#include "../TextOperations.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+JsonParser::JsonParser(const char * inputString, size_t stringSize):
+	input(inputString, stringSize),
+	lineCount(1),
+	lineStart(0),
+	pos(0)
+{
+}
+
+JsonNode JsonParser::parse(const std::string & fileName)
+{
+	JsonNode root;
+
+	if (input.size() == 0)
+	{
+		error("File is empty", false);
+	}
+	else
+	{
+		if (!TextOperations::isValidUnicodeString(&input[0], input.size()))
+			error("Not a valid UTF-8 file", false);
+
+		extractValue(root);
+		extractWhitespace(false);
+
+		//Warn if there are any non-whitespace symbols left
+		if (pos < input.size())
+			error("Not all file was parsed!", true);
+	}
+
+	if (!errors.empty())
+	{
+		logMod->warn("File %s is not a valid JSON file!", fileName);
+		logMod->warn(errors);
+	}
+	return root;
+}
+
+bool JsonParser::isValid()
+{
+	return errors.empty();
+}
+
+bool JsonParser::extractSeparator()
+{
+	if (!extractWhitespace())
+		return false;
+
+	if ( input[pos] !=':')
+		return error("Separator expected");
+
+	pos++;
+	return true;
+}
+
+bool JsonParser::extractValue(JsonNode &node)
+{
+	if (!extractWhitespace())
+		return false;
+
+	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);
+		default:
+		{
+			if (input[pos] >= '0' && input[pos] <= '9')
+				return extractFloat(node);
+			return error("Value expected!");
+		}
+	}
+}
+
+bool JsonParser::extractWhitespace(bool verbose)
+{
+	while (true)
+	{
+		while(pos < input.size() && static_cast<ui8>(input[pos]) <= ' ')
+		{
+			if (input[pos] == '\n')
+			{
+				lineCount++;
+				lineStart = pos+1;
+			}
+			pos++;
+		}
+		if (pos >= input.size() || input[pos] != '/')
+			break;
+
+		pos++;
+		if (pos == input.size())
+			break;
+		if (input[pos] == '/')
+			pos++;
+		else
+			error("Comments must consist of two slashes!", true);
+
+		while (pos < input.size() && input[pos] != '\n')
+			pos++;
+	}
+
+	if (pos >= input.size() && verbose)
+		return error("Unexpected end of file!");
+	return true;
+}
+
+bool JsonParser::extractEscaping(std::string &str)
+{
+	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);
+	}
+	return true;
+}
+
+bool JsonParser::extractString(std::string &str)
+{
+	if (input[pos] != '\"')
+		return error("String expected!");
+	pos++;
+
+	size_t first = pos;
+
+	while (pos != input.size())
+	{
+		if (input[pos] == '\"') // Correct end of string
+		{
+			str.append( &input[first], pos-first);
+			pos++;
+			return true;
+		}
+		if (input[pos] == '\\') // Escaping
+		{
+			str.append( &input[first], pos-first);
+			pos++;
+			if (pos == input.size())
+				break;
+			extractEscaping(str);
+			first = pos + 1;
+		}
+		if (input[pos] == '\n') // end-of-line
+		{
+			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;
+			error("Illegal character in the string!", true);
+		}
+		pos++;
+	}
+	return error("Unterminated string!");
+}
+
+bool JsonParser::extractString(JsonNode &node)
+{
+	std::string str;
+	if (!extractString(str))
+		return false;
+
+	node.setType(JsonNode::JsonType::DATA_STRING);
+	node.String() = str;
+	return true;
+}
+
+bool JsonParser::extractLiteral(const std::string &literal)
+{
+	if (literal.compare(0, literal.size(), &input[pos], literal.size()) != 0)
+	{
+		while (pos < input.size() && ((input[pos]>'a' && input[pos]<'z')
+								   || (input[pos]>'A' && input[pos]<'Z')))
+			pos++;
+		return error("Unknown literal found", true);
+	}
+
+	pos += literal.size();
+	return true;
+}
+
+bool JsonParser::extractNull(JsonNode &node)
+{
+	if (!extractLiteral("null"))
+		return false;
+
+	node.clear();
+	return true;
+}
+
+bool JsonParser::extractTrue(JsonNode &node)
+{
+	if (!extractLiteral("true"))
+		return false;
+
+	node.Bool() = true;
+	return true;
+}
+
+bool JsonParser::extractFalse(JsonNode &node)
+{
+	if (!extractLiteral("false"))
+		return false;
+
+	node.Bool() = false;
+	return true;
+}
+
+bool JsonParser::extractStruct(JsonNode &node)
+{
+	node.setType(JsonNode::JsonType::DATA_STRUCT);
+	pos++;
+
+	if (!extractWhitespace())
+		return false;
+
+	//Empty struct found
+	if (input[pos] == '}')
+	{
+		pos++;
+		return true;
+	}
+
+	while (true)
+	{
+		if (!extractWhitespace())
+			return 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(!vstd::contains(knownFlags, keyAndFlags[i]))
+				error("Encountered unknown flag #" + keyAndFlags[i], true);
+		}
+
+		if (node.Struct().find(key) != node.Struct().end())
+			error("Duplicate element encountered!", true);
+
+		if (!extractSeparator())
+			return false;
+
+		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]);
+
+		if (input[pos] == '}')
+		{
+			pos++;
+			return true;
+		}
+	}
+}
+
+bool JsonParser::extractArray(JsonNode &node)
+{
+	pos++;
+	node.setType(JsonNode::JsonType::DATA_VECTOR);
+
+	if (!extractWhitespace())
+		return false;
+
+	//Empty array found
+	if (input[pos] == ']')
+	{
+		pos++;
+		return 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);
+
+		if (!extractElement(node.Vector().back(), ']'))
+			return false;
+
+		if (input[pos] == ']')
+		{
+			pos++;
+			return true;
+		}
+	}
+}
+
+bool JsonParser::extractElement(JsonNode &node, char terminator)
+{
+	if (!extractValue(node))
+		return false;
+
+	if (!extractWhitespace())
+		return false;
+
+	bool comma = (input[pos] == ',');
+	if (comma )
+	{
+		pos++;
+		if (!extractWhitespace())
+			return false;
+	}
+
+	if (input[pos] == terminator)
+	{
+		//FIXME: MOD COMPATIBILITY: Too many of these right now, re-enable later
+		//if (comma)
+			//error("Extra comma found!", true);
+		return true;
+	}
+
+	if (!comma)
+		error("Comma expected!", true);
+
+	return true;
+}
+
+bool JsonParser::extractFloat(JsonNode &node)
+{
+	assert(input[pos] == '-' || (input[pos] >= '0' && input[pos] <= '9'));
+	bool negative=false;
+	double result=0;
+	si64 integerPart = 0;
+	bool isFloat = false;
+
+	if (input[pos] == '-')
+	{
+		pos++;
+		negative = true;
+	}
+
+	if (input[pos] < '0' || input[pos] > '9')
+		return error("Number expected!");
+
+	//Extract integer part
+	while (input[pos] >= '0' && input[pos] <= '9')
+	{
+		integerPart = integerPart*10+(input[pos]-'0');
+		pos++;
+	}
+
+	result = static_cast<double>(integerPart);
+
+	if (input[pos] == '.')
+	{
+		//extract fractional part
+		isFloat = true;
+		pos++;
+		double fractMult = 0.1;
+		if (input[pos] < '0' || input[pos] > '9')
+			return error("Decimal part expected!");
+
+		while (input[pos] >= '0' && input[pos] <= '9')
+		{
+			result = result + fractMult*(input[pos]-'0');
+			fractMult /= 10;
+			pos++;
+		}
+	}
+
+	if(input[pos] == 'e')
+	{
+		//extract exponential part
+		pos++;
+		isFloat = true;
+		bool powerNegative = false;
+		double power = 0;
+
+		if(input[pos] == '-')
+		{
+			pos++;
+			powerNegative = true;
+		}
+		else if(input[pos] == '+')
+		{
+			pos++;
+		}
+
+		if (input[pos] < '0' || input[pos] > '9')
+			return error("Exponential part expected!");
+
+		while (input[pos] >= '0' && input[pos] <= '9')
+		{
+			power = power*10 + (input[pos]-'0');
+			pos++;
+		}
+
+		if(powerNegative)
+			power = -power;
+
+		result *= std::pow(10, power);
+	}
+
+	if(isFloat)
+	{
+		if(negative)
+			result = -result;
+
+		node.setType(JsonNode::JsonType::DATA_FLOAT);
+		node.Float() = result;
+	}
+	else
+	{
+		if(negative)
+			integerPart = -integerPart;
+
+		node.setType(JsonNode::JsonType::DATA_INTEGER);
+		node.Integer() = integerPart;
+	}
+
+	return true;
+}
+
+bool JsonParser::error(const std::string &message, bool warning)
+{
+	std::ostringstream stream;
+	std::string type(warning?" warning: ":" error: ");
+
+	stream << "At line " << lineCount << ", position "<<pos-lineStart
+		   << type << message <<"\n";
+	errors += stream.str();
+
+	return warning;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 2 - 53
lib/JsonDetail.h → lib/json/JsonParser.h

@@ -1,5 +1,5 @@
 /*
- * JsonDetail.h, part of VCMI engine
+ * JsonParser.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -13,25 +13,6 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class JsonWriter
-{
-	//prefix for each line (tabulation)
-	std::string prefix;
-	std::ostream & out;
-	//sets whether compact nodes are written in single-line format
-	bool compact;
-	//tracks whether we are currently using single-line format
-	bool compactMode = false;
-public:
-	template<typename Iterator>
-	void writeContainer(Iterator begin, Iterator end);
-	void writeEntry(JsonMap::const_iterator entry);
-	void writeEntry(JsonVector::const_iterator entry);
-	void writeString(const std::string & string);
-	void writeNode(const JsonNode & node);
-	JsonWriter(std::ostream & output, bool compact = false);
-};
-
 //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
@@ -59,7 +40,7 @@ public:
 };
 
 //Internal class for string -> JsonNode conversion
-class DLL_LINKAGE JsonParser
+class JsonParser
 {
 	std::string errors;     // Contains description of all encountered errors
 	constString input;      // Input data
@@ -98,36 +79,4 @@ public:
 	bool isValid();
 };
 
-//Internal class for Json validation. Mostly compilant with json-schema v4 draft
-namespace Validation
-{
-	/// 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;
-
-		/// 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);
-	};
-
-	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 TValidatorMap = std::unordered_map<std::string, TFieldValidator>;
-
-	/// map of known fields in schema
-	const TValidatorMap & getKnownFieldsFor(JsonNode::JsonType type);
-	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);
-}
-
 VCMI_LIB_NAMESPACE_END

+ 16 - 15
lib/JsonRandom.cpp → lib/json/JsonRandom.cpp

@@ -13,21 +13,22 @@
 
 #include <vstd/StringUtils.h>
 
-#include "JsonNode.h"
-#include "CRandomGenerator.h"
-#include "constants/StringConstants.h"
-#include "VCMI_Lib.h"
-#include "CArtHandler.h"
-#include "CCreatureHandler.h"
-#include "CCreatureSet.h"
-#include "spells/CSpellHandler.h"
-#include "CSkillHandler.h"
-#include "CHeroHandler.h"
-#include "IGameCallback.h"
-#include "gameState/CGameState.h"
-#include "mapObjects/IObjectInterface.h"
-#include "modding/IdentifierStorage.h"
-#include "modding/ModScope.h"
+#include "JsonBonus.h"
+
+#include "../CRandomGenerator.h"
+#include "../constants/StringConstants.h"
+#include "../VCMI_Lib.h"
+#include "../CArtHandler.h"
+#include "../CCreatureHandler.h"
+#include "../CCreatureSet.h"
+#include "../spells/CSpellHandler.h"
+#include "../CSkillHandler.h"
+#include "../CHeroHandler.h"
+#include "../IGameCallback.h"
+#include "../gameState/CGameState.h"
+#include "../mapObjects/IObjectInterface.h"
+#include "../modding/IdentifierStorage.h"
+#include "../modding/ModScope.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 0 - 0
lib/JsonRandom.h → lib/json/JsonRandom.h


+ 407 - 0
lib/json/JsonUtils.cpp

@@ -0,0 +1,407 @@
+/*
+ * JsonUtils.cpp, 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
+ *
+ */
+
+#include "StdInc.h"
+#include "JsonUtils.h"
+
+#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
+
+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];
+
+#if defined(VCMI_IOS)
+	if (!fieldProps["defaultIOS"].isNull())
+		return fieldProps["defaultIOS"];
+#elif defined(VCMI_ANDROID)
+	if (!fieldProps["defaultAndroid"].isNull())
+		return fieldProps["defaultAndroid"];
+#elif defined(VCMI_WINDOWS)
+	if (!fieldProps["defaultWindows"].isNull())
+		return fieldProps["defaultWindows"];
+#endif
+
+#if !defined(VCMI_MOBILE)
+	if (!fieldProps["defaultDesktop"].isNull())
+		return fieldProps["defaultDesktop"];
+#endif
+	return fieldProps["default"];
+}
+
+static void eraseOptionalNodes(JsonNode & node, const JsonNode & schema)
+{
+	assert(schema["type"].String() == "object");
+
+	std::set<std::string> foundEntries;
+
+	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);
+	});
+}
+
+static void minimizeNode(JsonNode & node, const JsonNode & schema)
+{
+	if (schema["type"].String() != "object")
+		return;
+
+	for(const auto & entry : schema["required"].Vector())
+	{
+		const std::string & name = entry.String();
+		minimizeNode(node[name], schema["properties"][name]);
+
+		if (vstd::contains(node.Struct(), name) && node[name] == getDefaultValue(schema, name))
+			node.Struct().erase(name);
+	}
+	eraseOptionalNodes(node, schema);
+}
+
+static void maximizeNode(JsonNode & node, const JsonNode & schema)
+{
+	// "required" entry can only be found in object/struct
+	if (schema["type"].String() != "object")
+		return;
+
+	// check all required entries that have default version
+	for(const auto & entry : schema["required"].Vector())
+	{
+		const std::string & name = entry.String();
+
+		if (node[name].isNull() && !getDefaultValue(schema, name).isNull())
+			node[name] = getDefaultValue(schema, name);
+
+		maximizeNode(node[name], schema["properties"][name]);
+	}
+
+	eraseOptionalNodes(node, schema);
+}
+
+void JsonUtils::minimize(JsonNode & node, const std::string & schemaName)
+{
+	minimizeNode(node, getSchema(schemaName));
+}
+
+void JsonUtils::maximize(JsonNode & node, const std::string & schemaName)
+{
+	maximizeNode(node, getSchema(schemaName));
+}
+
+bool JsonUtils::validate(const JsonNode & node, const std::string & schemaName, const std::string & dataName)
+{
+	std::string log = Validation::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));
+	}
+	return log.empty();
+}
+
+const JsonNode & getSchemaByName(const std::string & name)
+{
+	// cached schemas to avoid loading json data multiple times
+	static std::map<std::string, JsonNode> loadedSchemas;
+
+	if (vstd::contains(loadedSchemas, name))
+		return loadedSchemas[name];
+
+	auto filename = JsonPath::builtin("config/schemas/" + name);
+
+	if (CResourceHandler::get()->existsResource(filename))
+	{
+		loadedSchemas[name] = JsonNode(filename);
+		return loadedSchemas[name];
+	}
+
+	logMod->error("Error: missing schema with name %s!", name);
+	assert(0);
+	return nullNode;
+}
+
+const JsonNode & JsonUtils::getSchema(const std::string & URI)
+{
+	size_t posColon = URI.find(':');
+	size_t posHash  = URI.find('#');
+	std::string filename;
+	if(posColon == std::string::npos)
+	{
+		filename = URI.substr(0, posHash);
+	}
+	else
+	{
+		std::string protocolName = URI.substr(0, posColon);
+		filename = URI.substr(posColon + 1, posHash - posColon - 1) + ".json";
+		if(protocolName != "vcmi")
+		{
+			logMod->error("Error: unsupported URI protocol for schema: %s", URI);
+			return nullNode;
+		}
+	}
+
+	// check if json pointer if present (section after hash in string)
+	if(posHash == std::string::npos || posHash == URI.size() - 1)
+	{
+		auto const & result = getSchemaByName(filename);
+		if (result.isNull())
+			logMod->error("Error: missing schema %s", URI);
+		return result;
+	}
+	else
+	{
+		auto const & result = getSchemaByName(filename).resolvePointer(URI.substr(posHash + 1));
+		if (result.isNull())
+			logMod->error("Error: missing schema %s", URI);
+		return result;
+	}
+}
+
+void JsonUtils::merge(JsonNode & dest, JsonNode & source, bool ignoreOverride, bool copyMeta)
+{
+	if (dest.getType() == JsonNode::JsonType::DATA_NULL)
+	{
+		std::swap(dest, source);
+		return;
+	}
+
+	switch (source.getType())
+	{
+		case JsonNode::JsonType::DATA_NULL:
+		{
+			dest.clear();
+			break;
+		}
+		case JsonNode::JsonType::DATA_BOOL:
+		case JsonNode::JsonType::DATA_FLOAT:
+		case JsonNode::JsonType::DATA_INTEGER:
+		case JsonNode::JsonType::DATA_STRING:
+		case JsonNode::JsonType::DATA_VECTOR:
+		{
+			std::swap(dest, source);
+			break;
+		}
+		case JsonNode::JsonType::DATA_STRUCT:
+		{
+			if(!ignoreOverride && vstd::contains(source.flags, "override"))
+			{
+				std::swap(dest, source);
+			}
+			else
+			{
+				if (copyMeta)
+					dest.meta = source.meta;
+
+				//recursively merge all entries from struct
+				for(auto & node : source.Struct())
+					merge(dest[node.first], node.second, ignoreOverride);
+			}
+		}
+	}
+}
+
+void JsonUtils::mergeCopy(JsonNode & dest, JsonNode source, bool ignoreOverride, bool copyMeta)
+{
+	// uses copy created in stack to safely merge two nodes
+	merge(dest, source, ignoreOverride, copyMeta);
+}
+
+void JsonUtils::inherit(JsonNode & descendant, const JsonNode & base)
+{
+	JsonNode inheritedNode(base);
+	merge(inheritedNode, descendant, true, true);
+	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;
+	return assembleFromFiles(files, isValid);
+}
+
+JsonNode JsonUtils::assembleFromFiles(const std::vector<std::string> & files, bool & isValid)
+{
+	isValid = true;
+	JsonNode result;
+
+	for(const auto & file : files)
+	{
+		bool isValidFile = false;
+		JsonNode section(JsonPath::builtinTODO(file), isValidFile);
+		merge(result, section);
+		isValid |= isValidFile;
+	}
+	return result;
+}
+
+JsonNode JsonUtils::assembleFromFiles(const std::string & filename)
+{
+	JsonNode result;
+	JsonPath resID = JsonPath::builtinTODO(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());
+		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

+ 99 - 0
lib/json/JsonUtils.h

@@ -0,0 +1,99 @@
+/*
+ * JsonUtils.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
+
+#include "JsonNode.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+namespace JsonUtils
+{
+	/**
+	 * @brief recursively merges source into dest, replacing identical fields
+	 * struct : recursively calls this function
+	 * arrays : each entry will be merged recursively
+	 * values : value in source will replace value in dest
+	 * null   : if value in source is present but set to null it will delete entry in dest
+	 * @note this function will destroy data in source
+	 */
+	DLL_LINKAGE void merge(JsonNode & dest, JsonNode & source, bool ignoreOverride = false, bool copyMeta = false);
+
+	/**
+	 * @brief recursively merges source into dest, replacing identical fields
+	 * struct : recursively calls this function
+	 * arrays : each entry will be merged recursively
+	 * values : value in source will replace value in dest
+	 * null   : if value in source is present but set to null it will delete entry in dest
+	 * @note this function will preserve data stored in source by creating copy
+	 */
+	DLL_LINKAGE void mergeCopy(JsonNode & dest, JsonNode source, bool ignoreOverride = false, bool copyMeta = false);
+
+	/** @brief recursively merges descendant into copy of base node
+	* Result emulates inheritance semantic
+	*/
+	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
+	 */
+	DLL_LINKAGE JsonNode assembleFromFiles(const std::vector<std::string> & files);
+	DLL_LINKAGE JsonNode assembleFromFiles(const std::vector<std::string> & files, bool & isValid);
+
+	/// This version loads all files with same name (overridden by mods)
+	DLL_LINKAGE JsonNode assembleFromFiles(const std::string & filename);
+
+	/**
+	 * @brief removes all nodes that are identical to default entry in schema
+	 * @param node - JsonNode to minimize
+	 * @param schemaName - name of schema to use
+	 * @note for minimizing data must be valid against given schema
+	 */
+	DLL_LINKAGE void minimize(JsonNode & node, const std::string & schemaName);
+	/// opposed to minimize, adds all missing, required entries that have default value
+	DLL_LINKAGE void maximize(JsonNode & node, const std::string & schemaName);
+
+	/**
+	* @brief validate node against specified schema
+	* @param node - JsonNode to check
+	* @param schemaName - name of schema to use
+	* @param dataName - some way to identify data (printed in console in case of errors)
+	* @returns true if data in node fully compilant with schema
+	*/
+	DLL_LINKAGE bool validate(const JsonNode & node, const std::string & schemaName, const std::string & dataName);
+
+	/// 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

+ 9 - 590
lib/JsonDetail.cpp → lib/json/JsonValidator.cpp

@@ -1,5 +1,5 @@
 /*
- * JsonDetail.cpp, part of VCMI engine
+ * JsonValidator.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -9,599 +9,18 @@
  */
 
 #include "StdInc.h"
-#include "JsonDetail.h"
+#include "JsonValidator.h"
 
-#include "VCMI_Lib.h"
-#include "TextOperations.h"
+#include "JsonUtils.h"
 
-#include "filesystem/Filesystem.h"
-#include "modding/ModScope.h"
-#include "modding/CModHandler.h"
-#include "ScopeGuard.h"
+#include "../VCMI_Lib.h"
+#include "../filesystem/Filesystem.h"
+#include "../modding/ModScope.h"
+#include "../modding/CModHandler.h"
+#include "../ScopeGuard.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-static const JsonNode nullNode;
-
-template<typename Iterator>
-void JsonWriter::writeContainer(Iterator begin, Iterator end)
-{
-	if (begin == end)
-		return;
-
-	prefix += '\t';
-
-	writeEntry(begin++);
-
-	while (begin != end)
-	{
-		out << (compactMode ? ", " : ",\n");
-		writeEntry(begin++);
-	}
-
-	out << (compactMode ? "" : "\n");
-	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";
-		out << prefix;
-	}
-	writeString(entry->first);
-	out << " : ";
-	writeNode(entry->second);
-}
-
-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";
-		out << prefix;
-	}
-	writeNode(*entry);
-}
-
-void JsonWriter::writeString(const std::string &string)
-{
-	static const std::string escaped = "\"\\\b\f\n\r\t/";
-
-	static const std::array<char, 8> escaped_code = {'\"', '\\', 'b', 'f', 'n', 'r', 't', '/'};
-
-	out <<'\"';
-	size_t pos = 0;
-	size_t start = 0;
-	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()) )
-		{
-			pos++; //write unchanged, next simbol also checked
-		}
-		else
-		{
-			size_t escapedPos = escaped.find(string[pos]);
-
-			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 <<'\"';
-}
-
-void JsonWriter::writeNode(const JsonNode &node)
-{
-	bool originalMode = compactMode;
-	if(compact && !compactMode && node.isCompact())
-		compactMode = true;
-
-	switch(node.getType())
-	{
-		break; case JsonNode::JsonType::DATA_NULL:
-			out << "null";
-
-		break; case JsonNode::JsonType::DATA_BOOL:
-			if (node.Bool())
-				out << "true";
-			else
-				out << "false";
-
-		break; case JsonNode::JsonType::DATA_FLOAT:
-			out << node.Float();
-
-		break; case JsonNode::JsonType::DATA_STRING:
-			writeString(node.String());
-
-		break; case JsonNode::JsonType::DATA_VECTOR:
-			out << "[" << (compactMode ? " " : "\n");
-			writeContainer(node.Vector().begin(), node.Vector().end());
-			out << (compactMode ? " " : prefix) << "]";
-
-		break; case JsonNode::JsonType::DATA_STRUCT:
-			out << "{" << (compactMode ? " " : "\n");
-			writeContainer(node.Struct().begin(), node.Struct().end());
-			out << (compactMode ? " " : prefix) << "}";
-
-		break; case JsonNode::JsonType::DATA_INTEGER:
-			out << node.Integer();
-	}
-
-	compactMode = originalMode;
-}
-
-JsonWriter::JsonWriter(std::ostream & output, bool compact)
-	: out(output), compact(compact)
-{
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-JsonParser::JsonParser(const char * inputString, size_t stringSize):
-	input(inputString, stringSize),
-	lineCount(1),
-	lineStart(0),
-	pos(0)
-{
-}
-
-JsonNode JsonParser::parse(const std::string & fileName)
-{
-	JsonNode root;
-
-	if (input.size() == 0)
-	{
-		error("File is empty", false);
-	}
-	else
-	{
-		if (!TextOperations::isValidUnicodeString(&input[0], input.size()))
-			error("Not a valid UTF-8 file", false);
-
-		extractValue(root);
-		extractWhitespace(false);
-
-		//Warn if there are any non-whitespace symbols left
-		if (pos < input.size())
-			error("Not all file was parsed!", true);
-	}
-
-	if (!errors.empty())
-	{
-		logMod->warn("File %s is not a valid JSON file!", fileName);
-		logMod->warn(errors);
-	}
-	return root;
-}
-
-bool JsonParser::isValid()
-{
-	return errors.empty();
-}
-
-bool JsonParser::extractSeparator()
-{
-	if (!extractWhitespace())
-		return false;
-
-	if ( input[pos] !=':')
-		return error("Separator expected");
-
-	pos++;
-	return true;
-}
-
-bool JsonParser::extractValue(JsonNode &node)
-{
-	if (!extractWhitespace())
-		return false;
-
-	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);
-		default:
-		{
-			if (input[pos] >= '0' && input[pos] <= '9')
-				return extractFloat(node);
-			return error("Value expected!");
-		}
-	}
-}
-
-bool JsonParser::extractWhitespace(bool verbose)
-{
-	while (true)
-	{
-		while(pos < input.size() && static_cast<ui8>(input[pos]) <= ' ')
-		{
-			if (input[pos] == '\n')
-			{
-				lineCount++;
-				lineStart = pos+1;
-			}
-			pos++;
-		}
-		if (pos >= input.size() || input[pos] != '/')
-			break;
-
-		pos++;
-		if (pos == input.size())
-			break;
-		if (input[pos] == '/')
-			pos++;
-		else
-			error("Comments must consist of two slashes!", true);
-
-		while (pos < input.size() && input[pos] != '\n')
-			pos++;
-	}
-
-	if (pos >= input.size() && verbose)
-		return error("Unexpected end of file!");
-	return true;
-}
-
-bool JsonParser::extractEscaping(std::string &str)
-{
-	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);
-	}
-	return true;
-}
-
-bool JsonParser::extractString(std::string &str)
-{
-	if (input[pos] != '\"')
-		return error("String expected!");
-	pos++;
-
-	size_t first = pos;
-
-	while (pos != input.size())
-	{
-		if (input[pos] == '\"') // Correct end of string
-		{
-			str.append( &input[first], pos-first);
-			pos++;
-			return true;
-		}
-		if (input[pos] == '\\') // Escaping
-		{
-			str.append( &input[first], pos-first);
-			pos++;
-			if (pos == input.size())
-				break;
-			extractEscaping(str);
-			first = pos + 1;
-		}
-		if (input[pos] == '\n') // end-of-line
-		{
-			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;
-			error("Illegal character in the string!", true);
-		}
-		pos++;
-	}
-	return error("Unterminated string!");
-}
-
-bool JsonParser::extractString(JsonNode &node)
-{
-	std::string str;
-	if (!extractString(str))
-		return false;
-
-	node.setType(JsonNode::JsonType::DATA_STRING);
-	node.String() = str;
-	return true;
-}
-
-bool JsonParser::extractLiteral(const std::string &literal)
-{
-	if (literal.compare(0, literal.size(), &input[pos], literal.size()) != 0)
-	{
-		while (pos < input.size() && ((input[pos]>'a' && input[pos]<'z')
-								   || (input[pos]>'A' && input[pos]<'Z')))
-			pos++;
-		return error("Unknown literal found", true);
-	}
-
-	pos += literal.size();
-	return true;
-}
-
-bool JsonParser::extractNull(JsonNode &node)
-{
-	if (!extractLiteral("null"))
-		return false;
-
-	node.clear();
-	return true;
-}
-
-bool JsonParser::extractTrue(JsonNode &node)
-{
-	if (!extractLiteral("true"))
-		return false;
-
-	node.Bool() = true;
-	return true;
-}
-
-bool JsonParser::extractFalse(JsonNode &node)
-{
-	if (!extractLiteral("false"))
-		return false;
-
-	node.Bool() = false;
-	return true;
-}
-
-bool JsonParser::extractStruct(JsonNode &node)
-{
-	node.setType(JsonNode::JsonType::DATA_STRUCT);
-	pos++;
-
-	if (!extractWhitespace())
-		return false;
-
-	//Empty struct found
-	if (input[pos] == '}')
-	{
-		pos++;
-		return true;
-	}
-
-	while (true)
-	{
-		if (!extractWhitespace())
-			return 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(!vstd::contains(knownFlags, keyAndFlags[i]))
-				error("Encountered unknown flag #" + keyAndFlags[i], true);
-		}
-
-		if (node.Struct().find(key) != node.Struct().end())
-			error("Duplicate element encountered!", true);
-
-		if (!extractSeparator())
-			return false;
-
-		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]);
-
-		if (input[pos] == '}')
-		{
-			pos++;
-			return true;
-		}
-	}
-}
-
-bool JsonParser::extractArray(JsonNode &node)
-{
-	pos++;
-	node.setType(JsonNode::JsonType::DATA_VECTOR);
-
-	if (!extractWhitespace())
-		return false;
-
-	//Empty array found
-	if (input[pos] == ']')
-	{
-		pos++;
-		return 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);
-
-		if (!extractElement(node.Vector().back(), ']'))
-			return false;
-
-		if (input[pos] == ']')
-		{
-			pos++;
-			return true;
-		}
-	}
-}
-
-bool JsonParser::extractElement(JsonNode &node, char terminator)
-{
-	if (!extractValue(node))
-		return false;
-
-	if (!extractWhitespace())
-		return false;
-
-	bool comma = (input[pos] == ',');
-	if (comma )
-	{
-		pos++;
-		if (!extractWhitespace())
-			return false;
-	}
-
-	if (input[pos] == terminator)
-	{
-		//FIXME: MOD COMPATIBILITY: Too many of these right now, re-enable later
-		//if (comma)
-			//error("Extra comma found!", true);
-		return true;
-	}
-
-	if (!comma)
-		error("Comma expected!", true);
-
-	return true;
-}
-
-bool JsonParser::extractFloat(JsonNode &node)
-{
-	assert(input[pos] == '-' || (input[pos] >= '0' && input[pos] <= '9'));
-	bool negative=false;
-	double result=0;
-	si64 integerPart = 0;
-	bool isFloat = false;
-
-	if (input[pos] == '-')
-	{
-		pos++;
-		negative = true;
-	}
-
-	if (input[pos] < '0' || input[pos] > '9')
-		return error("Number expected!");
-
-	//Extract integer part
-	while (input[pos] >= '0' && input[pos] <= '9')
-	{
-		integerPart = integerPart*10+(input[pos]-'0');
-		pos++;
-	}
-
-	result = static_cast<double>(integerPart);
-
-	if (input[pos] == '.')
-	{
-		//extract fractional part
-		isFloat = true;
-		pos++;
-		double fractMult = 0.1;
-		if (input[pos] < '0' || input[pos] > '9')
-			return error("Decimal part expected!");
-
-		while (input[pos] >= '0' && input[pos] <= '9')
-		{
-			result = result + fractMult*(input[pos]-'0');
-			fractMult /= 10;
-			pos++;
-		}
-	}
-
-	if(input[pos] == 'e')
-	{
-		//extract exponential part
-		pos++;
-		isFloat = true;
-		bool powerNegative = false;
-		double power = 0;
-
-		if(input[pos] == '-')
-		{
-			pos++;
-			powerNegative = true;
-		}
-		else if(input[pos] == '+')
-		{
-			pos++;
-		}
-
-		if (input[pos] < '0' || input[pos] > '9')
-			return error("Exponential part expected!");
-
-		while (input[pos] >= '0' && input[pos] <= '9')
-		{
-			power = power*10 + (input[pos]-'0');
-			pos++;
-		}
-
-		if(powerNegative)
-			power = -power;
-
-		result *= std::pow(10, power);
-	}
-
-	if(isFloat)
-	{
-		if(negative)
-			result = -result;
-
-		node.setType(JsonNode::JsonType::DATA_FLOAT);
-		node.Float() = result;
-	}
-	else
-	{
-		if(negative)
-			integerPart = -integerPart;
-
-		node.setType(JsonNode::JsonType::DATA_INTEGER);
-		node.Integer() = integerPart;
-	}
-
-	return true;
-}
-
-bool JsonParser::error(const std::string &message, bool warning)
-{
-	std::ostringstream stream;
-	std::string type(warning?" warning: ":" error: ");
-
-	stream << "At line " << lineCount << ", position "<<pos-lineStart
-		   << type << message <<"\n";
-	errors += stream.str();
-
-	return warning;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
 //TODO: integer support
 
 static const std::unordered_map<std::string, JsonNode::JsonType> stringToType =
@@ -1259,7 +678,7 @@ namespace Validation
 
 	const TFormatMap & getKnownFormats()
 	{
-		static const TFormatMap knownFormats = createFormatMap();
+		static TFormatMap knownFormats = createFormatMap();
 		return knownFormats;
 	}
 

+ 49 - 0
lib/json/JsonValidator.h

@@ -0,0 +1,49 @@
+/*
+ * JsonValidator.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
+
+#include "JsonNode.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+
+//Internal class for Json validation. Mostly compilant with json-schema v4 draft
+namespace Validation
+{
+	/// 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;
+
+		/// 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);
+	};
+
+	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 TValidatorMap = std::unordered_map<std::string, TFieldValidator>;
+
+	/// map of known fields in schema
+	const TValidatorMap & getKnownFieldsFor(JsonNode::JsonType type);
+	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);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 144 - 0
lib/json/JsonWriter.cpp

@@ -0,0 +1,144 @@
+/*
+ * JsonWriter.cpp, 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
+ *
+ */
+
+#include "StdInc.h"
+#include "JsonWriter.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+template<typename Iterator>
+void JsonWriter::writeContainer(Iterator begin, Iterator end)
+{
+	if (begin == end)
+		return;
+
+	prefix += '\t';
+
+	writeEntry(begin++);
+
+	while (begin != end)
+	{
+		out << (compactMode ? ", " : ",\n");
+		writeEntry(begin++);
+	}
+
+	out << (compactMode ? "" : "\n");
+	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";
+		out << prefix;
+	}
+	writeString(entry->first);
+	out << " : ";
+	writeNode(entry->second);
+}
+
+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";
+		out << prefix;
+	}
+	writeNode(*entry);
+}
+
+void JsonWriter::writeString(const std::string &string)
+{
+	static const std::string escaped = "\"\\\b\f\n\r\t/";
+
+	static const std::array<char, 8> escaped_code = {'\"', '\\', 'b', 'f', 'n', 'r', 't', '/'};
+
+	out <<'\"';
+	size_t pos = 0;
+	size_t start = 0;
+	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()) )
+		{
+			pos++; //write unchanged, next simbol also checked
+		}
+		else
+		{
+			size_t escapedPos = escaped.find(string[pos]);
+
+			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 <<'\"';
+}
+
+void JsonWriter::writeNode(const JsonNode &node)
+{
+	bool originalMode = compactMode;
+	if(compact && !compactMode && node.isCompact())
+		compactMode = true;
+
+	switch(node.getType())
+	{
+		break; case JsonNode::JsonType::DATA_NULL:
+			out << "null";
+
+		break; case JsonNode::JsonType::DATA_BOOL:
+			if (node.Bool())
+				out << "true";
+			else
+				out << "false";
+
+		break; case JsonNode::JsonType::DATA_FLOAT:
+			out << node.Float();
+
+		break; case JsonNode::JsonType::DATA_STRING:
+			writeString(node.String());
+
+		break; case JsonNode::JsonType::DATA_VECTOR:
+			out << "[" << (compactMode ? " " : "\n");
+			writeContainer(node.Vector().begin(), node.Vector().end());
+			out << (compactMode ? " " : prefix) << "]";
+
+		break; case JsonNode::JsonType::DATA_STRUCT:
+			out << "{" << (compactMode ? " " : "\n");
+			writeContainer(node.Struct().begin(), node.Struct().end());
+			out << (compactMode ? " " : prefix) << "}";
+
+		break; case JsonNode::JsonType::DATA_INTEGER:
+			out << node.Integer();
+	}
+
+	compactMode = originalMode;
+}
+
+JsonWriter::JsonWriter(std::ostream & output, bool compact)
+	: out(output), compact(compact)
+{
+}
+
+VCMI_LIB_NAMESPACE_END

+ 35 - 0
lib/json/JsonWriter.h

@@ -0,0 +1,35 @@
+/*
+ * JsonWriter.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
+
+#include "JsonNode.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class JsonWriter
+{
+	//prefix for each line (tabulation)
+	std::string prefix;
+	std::ostream & out;
+	//sets whether compact nodes are written in single-line format
+	bool compact;
+	//tracks whether we are currently using single-line format
+	bool compactMode = false;
+public:
+	template<typename Iterator>
+	void writeContainer(Iterator begin, Iterator end);
+	void writeEntry(JsonMap::const_iterator entry);
+	void writeEntry(JsonVector::const_iterator entry);
+	void writeString(const std::string & string);
+	void writeNode(const JsonNode & node);
+	JsonWriter(std::ostream & output, bool compact);
+};
+
+VCMI_LIB_NAMESPACE_END

+ 2 - 1
lib/mapObjectConstructors/AObjectTypeHandler.cpp

@@ -13,8 +13,9 @@
 
 #include "IObjectInfo.h"
 #include "../CGeneralTextHandler.h"
-#include "../modding/IdentifierStorage.h"
 #include "../VCMI_Lib.h"
+#include "../json/JsonUtils.h"
+#include "../modding/IdentifierStorage.h"
 #include "../mapObjects/CGObjectInstance.h"
 #include "../mapObjects/ObjectTemplate.h"
 

+ 1 - 1
lib/mapObjectConstructors/AObjectTypeHandler.h

@@ -9,9 +9,9 @@
  */
 #pragma once
 
+#include "../constants/EntityIdentifiers.h"
 #include "RandomMapInfo.h"
 #include "SObjectSounds.h"
-#include "../JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/mapObjectConstructors/CBankInstanceConstructor.cpp

@@ -10,7 +10,7 @@
 #include "StdInc.h"
 #include "CBankInstanceConstructor.h"
 
-#include "../JsonRandom.h"
+#include "../json/JsonRandom.h"
 #include "../CGeneralTextHandler.h"
 #include "../IGameCallback.h"
 #include "../CRandomGenerator.h"

+ 1 - 0
lib/mapObjectConstructors/CBankInstanceConstructor.h

@@ -14,6 +14,7 @@
 
 #include "../CCreatureSet.h"
 #include "../ResourceSet.h"
+#include "../json/JsonNode.h"
 #include "../mapObjects/CBank.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 1
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -12,12 +12,12 @@
 
 #include "../filesystem/Filesystem.h"
 #include "../filesystem/CBinaryReader.h"
+#include "../json/JsonUtils.h"
 #include "../VCMI_Lib.h"
 #include "../GameConstants.h"
 #include "../constants/StringConstants.h"
 #include "../CGeneralTextHandler.h"
 #include "../GameSettings.h"
-#include "../JsonNode.h"
 #include "../CSoundBase.h"
 
 #include "../mapObjectConstructors/CBankInstanceConstructor.h"

+ 2 - 1
lib/mapObjectConstructors/CObjectClassesHandler.h

@@ -9,8 +9,9 @@
  */
 #pragma once
 
+#include "../constants/EntityIdentifiers.h"
 #include "../IHandlerBase.h"
-#include "../JsonNode.h"
+#include "../json/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/mapObjectConstructors/CommonConstructors.cpp

@@ -14,7 +14,7 @@
 #include "../CHeroHandler.h"
 #include "../CTownHandler.h"
 #include "../IGameCallback.h"
-#include "../JsonRandom.h"
+#include "../json/JsonRandom.h"
 #include "../constants/StringConstants.h"
 #include "../TerrainHandler.h"
 #include "../VCMI_Lib.h"

+ 1 - 1
lib/mapObjectConstructors/DwellingInstanceConstructor.cpp

@@ -12,7 +12,7 @@
 
 #include "../CCreatureHandler.h"
 #include "../CGeneralTextHandler.h"
-#include "../JsonRandom.h"
+#include "../json/JsonRandom.h"
 #include "../VCMI_Lib.h"
 #include "../mapObjects/CGDwelling.h"
 #include "../modding/IdentifierStorage.h"

+ 2 - 0
lib/mapObjectConstructors/DwellingInstanceConstructor.h

@@ -10,6 +10,8 @@
 #pragma once
 
 #include "CDefaultObjectTypeHandler.h"
+
+#include "../json/JsonNode.h"
 #include "../mapObjects/CGDwelling.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 0
lib/mapObjectConstructors/HillFortInstanceConstructor.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "CDefaultObjectTypeHandler.h"
+#include "../json/JsonNode.h"
 #include "../mapObjects/MiscObjects.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 0
lib/mapObjectConstructors/ShipyardInstanceConstructor.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "CDefaultObjectTypeHandler.h"
+#include "../json/JsonNode.h"
 #include "../mapObjects/MiscObjects.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 0
lib/mapObjects/CGHeroInstance.cpp

@@ -32,6 +32,7 @@
 #include "../StartInfo.h"
 #include "CGTownInstance.h"
 #include "../campaign/CampaignState.h"
+#include "../json/JsonBonus.h"
 #include "../pathfinder/TurnInfo.h"
 #include "../serializer/JsonSerializeFormat.h"
 #include "../mapObjectConstructors/AObjectTypeHandler.h"

+ 1 - 1
lib/mapObjects/CObjectHandler.cpp

@@ -13,7 +13,7 @@
 
 #include "CGObjectInstance.h"
 #include "../filesystem/ResourcePath.h"
-#include "../JsonNode.h"
+#include "../json/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 0 - 1
lib/mapObjects/ObjectTemplate.cpp

@@ -16,7 +16,6 @@
 #include "../GameConstants.h"
 #include "../constants/StringConstants.h"
 #include "../CGeneralTextHandler.h"
-#include "../JsonNode.h"
 #include "../TerrainHandler.h"
 
 #include "../mapObjectConstructors/CRewardableConstructor.h"

+ 1 - 0
lib/mapping/CMapHeader.cpp

@@ -15,6 +15,7 @@
 #include "../VCMI_Lib.h"
 #include "../CTownHandler.h"
 #include "../CGeneralTextHandler.h"
+#include "../json/JsonUtils.h"
 #include "../modding/CModHandler.h"
 #include "../CHeroHandler.h"
 #include "../Languages.h"

+ 2 - 0
lib/mapping/CMapHeader.h

@@ -10,6 +10,8 @@
 
 #pragma once
 
+#include "../constants/EntityIdentifiers.h"
+#include "../constants/Enumerations.h"
 #include "../constants/VariantIdentifier.h"
 #include "../modding/CModInfo.h"
 #include "../LogicalExpression.h"

+ 1 - 0
lib/mapping/CMapService.cpp

@@ -10,6 +10,7 @@
 #include "StdInc.h"
 #include "CMapService.h"
 
+#include "../json/JsonUtils.h"
 #include "../filesystem/Filesystem.h"
 #include "../filesystem/CBinaryReader.h"
 #include "../filesystem/CCompressedStream.h"

+ 0 - 1
lib/mapping/MapEditUtils.cpp

@@ -12,7 +12,6 @@
 #include "MapEditUtils.h"
 
 #include "../filesystem/Filesystem.h"
-#include "../JsonNode.h"
 #include "../TerrainHandler.h"
 #include "CMap.h"
 #include "CMapOperation.h"

+ 2 - 2
lib/mapping/MapFormatJson.cpp

@@ -13,7 +13,7 @@
 
 #include "../filesystem/CInputStream.h"
 #include "../filesystem/COutputStream.h"
-#include "../JsonDetail.h"
+#include "../json/JsonWriter.h"
 #include "CMap.h"
 #include "MapFormat.h"
 #include "../ArtifactUtils.h"
@@ -1201,7 +1201,7 @@ CMapSaverJson::~CMapSaverJson() = default;
 void CMapSaverJson::addToArchive(const JsonNode & data, const std::string & filename)
 {
 	std::ostringstream out;
-	JsonWriter writer(out);
+	JsonWriter writer(out, false);
 	writer.writeNode(data);
 	out.flush();
 

+ 0 - 1
lib/mapping/MapFormatJson.h

@@ -11,7 +11,6 @@
 #pragma once
 
 #include "CMapService.h"
-#include "../JsonNode.h"
 
 #include "../filesystem/CZipSaver.h"
 #include "../filesystem/CZipLoader.h"

+ 0 - 1
lib/mapping/MapIdentifiersH3M.cpp

@@ -11,7 +11,6 @@
 #include "StdInc.h"
 #include "MapIdentifiersH3M.h"
 
-#include "../JsonNode.h"
 #include "../VCMI_Lib.h"
 #include "../CTownHandler.h"
 #include "../CHeroHandler.h"

+ 1 - 0
lib/modding/CModHandler.cpp

@@ -25,6 +25,7 @@
 #include "../ScriptHandler.h"
 #include "../constants/StringConstants.h"
 #include "../filesystem/Filesystem.h"
+#include "../json/JsonUtils.h"
 #include "../spells/CSpellHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 1
lib/modding/CModInfo.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "../JsonNode.h"
+#include "../json/JsonNode.h"
 #include "ModVerificationInfo.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 1 - 0
lib/modding/ContentTypeHandler.cpp

@@ -31,6 +31,7 @@
 #include "../ScriptHandler.h"
 #include "../constants/StringConstants.h"
 #include "../TerrainHandler.h"
+#include "../json/JsonUtils.h"
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
 #include "../rmg/CRmgTemplateStorage.h"
 #include "../spells/CSpellHandler.h"

+ 1 - 1
lib/modding/ContentTypeHandler.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "../JsonNode.h"
+#include "../json/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 0 - 1
lib/modding/IdentifierStorage.cpp

@@ -13,7 +13,6 @@
 #include "CModHandler.h"
 #include "ModScope.h"
 
-#include "../JsonNode.h"
 #include "../VCMI_Lib.h"
 #include "../constants/StringConstants.h"
 #include "../spells/CSpellHandler.h"

+ 1 - 1
lib/networkPacks/BattleChanges.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "JsonNode.h"
+#include "../json/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 2 - 2
lib/networkPacks/EntityChanges.h

@@ -9,9 +9,9 @@
  */
 #pragma once
 
-#include <vcmi/Metatype.h>
+#include "../json/JsonNode.h"
 
-#include "../JsonNode.h"
+#include <vcmi/Metatype.h>
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
lib/rewardable/Info.cpp

@@ -17,7 +17,7 @@
 
 #include "../CGeneralTextHandler.h"
 #include "../IGameCallback.h"
-#include "../JsonRandom.h"
+#include "../json/JsonRandom.h"
 #include "../mapObjects/IObjectInterface.h"
 #include "../modding/IdentifierStorage.h"
 #include "../CRandomGenerator.h"

+ 1 - 1
lib/rewardable/Info.h

@@ -10,7 +10,7 @@
 
 #pragma once
 
-#include "../JsonNode.h"
+#include "../json/JsonNode.h"
 #include "../mapObjectConstructors/IObjectInfo.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 0 - 2
lib/serializer/JsonDeserializer.cpp

@@ -10,8 +10,6 @@
 #include "StdInc.h"
 #include "JsonDeserializer.h"
 
-#include "../JsonNode.h"
-
 #include <vstd/StringUtils.h>
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 0 - 2
lib/serializer/JsonSerializeFormat.cpp

@@ -10,8 +10,6 @@
 #include "StdInc.h"
 #include "JsonSerializeFormat.h"
 
-#include "../JsonNode.h"
-
 VCMI_LIB_NAMESPACE_BEGIN
 
 //JsonSerializeHelper

+ 2 - 1
lib/serializer/JsonSerializeFormat.h

@@ -9,7 +9,8 @@
  */
 #pragma once
 
-#include "../JsonNode.h"
+#include "../constants/IdentifierBase.h"
+#include "../json/JsonNode.h"
 #include "../modding/IdentifierStorage.h"
 #include "../modding/ModScope.h"
 #include "../VCMI_Lib.h"

+ 0 - 2
lib/serializer/JsonSerializer.cpp

@@ -10,8 +10,6 @@
 #include "StdInc.h"
 #include "JsonSerializer.h"
 
-#include "../JsonNode.h"
-
 VCMI_LIB_NAMESPACE_BEGIN
 
 JsonSerializer::JsonSerializer(const IInstanceResolver * instanceResolver_, JsonNode & root_):

+ 1 - 2
lib/serializer/JsonUpdater.cpp

@@ -10,10 +10,9 @@
 #include "StdInc.h"
 #include "JsonUpdater.h"
 
-#include "../JsonNode.h"
-
 #include "../bonuses/CBonusSystemNode.h"
 #include "../bonuses/Bonus.h"
+#include "../json/JsonBonus.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 2 - 1
lib/spells/CSpellHandler.cpp

@@ -25,7 +25,8 @@
 #include "../battle/BattleInfo.h"
 #include "../battle/CBattleInfoCallback.h"
 #include "../battle/Unit.h"
-
+#include "../json/JsonBonus.h"
+#include "../json/JsonUtils.h"
 #include "../mapObjects/CGHeroInstance.h" //todo: remove
 #include "../serializer/CSerializer.h"
 #include "../modding/IdentifierStorage.h"

+ 1 - 1
lib/spells/CSpellHandler.h

@@ -13,7 +13,6 @@
 #include <vcmi/spells/Spell.h>
 #include <vcmi/spells/Service.h>
 #include <vcmi/spells/Magic.h>
-#include "../JsonNode.h"
 #include "../IHandlerBase.h"
 #include "../ConstTransitivePtr.h"
 #include "../int3.h"
@@ -21,6 +20,7 @@
 #include "../battle/BattleHex.h"
 #include "../bonuses/Bonus.h"
 #include "../filesystem/ResourcePath.h"
+#include "../json/JsonNode.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

部分文件因文件數量過多而無法顯示