ClickHouse/base/base/JSON.h
Robert Schulze cedf75ed5e
Enable clang-tidy for headers
clang-tidy now also checks code in header files. Because the analyzer
finds tons of issues, activate the check only for directory "base/" (see
file ".clang-tidy"). All other directories, in particular "src/" are
left to future work.

While many findings were fixed, some were not (and suppressed instead).
Reasons for this include: a) the file is 1:1 copypaste of a 3rd-party
lib (e.g. pcg_extras.h) and fixing stuff would make upgrades/fixes more
difficult b) a fix would have broken lots of using code
2022-08-31 10:48:15 +00:00

216 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include <typeinfo>
#include <Poco/Exception.h>
#include <base/StringRef.h>
#include <base/types.h>
/** Очень простой класс для чтения JSON (или его кусочков).
* Представляет собой ссылку на кусок памяти, в котором содержится JSON (или его кусочек).
* Не создаёт никаких структур данных в оперативке. Не выделяет память (кроме std::string).
* Не парсит JSON до конца (парсит только часть, необходимую для выполнения вызванного метода).
* Парсинг необходимой части запускается каждый раз при вызове методов.
* Может работать с обрезанным JSON-ом.
* При этом, (в отличие от SAX-подобных парсеров), предоставляет удобные методы для работы.
*
* Эта структура данных более оптимальна, если нужно доставать несколько элементов из большого количества маленьких JSON-ов.
* То есть, подходит для обработки "параметров визитов" и "параметров интернет магазинов" в Яндекс.Метрике.
* Если нужно много работать с одним большим JSON-ом, то этот класс может быть менее оптимальным.
*
* Имеются следующие соглашения:
* 1. Предполагается, что в JSON-е нет пробельных символов.
* 2. Предполагается, что строки в JSON в кодировке UTF-8; также могут использоваться \u-последовательности.
* Строки возвращаются в кодировке UTF-8, \u-последовательности переводятся в UTF-8.
* 3. Но суррогатная пара из двух \uXXXX\uYYYY переводится не в UTF-8, а в CESU-8.
* 4. Корректный JSON парсится корректно.
* При работе с некорректным JSON-ом, кидается исключение или возвращаются неверные результаты.
* (пример: считается, что если встретился символ 'n', то после него идёт 'ull' (null);
* если после него идёт ',1,', то исключение не кидается, и, таким образом, возвращается неверный результат)
* 5. Глубина вложенности JSON ограничена (см. MAX_JSON_DEPTH в cpp файле).
* При необходимости спуститься на большую глубину, кидается исключение.
* 6. В отличие от JSON, пользоволяет парсить значения вида 64-битное число, со знаком, или без.
* При этом, если число дробное - то дробная часть тихо отбрасывается.
* 7. Числа с плавающей запятой парсятся не с максимальной точностью.
*
* Подходит только для чтения JSON, модификация не предусмотрена.
* Все методы immutable, кроме operator++.
*/
// NOLINTBEGIN(google-explicit-constructor)
#ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wdeprecated-dynamic-exception-spec"
#endif
POCO_DECLARE_EXCEPTION(Foundation_API, JSONException, Poco::Exception)
#ifdef __clang__
# pragma clang diagnostic pop
#endif
// NOLINTEND(google-explicit-constructor)
class JSON
{
private:
using Pos = const char *;
Pos ptr_begin;
Pos ptr_end;
unsigned level;
public:
JSON(Pos ptr_begin_, Pos ptr_end_, unsigned level_ = 0) : ptr_begin(ptr_begin_), ptr_end(ptr_end_), level(level_)
{
checkInit();
}
explicit JSON(std::string_view s) : ptr_begin(s.data()), ptr_end(s.data() + s.size()), level(0)
{
checkInit();
}
JSON(const JSON & rhs)
{
*this = rhs;
}
JSON & operator=(const JSON & rhs) = default;
const char * data() const { return ptr_begin; }
const char * dataEnd() const { return ptr_end; }
enum ElementType
{
TYPE_OBJECT,
TYPE_ARRAY,
TYPE_NUMBER,
TYPE_STRING,
TYPE_BOOL,
TYPE_NULL,
TYPE_NAME_VALUE_PAIR,
TYPE_NOTYPE,
};
ElementType getType() const;
bool isObject() const { return getType() == TYPE_OBJECT; }
bool isArray() const { return getType() == TYPE_ARRAY; }
bool isNumber() const { return getType() == TYPE_NUMBER; }
bool isString() const { return getType() == TYPE_STRING; }
bool isBool() const { return getType() == TYPE_BOOL; }
bool isNull() const { return getType() == TYPE_NULL; }
bool isNameValuePair() const { return getType() == TYPE_NAME_VALUE_PAIR; }
/// Количество элементов в массиве или объекте; если элемент - не массив или объект, то исключение.
size_t size() const;
/// Является ли массив или объект пустыми; если элемент - не массив или объект, то исключение.
bool empty() const;
/// Получить элемент массива по индексу; если элемент - не массив, то исключение.
JSON operator[] (size_t n) const;
/// Получить элемент объекта по имени; если элемент - не объект, то исключение.
JSON operator[] (const std::string & name) const;
/// Есть ли в объекте элемент с заданным именем; если элемент - не объект, то исключение.
bool has(const std::string & name) const { return has(name.data(), name.size()); }
bool has(const char * data, size_t size) const;
/// Получить значение элемента; исключение, если элемент имеет неправильный тип.
template <class T>
T get() const;
/// если значения нет, или тип неверный, то возвращает дефолтное значение
template <class T>
T getWithDefault(const std::string & key, const T & default_ = T()) const;
double getDouble() const;
Int64 getInt() const; /// Отбросить дробную часть.
UInt64 getUInt() const; /// Отбросить дробную часть. Если число отрицательное - исключение.
std::string getString() const;
bool getBool() const;
std::string getName() const; /// Получить имя name-value пары.
JSON getValue() const; /// Получить значение name-value пары.
std::string_view getRawString() const;
std::string_view getRawName() const;
/// Получить значение элемента; если элемент - строка, то распарсить значение из строки; если не строка или число - то исключение.
double toDouble() const;
Int64 toInt() const;
UInt64 toUInt() const;
/** Преобразовать любой элемент в строку.
* Для строки возвращается её значение, для всех остальных элементов - сериализованное представление.
*/
std::string toString() const;
/// Класс JSON одновременно является итератором по самому себе.
using iterator = JSON;
using const_iterator = JSON;
iterator operator* () const { return *this; }
const JSON * operator-> () const { return this; }
bool operator== (const JSON & rhs) const { return ptr_begin == rhs.ptr_begin; }
bool operator!= (const JSON & rhs) const { return ptr_begin != rhs.ptr_begin; }
/** Если элемент - массив или объект, то begin() возвращает iterator,
* который указывает на первый элемент массива или первую name-value пару объекта.
*/
iterator begin() const;
/** end() - значение, которое нельзя использовать; сигнализирует о том, что элементы закончились.
*/
iterator end() const;
/// Перейти к следующему элементу массива или следующей name-value паре объекта.
iterator & operator++();
iterator operator++(int); // NOLINT(cert-dcl21-cpp)
/// Есть ли в строке escape-последовательности
bool hasEscapes() const;
/// Есть ли в строке спец-символы из набора \, ', \0, \b, \f, \r, \n, \t, возможно, заэскейпленные.
bool hasSpecialChars() const;
private:
/// Проверить глубину рекурсии, а также корректность диапазона памяти.
void checkInit() const;
/// Проверить, что pos лежит внутри диапазона памяти.
void checkPos(Pos pos) const;
/// Вернуть позицию после заданного элемента.
Pos skipString() const;
Pos skipNumber() const;
Pos skipBool() const;
Pos skipNull() const;
Pos skipNameValuePair() const;
Pos skipObject() const;
Pos skipArray() const;
Pos skipElement() const;
/// Найти name-value пару с заданным именем в объекте.
Pos searchField(const std::string & name) const { return searchField(name.data(), name.size()); }
Pos searchField(const char * data, size_t size) const;
template <class T>
bool isType() const;
};
template <class T>
T JSON::getWithDefault(const std::string & key, const T & default_) const
{
if (has(key))
{
JSON key_json = (*this)[key];
if (key_json.isType<T>())
return key_json.get<T>();
else
return default_;
}
else
return default_;
}