444 lines
13 KiB
C++
444 lines
13 KiB
C++
/*
|
||
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";
|
||
}
|