blob: 75b3068d4ebdd4a06885bc53ca7b1cd42d53dd40 [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.
#include "json_parser.h"
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include "json.h"
namespace {
int ParseHexDigit(char ch) {
if (ch >= '0' && ch <= '9')
return (ch - '0');
if (ch >= 'A' && ch <= 'F')
return 10 + (ch - 'A');
if (ch >= 'a' && ch <= 'f')
return 10 + (ch - 'a');
return -1;
}
bool ParseHex4(const char* p, uint32_t* value) {
int v = (ParseHexDigit(p[0]) << 12) | (ParseHexDigit(p[1]) << 8) |
(ParseHexDigit(p[2]) << 4) | ParseHexDigit(p[3]);
if (v < 0)
return false;
*value = static_cast<uint32_t>(v);
return true;
}
bool IsUnicodeSurrogatePairStart(uint32_t cp) {
return ((cp & 0xfc00) == 0xd800);
}
bool IsUnicodeSurrogatePairEnd(uint32_t cp) {
return ((cp & 0xfc00) == 0xdc00);
}
// Result for ParseJsonEscape function. On failure, error will be true.
// Otherwise |len| will be the length of the escape sequence in chars,
// |single_char| will be non-0 for single-char escapes, or
// |unicode_cp| will be the unicode codepoint for unicode escapes.
struct JsonEscapeInfo {
bool error = false;
char len = 0;
char single_char = '\0';
uint32_t unicode_cp = 0;
};
JsonEscapeInfo ParseJsonEscape(const char* p, const char* limit) {
JsonEscapeInfo info = { true }; // error set by default.
if (p + 1 >= limit) // trailing escape.
return info;
char single = '\0';
switch (p[1]) {
case 'b':
single = '\b';
break;
case 'f':
single = '\f';
break;
case 'n':
single = '\n';
break;
case 'r':
single = '\r';
break;
case 't':
single = '\t';
break;
case '"':
single = '"';
break;
case '\\':
single = '\\';
break;
case 'u': {
// Handle unicode quoting.
uint32_t cp = 0;
if (p + 6 > limit || !ParseHex4(p + 2, &cp) ||
IsUnicodeSurrogatePairEnd(cp))
return info;
p += 6;
info.len = 6;
if (IsUnicodeSurrogatePairStart(cp)) {
// Surrogate pair start must be followed by escape with surrogate pair
// end.
uint32_t cp2;
if (p + 6 > limit || p[0] != '\\' || p[1] != 'u' ||
!ParseHex4(p + 2, &cp2) || !IsUnicodeSurrogatePairEnd(cp2)) {
return info;
}
cp = 0x100000 + ((static_cast<uint32_t>(cp) & 0x3ff) | (cp2 & 0x3ff));
info.len = 12;
}
info.unicode_cp = cp;
info.error = false;
return info;
}
default:
// Unsupported escape.
return info;
}
// Fallback for single-char escapes.
info.len = 2;
info.single_char = single;
info.error = false;
return info;
}
} // namespace
namespace {
static bool IsDecimal(char ch) {
return (ch >= '0' && ch <= '9'); // std:digit() is locale-dependent and slow.
}
static bool IsJsonWhitespace(char ch) {
return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
};
bool SetMissingKeyError(JsonParser& parser, StringPiece key) {
return parser.SetErrorFormat("Missing required key '%.*s'",
static_cast<int>(key.size()), key.begin());
}
} // namespace
JsonParser::JsonParser(StringPiece input)
: p_(input.begin()), end_(input.end()) {}
void JsonParser::Reset(StringPiece input) {
p_ = input.begin();
end_ = input.end();
token_type_ = kNone;
token_ = {};
error_.clear();
}
JsonParser::TokenType JsonParser::PeekTokenType() {
EnsureToken();
return token_type_;
}
bool JsonParser::SetError(std::string error) {
error_ = std::move(error);
token_type_ = kParsingError;
return false;
}
bool JsonParser::SetErrorFormat(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
SetError(StringFormat(fmt, args));
va_end(args);
return false;
}
bool JsonParser::SetExpectedError(const char* expected) {
return SetErrorFormat("expected %s, got %s", expected,
ToString(token_type_).c_str());
}
StringPiece JsonParser::PeekToken() {
EnsureToken();
return token_;
}
void JsonParser::SkipToken() {
if (token_type_ == kNone)
ParseToken();
token_type_ = kNone;
// The next PeekToken() will parse the next token.
}
std::string JsonParser::ToString(JsonParser::TokenType type) {
StringPiece result;
switch (type) {
case kNone:
result = "NONE";
break;
case kEndOfInput:
result = "END_OF_INPUT";
break;
case kParsingError:
result = "PARSING_ERROR";
break;
case kObjectStart:
result = "OBJECT_START";
break;
case kObjectEnd:
result = "OBJECT_END";
break;
case kArrayStart:
result = "ARRAY_START";
break;
case kArrayEnd:
result = "ARRAY_END";
break;
case kComma:
result = "COMMA";
break;
case kColon:
result = "COLON";
break;
case kString:
result = "STRING";
break;
case kTrue:
result = "TRUE";
break;
case kFalse:
result = "FALSE";
break;
case kNull:
result = "NULL";
break;
case kNumber:
result = "NUMBER";
break;
default:
return StringFormat("UNKNOWN(%d)", type);
}
return result.AsString();
}
// Return true if the next token matches the start of a JSON value.
bool JsonParser::PeekValueStart() {
EnsureToken();
switch (token_type_) {
case kEndOfInput:
case kParsingError:
case kArrayEnd:
case kObjectEnd:
return false;
default:
return true;
}
}
bool JsonParser::ExpectValueStartNoSkip() {
if (!PeekValueStart()) {
error_ = "expected JSON value";
return false;
}
return true;
}
bool JsonParser::ExpectNumber(StringPiece* str_value) {
EnsureToken();
if (token_type_ != kNumber) {
return SetExpectedError("number");
}
*str_value = token_;
SkipToken();
return true;
}
bool JsonParser::ExpectInteger(int64_t* int_value) {
StringPiece number;
if (!ExpectNumber(&number))
return false;
std::string number_str = number.AsString();
errno = 0;
char* end = nullptr;
int64_t value = strtoll(number_str.c_str(), &end, 10);
if (errno != 0 || end != &number_str.back() + 1) {
error_ = StringFormat("Number is not an integer: %s", number_str.c_str());
return false;
}
*int_value = value;
return true;
}
bool JsonParser::ExpectDouble(double* float_value) {
StringPiece number;
if (!ExpectNumber(&number))
return false;
std::string number_str = number.AsString();
errno = 0;
char* end = nullptr;
double value = strtod(number_str.c_str(), &end);
if (errno != 0 || end != &number_str.back() + 1) {
error_ =
StringFormat("Number is not floating point: %s", number_str.c_str());
return false;
}
*float_value = value;
return true;
}
bool JsonParser::ExpectBoolean(bool* flag_value) {
EnsureToken();
if (token_type_ == kTrue || token_type_ == kFalse) {
*flag_value = (token_type_ == kTrue);
SkipToken();
return true;
}
return SetExpectedError("boolean");
}
bool JsonParser::SkipOptionalArrayEnd() {
if (PeekTokenType() != kArrayEnd)
return false;
SkipToken();
return true;
}
bool JsonParser::CheckOptionalArrayComma() {
EnsureToken();
if (token_type_ == kComma) {
SkipToken();
return true;
} else if (token_type_ == kArrayEnd) {
return true;
}
return SetExpectedError("comma or array end");
}
bool JsonParser::SkipOptionalObjectEnd() {
if (PeekTokenType() != kObjectEnd)
return false;
SkipToken();
return true;
}
bool JsonParser::CheckOptionalObjectComma() {
EnsureToken();
if (token_type_ == kComma) {
SkipToken();
return true;
} else if (token_type_ == kObjectEnd) {
return true;
}
return SetExpectedError("comma or object end");
}
bool JsonParser::ExpectEncodedString(StringPiece* encoded_str) {
EnsureToken();
if (token_type_ != kString) {
return SetExpectedError("string");
}
*encoded_str = token_;
SkipToken();
return true;
}
bool JsonParser::ExpectString(std::string* str) {
StringPiece encoded;
if (!ExpectEncodedString(&encoded))
return false;
*str = JsonParser::DecodeString(encoded);
return true;
}
bool JsonParser::ExpectStringList(std::vector<std::string>* str_list) {
str_list->clear();
JsonParser::ArrayIterator it(*this);
while (it.HasItem()) {
StringPiece encoded;
if (!ExpectEncodedString(&encoded)) {
break;
}
str_list->push_back(JsonParser::DecodeString(encoded));
}
return IsOk();
}
bool JsonParser::ParseJsonValue(JsonParser::ValueInfo* info) {
EnsureToken();
switch (token_type_) {
case kEndOfInput:
return SetExpectedError("JSON value");
case kParsingError:
error_ = "malformed JSON input";
return false;
case kArrayStart: {
const char* start = token_.str_;
SkipToken();
while (!SkipOptionalArrayEnd()) {
if (PeekTokenType() == kArrayEnd)
break;
if (!SkipJsonValue())
return false;
if (!CheckOptionalArrayComma())
return false;
}
if (info) {
info->type = kArrayStart;
info->value = StringPiece(start, p_ - start);
}
return true;
}
case kObjectStart: {
const char* start = token_.str_;
SkipToken();
while (!SkipOptionalObjectEnd()) {
if (PeekTokenType() == kObjectEnd)
break;
StringPiece key;
if (!ExpectObjectKeyAndColon(&key))
return false;
if (!SkipJsonValue())
return false;
if (!CheckOptionalObjectComma())
return false;
}
if (info) {
info->type = kObjectStart;
info->value = StringPiece(start, p_ - start);
}
return true;
}
case kString:
case kTrue:
case kFalse:
case kNumber:
case kNull:
if (info) {
info->type = token_type_;
info->value = token_;
}
SkipToken();
return true;
default:
// Anything else is an unexpected token for a value.
return SetExpectedError("JSON value");
}
}
bool JsonParser::SkipJsonValue() {
return ParseJsonValue(nullptr);
}
JsonParser::ArrayIterator::ArrayIterator(JsonParser& parser) : parser_(parser) {
parser_.ExpectArrayStart();
}
bool JsonParser::ArrayIterator::HasItem() {
if (parser_.HasError())
return false;
TokenType token = parser_.PeekTokenType();
if (token == kArrayEnd) {
parser_.SkipToken();
return false;
}
if (expect_comma_ && !parser_.ExpectComma()) {
return false;
}
expect_comma_ = true;
if (!parser_.ExpectValueStartNoSkip()) {
return false;
}
return true;
}
JsonParser::ObjectIterator::ObjectIterator(JsonParser& parser)
: parser_(parser) {
parser_.ExpectObjectStart();
}
bool JsonParser::ObjectIterator::HasKeyValue() {
if (parser_.HasError())
return false;
TokenType token = parser_.PeekTokenType();
if (token == kObjectEnd) {
parser_.SkipToken();
return false;
}
if (expect_comma_ && !parser_.ExpectComma()) {
return false;
}
expect_comma_ = true;
if (!parser_.ExpectObjectKeyAndColon(&key_)) {
return false;
}
return true;
}
bool JsonParser::ExpectTokenType(TokenType token_type, const char* error_msg) {
EnsureToken();
if (token_type_ != token_type) {
error_ = error_msg;
return false;
}
SkipToken();
return true;
}
bool JsonParser::IsJsonNumber(const char* start, const char* limit,
const char** end_ptr) {
const char* p = start;
// Required 0, or [1-9][0-9]*
if (p >= limit || !IsDecimal(p[0]))
return false;
p++;
if (p[-1] != '0') {
while (p <= limit && IsDecimal(p[0]))
p++;
}
// Optional dot followed by [0-9]+
if (p + 2 < limit && p[0] == '.' && IsDecimal(p[1])) {
p += 2;
while (p < limit && IsDecimal(p[0]))
p++;
}
// Optional [eE][+-]?[0-9]+
if (p < limit && (p[0] == 'e' || p[0] == 'E')) {
const char* q = p + 1;
if (q < limit && (q[0] == '+' || q[0] == '-'))
q++;
if (q < limit && IsDecimal(q[0])) {
p = q + 1;
while (p < limit && IsDecimal(p[0]))
p++;
}
}
*end_ptr = p;
return true;
}
void JsonParser::ParseToken() {
token_ = StringPiece(p_, 0);
if (HasError()) {
token_type_ = kParsingError;
return;
}
// Skip whitespace.
while (p_ < end_ && IsJsonWhitespace(*p_))
p_++;
if (p_ == end_) {
token_type_ = kEndOfInput;
return;
}
switch (*p_) {
case '{':
token_type_ = kObjectStart;
break;
case '}':
token_type_ = kObjectEnd;
break;
case '[':
token_type_ = kArrayStart;
break;
case ']':
token_type_ = kArrayEnd;
break;
case ',':
token_type_ = kComma;
break;
case ':':
token_type_ = kColon;
break;
case '"': {
JsonInputStringInfo info = ParseJsonInputString(p_, end_);
if (info.error)
goto MALFORMED_INPUT;
token_type_ = kString;
token_ = StringPiece(info.start, info.end - info.start);
p_ = info.end + 1; // Skip over closing quote.
return;
}
case 'f':
if (p_ + 5 > end_ || memcmp(p_ + 1, "alse", 4) != 0)
goto MALFORMED_INPUT;
token_type_ = kFalse;
token_ = StringPiece(p_, 5u);
p_ += 5;
return;
case 't':
if (p_ + 4 > end_ || memcmp(p_ + 1, "rue", 3) != 0)
goto MALFORMED_INPUT;
token_type_ = kTrue;
token_ = StringPiece(p_, 4u);
p_ += 4;
return;
case 'n':
if (p_ + 4 > end_ || memcmp(p_ + 1, "ull", 3) != 0)
goto MALFORMED_INPUT;
token_type_ = kNull;
token_ = StringPiece(p_, 4u);
p_ += 4;
return;
default:
const char* number_end = nullptr;
if (!IsJsonNumber(p_, end_, &number_end))
goto MALFORMED_INPUT;
token_type_ = kNumber;
token_ = StringPiece(p_, number_end - p_);
p_ = number_end;
return;
}
// Common code for single character tokens.
token_ = StringPiece(p_, 1);
p_ += 1;
return;
MALFORMED_INPUT:
token_type_ = kParsingError;
token_ = StringPiece();
error_ = "malformed input";
return;
}
bool JsonParser::KeyValueMap::Get(StringPiece key,
JsonParser::ValueInfo* vi) const {
auto it = this->find(key);
if (it == end())
return false;
*vi = it->second;
return true;
}
bool JsonParser::KeyValueMap::GetRequired(StringPiece key,
JsonParser::ValueInfo* vi,
JsonParser& parser) const {
if (Get(key, vi))
return true;
return SetMissingKeyError(parser, key);
}
bool JsonParser::KeyValueMap::GetRequiredExpected(StringPiece key,
TokenType expected_type,
JsonParser::ValueInfo* vi,
JsonParser& parser) const {
if (!Get(key, vi))
return SetMissingKeyError(parser, key);
if (vi->type != expected_type) {
return parser.SetErrorFormat("Invalid type for key '%.*s', expected %s",
static_cast<int>(key.size()), key.begin(),
parser.ToString(expected_type).c_str());
}
return true;
}
bool JsonParser::KeyValueMap::GetOptionalTyped(StringPiece key,
TokenType expected_type,
StringPiece* value,
JsonParser& parser) const {
JsonParser::ValueInfo vi;
if (!Get(key, &vi)) {
*value = {};
} else if (vi.type == expected_type) {
*value = vi.value;
} else {
return parser.SetErrorFormat("Invalid type for key '%.*s', expected %s",
static_cast<int>(key.size()), key.begin(),
parser.ToString(expected_type).c_str());
}
return true;
}
bool JsonParser::KeyValueMap::GetRequiredInteger(StringPiece key,
int64_t* value,
JsonParser& parser) const {
JsonParser::ValueInfo vi;
if (!GetRequiredExpected(key, JsonParser::kNumber, &vi, parser))
return false;
std::string number = vi.value.AsString();
char* end = nullptr;
errno = 0;
long long v = strtoll(number.c_str(), &end, 10);
if (end != &number.back() + 1) {
return parser.SetErrorFormat("Integer number expected, got '%s'",
number.c_str());
}
if (errno == ERANGE)
return parser.SetErrorFormat("Integer number is too large: '%s'",
number.c_str());
*value = static_cast<int64_t>(v);
return true;
}
bool JsonParser::KeyValueMap::GetRequiredString(StringPiece key,
std::string* value,
JsonParser& parser) const {
JsonParser::ValueInfo vi;
if (!GetRequiredExpected(key, JsonParser::kString, &vi, parser))
return false;
*value = JsonParser::DecodeString(vi.value);
return true;
}
bool JsonParser::KeyValueMap::GetRequiredStringList(
StringPiece key, std::vector<std::string>* value,
JsonParser& parser) const {
value->clear();
JsonParser::ValueInfo vi;
if (!GetRequiredExpected(key, JsonParser::kArrayStart, &vi, parser))
return false;
JsonParser list_parser(vi.value);
JsonParser::ArrayIterator it(list_parser);
while (it.HasItem()) {
StringPiece encoded;
if (!list_parser.ExpectEncodedString(&encoded)) {
break;
}
value->push_back(JsonParser::DecodeString(encoded));
}
if (list_parser.HasError())
return parser.SetError(list_parser.TakeError());
return true;
}
bool JsonParser::KeyValueMap::GetOptionalOrEmptyString(
StringPiece key, std::string* value, JsonParser& parser) const {
StringPiece encoded_value;
if (!GetOptionalTyped(key, JsonParser::kString, &encoded_value, parser))
return false;
if (encoded_value.empty()) {
value->clear();
} else {
*value = JsonParser::DecodeString(encoded_value);
}
return true;
}
bool JsonParser::ParseObject(JsonParser::KeyValueMap* kv_map) {
kv_map->clear();
JsonParser::ObjectIterator it(*this);
while (it.HasKeyValue()) {
StringPiece key = it.key();
ValueInfo vi;
if (!ParseJsonValue(&vi))
break;
auto ret = kv_map->emplace(key, vi);
if (!ret.second) {
ret.first->second = vi;
}
}
return IsOk();
}
JsonParser::JsonInputStringInfo JsonParser::ParseJsonInputString(
const char* start, const char* limit) {
JsonInputStringInfo result = { start, limit, true };
if (start > limit || *start != '"')
return result;
const char* p = start + 1;
result.start = p;
auto* next_quote = static_cast<const char*>(::memchr(p, '"', limit - p));
if (!next_quote) // Missing closing quote from input.
return result;
// If there are no escape sequences, return quickly.
if (!::memchr(p, '\\', next_quote - p)) {
result.end = next_quote;
result.error = false;
return result;
}
// Parse the string looking for escape sequences to find the real closing
// quote.
while (true) {
if (p == limit) { // Missing closing quote.
return result;
}
if (*p == '"') { // closing quote.
result.end = p;
result.error = false;
return result;
}
if (*p != '\\') { // non-escaped character.
p++;
continue;
}
JsonEscapeInfo info = ParseJsonEscape(p, limit);
if (!info.error) {
p += info.len;
continue;
}
result.end = p;
return result;
}
}
std::string JsonParser::DecodeString(StringPiece str) {
const char* start = str.begin();
size_t len = str.size();
const char* limit = start + len;
if (start >= limit)
return {};
std::string result;
result.reserve(len);
while (start < limit) {
auto* escape =
static_cast<const char*>(::memchr(start, '\\', limit - start));
if (!escape) {
result.append(start, limit - start);
break;
}
if (escape > start)
result.append(start, escape - start);
JsonEscapeInfo info = ParseJsonEscape(escape, limit);
if (info.error)
break; // stop at invalid escapes.
if (info.single_char) {
result.push_back(info.single_char);
} else {
// Write Unicode codepoint as UTF-8.
uint32_t cp = info.unicode_cp;
if (cp < 0x80) {
result.push_back(static_cast<char>(cp));
} else if (cp < 0x800) {
result.push_back(static_cast<char>(0xc0 | ((cp >> 6) & 0x1f)));
result.push_back(static_cast<char>(0x80 | (cp & 0x3f)));
} else if (cp < 0x10000) {
result.push_back(static_cast<char>(0xe0 | ((cp >> 12) & 0x1f)));
result.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3f)));
result.push_back(static_cast<char>(0x80 | (cp & 0x3f)));
} else if (cp < 0x1fffff) {
result.push_back(static_cast<char>(0xf0 | ((cp >> 18) & 0x07)));
result.push_back(static_cast<char>(0x80 | ((cp >> 12) & 0x3f)));
result.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3f)));
result.push_back(static_cast<char>(0x80 | (cp & 0x3f)));
}
}
start = escape + info.len;
}
return result;
}