/* * JsonNode.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 class JsonNode; typedef std::map JsonMap; typedef std::vector JsonVector; DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const JsonNode &node); struct Bonus; class ResourceID; class DLL_LINKAGE JsonNode { public: enum JsonType { DATA_NULL, DATA_BOOL, DATA_FLOAT, DATA_STRING, DATA_VECTOR, DATA_STRUCT }; private: union JsonData { bool Bool; double Float; std::string* String; JsonVector* Vector; JsonMap* Struct; }; JsonType type; JsonData data; public: /// free to use metadata field std::string meta; //Create empty node JsonNode(JsonType Type = DATA_NULL); //Create tree from Json-formatted input explicit JsonNode(const char * data, size_t datasize); //Create tree from JSON file explicit JsonNode(ResourceID && fileURI); //Copy c-tor JsonNode(const JsonNode ©); ~JsonNode(); void swap(JsonNode &b); JsonNode& operator =(JsonNode node); bool operator == (const JsonNode &other) const; bool operator != (const JsonNode &other) const; void setMeta(std::string metadata, bool recursive = true); /// Convert node to another type. Converting to nullptr will clear all data void setType(JsonType Type); JsonType getType() const; bool isNull() const; /// removes all data from node and sets type to null void clear(); /// non-const accessors, node will change type on type mismatch bool & Bool(); double & Float(); std::string & String(); JsonVector & Vector(); JsonMap & Struct(); /// const accessors, will cause assertion failure on type mismatch const bool & Bool() const; const double & Float() const; const std::string & String() const; const JsonVector & Vector() const; const JsonMap & Struct() const; /// returns resolved "json pointer" (string in format "/path/to/node") const JsonNode & resolvePointer(const std::string & jsonPointer) const; JsonNode & resolvePointer(const std::string & jsonPointer); /// convert json tree into specified type. Json tree must have same type as Type /// Valid types: bool, string, any numeric, map and vector /// example: convertTo< std::map< std::vector > >(); template Type convertTo() const; //operator [], for structs only - get child node by name JsonNode & operator[](std::string child); const JsonNode & operator[](std::string child) const; template void serialize(Handler &h, const int version) { h & meta; // simple saving - save json in its string interpretation if (h.saving) { std::ostringstream stream; stream << *this; std::string str = stream.str(); h & str; } else { std::string str; h & str; JsonNode(str.c_str(), str.size()).swap(*this); } } }; namespace JsonUtils { /** * @brief parse short bonus format, excluding type * @note sets duration to Permament */ DLL_LINKAGE void parseTypedBonusShort(const JsonVector &source, Bonus *dest); /// DLL_LINKAGE Bonus * parseBonus (const JsonVector &ability_vec); DLL_LINKAGE Bonus * parseBonus (const JsonNode &bonus); DLL_LINKAGE void unparseBonus (JsonNode &node, const Bonus * bonus); DLL_LINKAGE void resolveIdentifier (si32 &var, const JsonNode &node, std::string name); DLL_LINKAGE void resolveIdentifier (const JsonNode &node, si32 &var); /** * @brief recursivly 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); /** * @brief recursivly 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); /** * @brief generate one Json structure from multiple files * @param files - list of filenames with parts of json structure */ DLL_LINKAGE JsonNode assembleFromFiles(std::vector files); /// This version loads all files with same name (overriden by mods) DLL_LINKAGE JsonNode assembleFromFiles(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, std::string schemaName); /// opposed to minimize, adds all missing, required entries that have default value DLL_LINKAGE void maximize(JsonNode & node, 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, std::string schemaName, std::string dataName); /// get schema by json URI: vcmi:# /// example: schema "vcmi:settings" is used to check user settings DLL_LINKAGE const JsonNode & getSchema(std::string URI); } ////////////////////////////////////////////////////////////////////////////////////////////////////// // End of public section of the file. Anything below should be only used internally in JsonNode.cpp // ////////////////////////////////////////////////////////////////////////////////////////////////////// namespace JsonDetail { // convertion helpers for JsonNode::convertTo (partial template function instantiation is illegal in c++) template struct JsonConvImpl; template struct JsonConvImpl { static T convertImpl(const JsonNode & node) { return T((int)node.Float()); } }; template struct JsonConvImpl { static T convertImpl(const JsonNode & node) { return node.Float(); } }; template struct JsonConverter { static Type convert(const JsonNode & node) { ///this should be triggered only for numeric types and enums static_assert(boost::mpl::or_, std::is_enum, boost::is_class >::value, "Unsupported type for JsonNode::convertTo()!"); return JsonConvImpl, boost::is_class >::value >::convertImpl(node); } }; template struct JsonConverter > { static std::map convert(const JsonNode & node) { std::map ret; for (const JsonMap::value_type & entry : node.Struct()) { ret.insert(entry.first, entry.second.convertTo()); } return ret; } }; template struct JsonConverter > { static std::set convert(const JsonNode & node) { std::set ret; for(const JsonVector::value_type & entry : node.Vector()) { ret.insert(entry.convertTo()); } return ret; } }; template struct JsonConverter > { static std::vector convert(const JsonNode & node) { std::vector ret; for (const JsonVector::value_type & entry: node.Vector()) { ret.push_back(entry.convertTo()); } return ret; } }; template<> struct JsonConverter { static std::string convert(const JsonNode & node) { return node.String(); } }; template<> struct JsonConverter { static bool convert(const JsonNode & node) { return node.Bool(); } }; class JsonWriter { //prefix for each line (tabulation) std::string prefix; std::ostream &out; public: template 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, const JsonNode &node); }; //Tiny string class that uses const char* as data for speed, members are private //for ease of debugging and some compatibility with std::string class constString { const char *data; const size_t datasize; public: constString(const char * inputString, size_t stringSize): data(inputString), datasize(stringSize) { } inline size_t size() const { return datasize; }; inline const char& operator[] (size_t position) { assert (position < datasize); return data[position]; } }; //Internal class for string -> JsonNode conversion class JsonParser { std::string errors; // Contains description of all encountered errors constString input; // Input data ui32 lineCount; // Currently parsed line, starting from 1 size_t lineStart; // Position of current line start size_t pos; // Current position of parser //Helpers bool extractEscaping(std::string &str); bool extractLiteral(const std::string &literal); bool extractString(std::string &string); bool extractWhitespace(bool verbose = true); bool extractSeparator(); bool extractElement(JsonNode &node, char terminator); //Methods for extracting JSON data bool extractArray(JsonNode &node); bool extractFalse(JsonNode &node); bool extractFloat(JsonNode &node); bool extractNull(JsonNode &node); bool extractString(JsonNode &node); bool extractStruct(JsonNode &node); bool extractTrue(JsonNode &node); bool extractValue(JsonNode &node); //Add error\warning message to list bool error(const std::string &message, bool warning=false); public: JsonParser(const char * inputString, size_t stringSize); /// do actual parsing. filename is name of file that will printed to console if any errors were found JsonNode parse(std::string fileName); }; //Internal class for Json validation. Mostly compilant with json-schema v4 draft class JsonValidator { // 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 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 usedSchemas; /// helpers for other validation methods std::string validateVectorItem(const JsonVector items, const JsonNode & schema, const JsonNode & additional, size_t index); std::string validateStructItem(const JsonNode &node, const JsonNode &schema, const JsonNode & additional, std::string nodeName); std::string validateEnum(const JsonNode &node, const JsonVector &enumeration); std::string validateNodeType(const JsonNode &node, const JsonNode &schema); std::string validatesSchemaList(const JsonNode &node, const JsonNode &schemas, std::string errorMsg, std::function isValid); /// contains all type-independent checks std::string validateNode(const JsonNode &node, const JsonNode &schema); /// type-specific checks std::string validateVector(const JsonNode &node, const JsonNode &schema); std::string validateStruct(const JsonNode &node, const JsonNode &schema); std::string validateString(const JsonNode &node, const JsonNode &schema); std::string validateNumber(const JsonNode &node, const JsonNode &schema); /// validation of root node of both schema and input data std::string validateRoot(const JsonNode &node, std::string schemaName); /// add error message to list and return false std::string fail(const std::string &message); public: /// returns true if parsed data is fully compilant with schema bool validate(const JsonNode &root, std::string schemaName, std::string name); }; } // namespace JsonDetail template Type JsonNode::convertTo() const { return JsonDetail::JsonConverter::convert(*this); }