Files
pip/libs/main/core/pijson.cpp

444 lines
13 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.
/*
PIP - Platform Independent Primitives
JSON class
Ivan Pelipenko peri4ko@yandex.ru
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "pijson.h"
//! \~\class PIJSON pijson.h
//! \~\details
//! \~english \section PIJSON_sec0 Synopsis
//! \~russian \section PIJSON_sec0 Краткий обзор
//! \~english
//!
//! \~russian
//! JSON - это древовидная структура, каждый элемент которой может бить либо
//! парой имя:значение, либо массивом, либо объектом, т.е. именованным
//! списком элементов. Корневой элемент JSON может быть либо массивом,
//! либо объектом.
//!
//! Массивы заключены в квадратные скобки [], их элементы не имеют имени
//! и разделяются запятыми.
//!
//! Объекты заключены в фигурные скобки {}, их элементы имеют имя
//! и разделяются запятыми.
//!
//!
//! \~english \section PIJSON_sec1 PIJSON tree
//! \~russian \section PIJSON_sec1 Дерево PIJSON
//! \~english
//!
//! \~russian
//! %PIJSON представляет собой элемент дерева JSON. Каждый элемент имеет тип (\a type())
//! и может иметь имя (\a name()). Если это конечный элемент,то он будет иметь значение,
//! доступное через \a value(), \a toBool(), \a toInt(), \a toDouble() или \a toString().
//!
//! Если элемент преставляет собой массив, то его размер доступен через \a size(),
//! а элементы массива через целочисленный оператор []. Весь массив доступен через \a array().
//!
//! Если элемент преставляет собой объект, то его размер доступен через \a size(),
//! а элементы объекта через строковый оператор []. Проверить наличие элемента по имени можно
//! с помощью \a contains(). Весь объект доступен через \a object().
//!
//! Создать дерево из текстового представления JSON можно с помощью \a fromJSON(), а
//! преобразовать в текст с помощью \a toJSON().
//!
//!
//! \~english \section PIJSON_sec2 PIJSON creation
//! \~russian \section PIJSON_sec2 Создание PIJSON
//! \~english
//!
//! \~russian
//! Для создания нового дерева необходимо лишь создать пустой корневой PIJSON и заполнить его
//! значениями, массивами или объектами с помощью целочисленного или строкового оператора [].
//! В зависимости от типа аргумента элемент преобразуется либо в массив, либо в объект.
//!
//! При приравнивании PIJSON к какому-либо значению, его тип автоматически установится в нужный.
//! При обращении на запись целочисленным оператором [] размер массива автоматически увеличится
//! при необходимости.
//!
//!
//! \~english \section PIJSON_sec3 Mask/unmask
//! \~russian \section PIJSON_sec3 Маскирование/размаскирование
//! \~english
//!
//! \~russian
//! Строковые значения в стандарте JSON могут иметь в явном виде только печатные символы,
//! спецсимволы и юникод должны быть преобразованы маскированием, т.е., например, вместо
//! символа новой строки должно быть "\n", а не-ASCII символы должны быть в виде "\uXXXX".
//!
//! Оператор вывода в PICout не выполняет маскирования строк.
//!
//! Методы \a toJSON() и \a fromJSON() маскируют и размаскируют строковые поля.
//!
//!
PIJSON PIJSON::newObject() {
PIJSON ret;
ret.c_type = Object;
return ret;
}
PIJSON PIJSON::newArray() {
PIJSON ret;
ret.c_type = Array;
return ret;
}
PIJSON PIJSON::newString(const PIString & v) {
PIJSON ret;
ret = v;
return ret;
}
const PIVector<PIJSON> & PIJSON::array() const {
if (!isArray())
return nullEntry().c_array;
return c_array;
}
const PIMap<PIString, PIJSON> & PIJSON::object() const {
if (!isObject())
return nullEntry().c_object;
return c_object;
}
//! \details
//! \~english
//! If "v" type is boolean set type to \a PIJSON::Boolean.\n
//! If "v" type is any numeric set type to \a PIJSON::Number.\n
//! If "v" type is string set type to \a PIJSON::String.\n
//! In case of any other types set element type to \a PIJSON::Invalid.
//! \~russian
//! Если тип "v" логический, то устанавливает тип в \a PIJSON::Boolean.\n
//! Если тип "v" любой численный, то устанавливает тип в \a PIJSON::Number.\n
//! Если тип "v" строковый, то устанавливает тип в \a PIJSON::String.\n
//! Если тип "v" любой другой, то устанавливает тип в \a PIJSON::Invalid.
void PIJSON::setValue(const PIVariant & v) {
c_value = v;
switch (v.type()) {
case PIVariant::pivBool: c_type = Boolean; break;
case PIVariant::pivUChar:
case PIVariant::pivShort:
case PIVariant::pivUShort:
case PIVariant::pivInt:
case PIVariant::pivUInt:
case PIVariant::pivLLong:
case PIVariant::pivULLong:
case PIVariant::pivFloat:
case PIVariant::pivDouble:
case PIVariant::pivLDouble: c_type = Number; break;
case PIVariant::pivString: c_type = String; break;
default: c_type = Invalid; break;
}
}
void PIJSON::clear() {
c_type = Invalid;
c_value = PIVariant();
c_name.clear();
c_object.clear();
c_array.clear();
}
int PIJSON::size() const {
if (isArray())
return c_array.size_s();
if (isObject())
return c_object.size_s();
return 0;
}
bool PIJSON::contains(const PIString & key) const {
if (!isObject()) return false;
return c_object.contains(key);
}
void PIJSON::resize(int new_size) {
c_type = Array;
c_array.resize(new_size, newString());
}
const PIJSON & PIJSON::operator[](int index) const {
if (!isArray())
return nullEntry();
return c_array[index];
}
PIJSON & PIJSON::operator[](int index) {
c_type = Array;
if (index >= c_array.size_s())
c_array.resize(index + 1, newString());
PIJSON & ret(c_array[index]);
return ret;
}
PIJSON & PIJSON::operator=(const PIJSON & v) {
c_type = v.c_type;
c_value = v.c_value;
c_object = v.c_object;
c_array = v.c_array;
return *this;
}
PIJSON PIJSON::operator[](const PIString & key) const {
if (!isObject())
return nullEntry();
if (!c_object.contains(key))
return nullEntry();
return c_object.value(key);
}
PIJSON & PIJSON::operator[](const PIString & key) {
c_type = Object;
PIJSON & ret(c_object[key]);
ret.c_name = key;
return ret;
}
PIString PIJSON::toJSON(PrintType print_type) const {
PIString ret;
print(ret, *this, "", print_type == Tree, true);
return ret;
}
PIJSON PIJSON::fromJSON(PIString str) {
return parseValue(str);
}
PIJSON & PIJSON::nullEntry() {
static PIJSON ret;
ret.clear();
return ret;
}
PIString PIJSON::parseName(PIString & s) {
//piCout << "\n\n\n parseName" << s;
PIString ret;
ret = s.takeRange('"', '"');
s.trim();
if (s.isEmpty()) return PIString();
if (s[0] != ':') return PIString();
s.remove(0).trim();
return PIJSON::stringUnmask(ret);
}
PIJSON PIJSON::parseValue(PIString & s) {
PIJSON ret;
//piCout << "\n\n\n parseValue" << s;
s.trim();
if (s.isEmpty()) return ret;
if (s[0] == '{')
ret = parseObject(s.takeRange('{', '}'));
else if (s[0] == '[')
ret = parseArray(s.takeRange('[', ']'));
else {
s.trim();
if (s.startsWith('"')) {
ret.c_type = PIJSON::String;
ret.c_value = PIJSON::stringUnmask(s.takeRange('"', '"'));
} else {
PIString value;
int ind = s.find(',');
if (ind >= 0) {
value = s.takeLeft(ind).trim().toLowerCase();
} else {
value = s;
s.clear();
}
//piCout << "\n\n\n parseValue value = \"" << value << "\"";
if (value == "null") {
ret.c_type = PIJSON::Null;
} else if (value == "true") {
ret.c_type = PIJSON::Boolean;
ret.c_value = true;
} else if (value == "false") {
ret.c_type = PIJSON::Boolean;
ret.c_value = false;
} else {
ret.c_type = PIJSON::Number;
ret.c_value = value;
}
}
}
return ret;
}
PIJSON PIJSON::parseObject(PIString s) {
//piCout << "\n\n\n parseObject" << s;
PIJSON ret;
ret.c_type = PIJSON::Object;
while (s.isNotEmpty()) {
PIString name = PIJSON::stringUnmask(parseName(s));
PIJSON value = parseValue(s);
auto & child(ret.c_object[name]);
child = value;
child.c_name = name;
s.trim();
if (s.isEmpty()) break;
if (s[0] != ',') break;
s.remove(0);
}
return ret;
}
PIJSON PIJSON::parseArray(PIString s) {
//piCout << "\n\n\n parseArray" << s;
PIJSON ret;
ret.c_type = PIJSON::Array;
while (s.isNotEmpty()) {
PIJSON value = parseValue(s);
ret.c_array << value;
s.trim();
if (s.isEmpty()) break;
if (s[0] != ',') break;
s.remove(0);
}
return ret;
}
PIString PIJSON::stringMask(const PIString & s) {
PIString ret;
for (auto c: s) {
if (!c.isAscii()) {
ret += "\\u" + PIString::fromNumber(c.unicode16Code(), 16).expandLeftTo(4, '0');
} else {
char ca = c.toAscii();
switch (ca) {
case '"' : ret += "\\\""; break;
case '\\': ret += "\\\\"; break;
case '/' : ret += "\\/" ; break;
case '\b': ret += "\\b" ; break;
case '\f': ret += "\\f" ; break;
case '\n': ret += "\\n" ; break;
case '\r': ret += "\\r" ; break;
case '\t': ret += "\\t" ; break;
default: ret += ca;
}
}
}
return ret;
}
PIString PIJSON::stringUnmask(const PIString & s) {
PIString ret;
for (int i = 0; i < s.size_s(); ++i) {
char ca = s[i].toAscii();
if (ca == '\\') {
if (i == s.size_s() - 1) continue;
++i;
char na = s[i].toAscii();
switch (na) {
case '"' : ret += '\"'; break;
case '\\': ret += '\\'; break;
case '/' : ret += '/' ; break;
case 'b' : ret += '\b'; break;
case 'f' : ret += '\f'; break;
case 'n' : ret += '\n'; break;
case 'r' : ret += '\r'; break;
case 't' : ret += '\t'; break;
case 'u' :
ret += PIChar(s.mid(i + 1, 4).toUShort(16));
i += 4;
break;
default: break;
}
} else {
ret += s[i];
}
}
return ret;
}
void PIJSON::print(PIString & s, const PIJSON & v, PIString tab, bool spaces, bool transform, bool comma) {
if (spaces) s += tab;
/*
switch (v.c_type) {
case JSONEntry::Invalid: s << "(Invalid)"; break;
case JSONEntry::Null: s << "(Null)"; break;
case JSONEntry::Boolean: s << "(Boolean)"; break;
case JSONEntry::Number: s << "(Number)"; break;
case JSONEntry::String: s << "(String)"; break;
case JSONEntry::Object: s << "(Object)"; break;
case JSONEntry::Array: s << "(Array)"; break;
}
*/
if (v.name().isNotEmpty()) {
s += '"' + (transform ? stringMask(v.name()) : v.name()) + "\":";
if (spaces) s += ' ';
}
switch (v.c_type) {
case PIJSON::Invalid: break;
case PIJSON::Null: s += "null"; break;
case PIJSON::Boolean: s += PIString::fromBool(v.c_value.toBool()); break;
case PIJSON::Number: s += v.c_value.toString(); break;
case PIJSON::String: s += '"' + (transform ? stringMask(v.c_value.toString()) : v.c_value.toString()) + '"'; break;
case PIJSON::Object:
s += "{";
if (spaces) s += '\n';
{
PIString ntab = tab + " ";
auto it = v.c_object.makeIterator();
int cnt = 0;
while (it.next())
print(s, it.value(), ntab, spaces, transform, ++cnt < v.c_object.size_s());
}
if (spaces) s += tab;
s += "}";
break;
case PIJSON::Array:
s += "[";
if (spaces) s += '\n';
{
PIString ntab = tab + " ";
for (int i = 0; i < v.c_array.size_s(); ++i)
print(s, v.c_array[i], ntab, spaces, transform, i < v.c_array.size_s() - 1);
}
if (spaces) s += tab;
s += "]";
break;
}
if (comma) s += ',';
if (spaces) s += "\n";
}