/* 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 . */ #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::array() const { if (!isArray()) return nullEntry().c_array; return c_array; } const PIMap & 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"; }