blob: c96f3d44a3418a6436f68e9a4cb26a9594e222c2 [file] [edit]
// Copyright 2025 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <functional>
#include <string>
#include "hash_map.h"
#include "string_piece.h"
#include "util.h"
/// A minimalist JSON parser, designed for performance and simplicity, and as
/// such, with a few intentional limitations:
///
/// - Error reporting is minimal and does not include line or column numbers.
/// This is a trade-off to keep the implementation simple and fast.
///
/// - It uses StringPiece for all string-like tokens to avoid dynamic memory
/// allocations during parsing. The caller is responsible for ensuring the
/// underlying input buffer remains valid.
///
/// - It is a streaming parser, and does not support random access.
///
/// - It does not support comments (i.e. JSON, not JSON5).
///
struct JsonParser {
/// A simple enum describing supported JSON token types.
enum TokenType {
kNone,
kEndOfInput,
kParsingError,
kObjectStart,
kObjectEnd,
kArrayStart,
kArrayEnd,
kComma,
kColon,
kString,
kTrue,
kFalse,
kNull,
kNumber,
};
/// Constructor takes input as a single StringPiece.
JsonParser(StringPiece input);
/// Reset the state of the parser for new input, clearing the error.
void Reset(StringPiece input);
/// Return true if an error was encountered during parsing. This
/// condition is sticky until the next Reset() call.
bool HasError() const { return !error_.empty(); }
/// Return true if no error was encountered during parsing.
bool IsOk() const { return error_.empty(); }
/// Return error string. This will be empty if no error occurred.
const std::string& error() const { return error_; }
/// Take the error string, moving it out of the parser. State
/// is undefined after this call, but useful to pass the error
/// to the caller before returning.
std::string TakeError() { return std::move(error_); }
/// Set the error message. This forces future PeekToken() to return
/// kParsingError, and future HasError() calls to return true.
/// Always return false to simplify error handling.
bool SetError(std::string error);
/// A SetError() variant that can format arguments.
bool SetErrorFormat(const char* fmt, ...);
/// Used internally to set the error in an ExpectXXX() method and return
/// false to simplify error handling.
bool SetExpectedError(const char* expected);
/// Return type of current token. This will be kEndOfInput if there are no
/// more tokens from the input, or kParsingError if an error occurred.
TokenType PeekTokenType();
/// Return the current token as a StringPiece. This will be empty
/// if PeekTokenType() returns kEndOfInput or kParsingError.
StringPiece PeekToken();
/// Skip the current token. This is a no-op if an error occurred.
/// Useful after checking the current token type with PeekTokenType().
void SkipToken();
/// Return a string representation of a given token.
static std::string ToString(TokenType type);
/// Return true if the next token matches the start of a JSON value.
/// or false if it corresponds to an array end, object end, or malformed
/// input.
bool PeekValueStart();
/// Verify that the current token is the start of a JSON value, but
/// do not skip over it. If not, set an error and return false.
bool ExpectValueStartNoSkip();
/// For all ExpectXXXX() methods below, if the current token is of a certain
/// type, skip over it then return true. Otherwise, set error then return
/// false.
bool ExpectColon() { return ExpectTokenType(kColon, "expecting colon"); }
bool ExpectComma() { return ExpectTokenType(kComma, "expecting comma"); }
bool ExpectNull() { return ExpectTokenType(kNull, "expecting comma"); }
bool ExpectObjectStart() {
return ExpectTokenType(kObjectStart, "expecting object start");
}
bool ExpectObjectEnd() {
return ExpectTokenType(kObjectEnd, "expecting object end");
}
bool ExpectArrayStart() {
return ExpectTokenType(kArrayStart, "expecting array start");
}
bool ExpectArrayEnd() {
return ExpectTokenType(kArrayEnd, "expecting array end");
}
/// If the current token is a number, set |*number| to its value,
/// skip over it then return true. Otherwise set error then return false.
bool ExpectNumber(StringPiece* number);
/// If the current token is a decimal number, set |*int_value| to its value,
/// skip over it then return true. Otherwise set error then return false.
bool ExpectInteger(int64_t* int_value);
/// If the current token is a floating point number, set |*double_value|
// to its value, skip over it then return true. Otherwise set error then
// return false.
bool ExpectDouble(double* double_value);
/// If the current token is a boolean, set |*flag_value| to its value,
/// skip over it then return true. Otherwise set error then return false.
bool ExpectBoolean(bool* flag_value);
/// If the current token is a string, set |*encoded_str| to its undecoded
/// value, without opening and closing quotes, skip over it, then return true.
/// Otherwise set error then return false.
bool ExpectEncodedString(StringPiece* encoded_str);
/// Same as ExpectedEncodedString, but set |*str| to the decoded value.
bool ExpectString(std::string* str);
/// Expect an array of strings. On success sets |*str_list| and return true.
/// On failure set parser error then return false.
bool ExpectStringList(std::vector<std::string>* str_list);
/// If the current token is a string followed by a colon token, as required
/// for object keys, set |key_name| to the _encoded_ key name, skip over
/// both tokens then return true. Otherwise set error then return false.
bool ExpectObjectKeyAndColon(StringPiece* key_name) {
return ExpectEncodedString(key_name) && ExpectColon();
}
/// If the current three tokens are a string, a colon, and a string, then
/// set |*key| and |*value|, skip over the three tokens, then return true.
/// Otherwise set error then return false. Note that both |*key| and |*value|
/// are encoded.
bool ExpectObjectKeyAndStringValue(StringPiece* key, StringPiece* value) {
return ExpectObjectKeyAndColon(key) && ExpectEncodedString(value);
}
/// Skip the current JSON value. This handles arrays and objects by calling
/// itself recursively. On success, return true. On failure, set error then
/// return false. It is an error if the current token does not start a value.
bool SkipJsonValue();
/// A struct to store the current JSON encoded value.
struct ValueInfo {
TokenType type = JsonParser::kNone;
StringPiece value;
};
/// Store the current JSON value into |*value| then skip it.
bool ParseJsonValue(ValueInfo* info);
/// If the current token is "]", skip over it and return true.
/// Otherwise simply return false.
bool SkipOptionalArrayEnd();
/// If the current token is a comma, skip over it and return true.
/// Otherwise check that the current token is a "]" denoting an array end.
/// On success return true but do not skip it. On failure, set error
/// condition and return false.
bool CheckOptionalArrayComma();
/// If the current token is "}", sip over it and return true.
/// Otherwise simply return false.
bool SkipOptionalObjectEnd();
/// If the current token is a comma, skip over it and return true.
/// Otherwise check that the current token is a "}" denoting an object end.
/// On success return true but do not skip it. On failure, set error
/// condition and return false.
bool CheckOptionalObjectComma();
/// Helper class used to iterate over a JSON array value. Usage is:
///
/// ArrayIterator it(parser);
/// while (it.HasItem()) {
/// .. read single item from |parser|.
/// }
/// return !parser.HasError(); // always check after the loop.
///
/// For example, to read an array of strings:
///
/// auto parse_string_list = [&parser](
// std::vector<std::string>* result) -> bool {
/// ArrayIterator it(parser);
/// while (it.HasItem()) {
/// std::string item;
/// if (!parser.ExpectString(&item))
/// return false;
/// result->push_back(std::move(item));
/// }
/// return true;
/// };
///
struct ArrayIterator {
/// Constructor takes a mutable reference to a JsonParser object
/// that will be modified directly by calling HasItem().
/// It is an error if the current token is not kArrayStart.
ArrayIterator(JsonParser& parser);
/// Return true if there is one more item to read, and false
/// if the array has ended, or an error occurred. If the parser
/// passed to the constructor was not set to kArrayStart, then
/// the first call to HasItem() will return false.
bool HasItem();
private:
JsonParser& parser_;
bool expect_comma_ = false;
};
/// Helper class used to iterate over all (key, value) pairs in an object.
/// Users are encouraged to use JsonParser::ParseObject() instead which
/// keys appearing in any order in the input object, and delaying their
/// parsing until the value is needed.
///
/// Usage is:
///
/// ObjectIterator it(parser);
/// while (it.HasKeyValue()) {
/// ... Use it.key() to access the encoded key.
/// ... read single value from |parser| here.
/// }
/// return !parser.HasError(); // Always check after the loop.
//
struct ObjectIterator {
/// Constructor takes a mutable reference to a JsonParser instance
/// that will be modified by HasKeyValue() calls below.
///
/// If the current token is not "{", then the first HasKeyValue() call
/// will return false, and HasError() will return true.
ObjectIterator(JsonParser& parser);
/// Return true if there is a (key, value) pair to read.
/// On success, read the key and colon and leave the parser's
/// position on the token of the corresponding value, to be read
/// by the caller. The key can be retrieved after this call with key().
bool HasKeyValue();
/// The encoded key corresponding to the latest successful HasKeyValue()
/// call.
StringPiece key() const { return key_; }
private:
JsonParser& parser_;
StringPiece key_;
bool expect_comma_ = false;
};
/// A mapping from object key names to object values, as they appear in
/// the input. Used by ParseObject() method below.
///
/// Note that to minimize memory allocations, this only uses StringPiece
/// for keys and for the returned encoded value (ValueInfo::value). The
/// caller is responsible for ensuring that the StringPiece values remain
/// valid for the lifetime of the KeyValueMap.
///
/// Usage example is:
///
/// // Parse a simple JSON object which expects the following schema:
/// //
/// // "version": Required. An integer that must be 1 or 2
/// // "command": Required. A command string.
/// // "args": Required in version 1, and optional in version 2,
/// // an array of argument strings for the command.
/// //
/// // On success, set |*command| and |*args| then return true.
/// // On failure, set parser error then return false.
/// //
/// auto parse_my_object = [&parser](std::string* command,
/// std::vector<std::string>* args) -> bool {
/// KeyValueMap kv;
/// if (!parser.ParseObject(&kv))
/// return false;
///
/// int64_t version = 0;
/// if (!kv.GetRequiredInteger("version", &version, parser))
/// return false;
///
/// if (version != 1 && version != 2) {
/// parser.SetErrorFormat("Invalid version: %" PRId64, version);
/// return false;
/// }
///
/// if (!kv.GetRequiredString("command", command, parser))
/// return false;
///
/// args->clear();
/// ValueInfo vi;
/// bool has_args = kv.Get("args", &vi);
/// if (version == 1 && !has_args) {
/// return parser.SetError("Missing array for args");
/// }
/// if (has_args) {
/// JsonParser subparser(vi.value);
/// if (!subparser.ExpectStringList(args))
/// return parser.SetError(subparser.TakeError());
/// }
/// // Done.
/// return true;
/// };
///
struct KeyValueMap : public ExternalStringHashMap<ValueInfo>::Type {
/// Retrieve the value associated with a given optional |key|. If the key
/// has a value, set |*vi| to it, then return true. If there
/// is no associated value, return false.
bool Get(StringPiece key, ValueInfo* vi) const;
/// Retrieve the value associated with a given optional |key|, after
/// verifying its type. If the key has an associated value with the
/// right type, set |*vi| to it, then return true. If there is no
/// associated value, set |*vi| to an invalid value, then return
/// true as well. Otherwise, cal |parser.SetError()| then return false.
bool GetOptionalTyped(StringPiece key, TokenType expected_type,
StringPiece* value, JsonParser& parser) const;
/// Retrieve the value associated with a given required |key|. On success,
/// set |*vi| then return true, on failure call |parser.SetError()|
/// then return false.
bool GetRequired(StringPiece key, ValueInfo* vi, JsonParser& parser) const;
/// Same as GetRequired, but also errors if the returned token type
/// does not match |expected_type|.
bool GetRequiredExpected(StringPiece key, TokenType expected_type,
ValueInfo* vi, JsonParser& parser) const;
/// Retrieve a required integer value associated with |key|.
bool GetRequiredInteger(StringPiece key, int64_t* value,
JsonParser& parser) const;
/// Retrieve a required string value associated with |key|. On success
/// set |*value| to the decoded string value and return true. On failure
/// call |parser.SetError()| and return false.
bool GetRequiredString(StringPiece key, std::string* value,
JsonParser& parser) const;
/// Retrieve a required array of strings associated with |key|. On success
/// set |*value| and return true. On failure, call |parser.SetError()|
/// then return false.
bool GetRequiredStringList(StringPiece key, std::vector<std::string>* value,
JsonParser& parser) const;
/// Retrieve an optional string associated with |key|, or an empty string
/// otherwise. On success, set |*value| then return true. On failure
/// call |parser.SetError()| then return false.
bool GetOptionalOrEmptyString(StringPiece key, std::string* value,
JsonParser& parser) const;
};
/// Parse a JSON object into an ObjectKeyValueMap.
/// On success, set |*kv_map| then return true.
/// On failure, clear |*kv_map|, set parser error, then return false.
///
/// Example usage:
///
/// ObjectKeyValueMap key_values = parser.ParseObject();
/// if (parser.HasError()) {
/// ... deal with error
/// }
/// StringPiece version;
/// if (!key_values.GetRequired("version", &version)) {
/// JsonParser subparser(version)
/// }
/// parser.SetError("Missing required key 'version'");
bool ParseObject(KeyValueMap* kv_map);
/// If [start,limit) begins with a JSON number expression, set |*end_ptr|
/// to the first character after it, then return true. Otherwise return false.
/// Exposed for unit-tests only.
static bool IsJsonNumber(const char* start, const char* limit,
const char** end_ptr);
/// Helper function used to parse JSON input strings.
/// |start| points to the opening quote in the input.
/// |limit| points to the first character in memory after the input.
/// function is called. On success, set |*limit_ptr| to point to the closing
/// quote in the input, and return true. On failure, set |*limit_ptr| to the
/// first malformed input char from the input, and return false.
///
/// The function does not try ton unquote the input string, only find its
/// closing quote position. Example usage:
///
/// StringPiece json_input = ...;
/// if (json_input[0] == '"') {
/// auto str_info = ParseJsonInputString(json_input.begin(),
/// json_input.end()); if (str_info.error) {
/// // malformed input. return error.
/// }
/// std::string value = DecodeString(str_info.start, str_info.end -
/// str_info.start);
/// }
/// const char* token_start = ...;
/// const char* token_end = input.end();
struct JsonInputStringInfo {
const char* start = nullptr; // pointer to first char after opening quote.
const char* end = nullptr; // pointer to closing quote, or first malformed
// character, or limit.
bool error = false;
};
static JsonInputStringInfo ParseJsonInputString(const char* start_quote,
const char* limit);
/// Decode a string in JSON format into the corresponding UTF-8
/// representation. This assumes the input is well-formed. Otherwise the
/// result value is not guaranteed to be valid.
static std::string DecodeString(StringPiece str);
private:
bool ExpectTokenType(TokenType token_type, const char* error_msg);
void EnsureToken() {
if (token_type_ == kNone)
ParseToken();
}
void ParseToken();
const char* p_ = nullptr;
const char* end_ = nullptr;
TokenType token_type_ = kNone;
StringPiece token_;
std::string error_;
};