#pragma once #include #include #include #include #include #include #include namespace json { /** * @brief Unserialize a JSON document into a * Value. */ class Reader { public: using Char = char; using Location = Char const*; /** * @brief Constructs a Reader allowing all features * for parsing. */ Reader() = default; /** * @brief Read a Value from a JSON * document. @param document UTF-8 encoded string containing the document to * read. @param root [out] Contains the root value of the document if it was * successfully parsed. * @return @c true if the document was successfully parsed, @c false if an * error occurred. */ bool parse(std::string const& document, Value& root); /** * @brief Read a Value from a JSON * document. @param document UTF-8 encoded string containing the document to * read. @param root [out] Contains the root value of the document if it was * successfully parsed. * @return @c true if the document was successfully parsed, @c false if an * error occurred. */ bool parse(char const* beginDoc, char const* endDoc, Value& root); /** * @brief Parse from input stream. * @see json::operator>>(std::istream&, json::Value&). */ bool parse(std::istream& is, Value& root); /** * @brief Read a Value from a JSON buffer * sequence. @param root [out] Contains the root value of the document if it * was successfully parsed. @param UTF-8 encoded buffer sequence. @return @c * true if the buffer was successfully parsed, @c false if an error * occurred. */ template bool parse(Value& root, BufferSequence const& bs); /** * @brief Returns a user friendly string that list errors in the parsed * document. @return Formatted error message with the list of errors with * their location in the parsed document. An empty string is returned if no * error occurred during parsing. */ [[nodiscard]] std::string getFormattedErrorMessages() const; static constexpr unsigned kNestLimit{25}; private: enum class TokenType { EndOfStream = 0, ObjectBegin, ObjectEnd, ArrayBegin, ArrayEnd, String, Integer, Double, True, False, Null, ArraySeparator, MemberSeparator, Comment, Error }; class Token { public: explicit Token() = default; TokenType type; Location start; Location end; }; class ErrorInfo { public: explicit ErrorInfo() = default; Token token{}; std::string message; Location extra{}; }; using Errors = std::deque; bool expectToken(TokenType type, Token& token, char const* message); bool readToken(Token& token); void skipSpaces(); bool match(Location pattern, int patternLength); bool readComment(); bool readCStyleComment(); bool readCppStyleComment(); bool readString(); Reader::TokenType readNumber(); bool readValue(unsigned depth); bool readObject(Token& token, unsigned depth); bool readArray(Token& token, unsigned depth); bool decodeNumber(Token& token); bool decodeString(Token& token); bool decodeString(Token& token, std::string& decoded); bool decodeDouble(Token& token); bool decodeUnicodeCodePoint(Token& token, Location& current, Location end, unsigned int& unicode); bool decodeUnicodeEscapeSequence( Token& token, Location& current, Location end, unsigned int& unicode); bool addError(std::string const& message, Token& token, Location extra = nullptr); bool recoverFromError(TokenType skipUntilToken); bool addErrorAndRecover(std::string const& message, Token& token, TokenType skipUntilToken); void skipUntilSpace(); Value& currentValue(); Char getNextChar(); void getLocationLineAndColumn(Location location, int& line, int& column) const; std::string getLocationLineAndColumn(Location location) const; void skipCommentTokens(Token& token); using Nodes = std::stack; Nodes nodes_; Errors errors_; std::string document_; Location begin_{}; Location end_{}; Location current_{}; Location lastValueEnd_{}; Value* lastValue_{}; }; template bool Reader::parse(Value& root, BufferSequence const& bs) { using namespace boost::asio; std::string s; s.reserve(buffer_size(bs)); for (auto const& b : bs) s.append(static_cast(b.data()), buffer_size(b)); return parse(s, root); } /** * @brief Read from 'sin' into 'root'. * * Always keep comments from the input JSON. * * This can be used to read a file into a particular sub-object. * For example: * @code * json::Value root; * cin >> root["dir"]["file"]; * cout << root; * @endcode * Result: * @verbatim * { * "dir": { * "file": { * // The input stream JSON would be nested here. * } * } * } * @endverbatim * @throws std::exception on parse error. * @see json::operator<<() */ std::istream& operator>>(std::istream&, Value&); } // namespace json