Взаимодействие с API сервисами в интернете, JSON.
API - сокращение от Application programming interface, дословно интерфейс
программирования приложений. Описание методов обмена информацией между
программами. Программу, которая работает в интернете (грубо говоря сайт), будем
называть ресурсом.
Описание включает в себя:
- какую операцию мы можем выполнить с ресурсом.
- какие данные мы подаём на вход ресурса.
- какие данные мы получаем от ресурса.
Если у какого-то ресурса есть API, то это всегда задокументировано и описано. Другое дело,
когда к ресурсу требуется доступ, ибо он не всегда свободный.
Для чего нужен API?
API позволяет интегрировать разные системы между собой. Например, мы хотим, чтобы
звонки клиентам осуществлялись из CRM системы предприятия, а также чтобы в ней вёлся
учёт звонков, сохранялись их результаты и записи разговоров. С точки зрения маркетинга -
не плохо было бы знать источник входящего звонка или какие действия клиента в
интернете (напр. переходы с сайта на сайт) привели к звонку, и заодно отобразить эту
информацию в карточке клиента в CRM. Или, вы являетесь клиентом какой-то организации,
и при звонке туда к вам сразу обращаются по имени и отчеству - приятно. Всё это берётся
из разных API - телефонии и внешней службы коллтрекинга.
Технология взаимодействия между системами по API через интернет называется REST API.
REST – сокращение от Representational State Transfer – передача состояния представления
через интернет посредством HTTP вызовов.
В основном используются два типа вызовов – GET и POST.
GET - как правило, запрос информации. Он передаётся в URL строке. Вообще говоря, любая
строка в браузере является GET запросом.
Структура запроса: <адрес ресурса><api метод><параметры>.
Пример: [Link] Запрашиваем у ресурса [Link]/api, метод – user,
информацию по пользователю с id = 5.
POST – создание новой или модификация имеющейся информации.
Структура запроса: <POST><URL адрес><заголовок><тело запроса>.
Пример. POST [Link]
{"purchase_id":3066649,"type":0,"code":"9835208680","name":"ЭКРАН
ТЕПЛОЗАЩИТНЫЙ","price":8508.9000,"quantity":1.0000,"discount":0.0000,"sum":8508.9000}
Создаём на ресурсе [Link] новую строку. Где текст в {..} является
телом запроса.
JSON
Этот формат текста используется как для описания тела запроса, так и для ответа ресурса.
JSON сокращение от JavaScript Object Notation — текстовый формат данных в нотации
объекта JavaScript.
Формат JSON читается человеком. Кроме того, он занимает меньше места по сравнению с
XML. Поэтому он приобрёл большую популярность.
В качестве значений в JSON используются:
запись — пара ключ: значение, заключённая в фигурные скобки «{ }».
одномерный массив — множество значений, заключённые в квадратные скобки «[ ]».
число.
литералы: true, false и null.
строка — это символы юникода, заключённые в двойные кавычки.
Важно. Тело запроса и файл ответа кодируются в UTF8.
Пример в формате JSON с различными типами данных:
По запросу «проверить json online» поисковик выдаст огромное количество бесплатных
инструментов для проверки текстов в формате JSON.
К слову сказать, текстовый редактор Notepad++ легко настраивается на просмотр JSON и
отображение структуры такого текста.
Надо установить плагин JSON Viewer. Выбираем в меню Plugins->Plugins Admin...
Набираем в Search слово JSON, нажимаем Next, будет показана строка с плагином.
Выбираем её. Нажимаем Install.
Готово.
Авторизация
Для доступа к ресурсу и выполнения на нём различных действий может потребоваться
авторизация.
Под авторизацией понимается регистрация на ресурсе логина и пароля, а также получение
токена, который может иметь срок годности или быть бессрочным. Порядок авторизации
также описан на ресурсе с API.
В данном уроке нам это не потребуется, т.к. тестируем приложение на локальном сервере.
Работа с API в QT
#include <QNetworkAccessManager>
Класс QNetworkAccessManager позволяет приложению отправлять сетевые запросы и
получать ответы.
#include <QNetworkReply>
Класс QNetworkReply содержит данные и заголовки для запроса, отправляемого с помощью
QNetworkAccessManager.
Работа с JSON в QT
Для работы с JSON в QT также есть соответствующие классы.
Преобразование объекта программы в текст (в данном случае JSON) называется
сериализацией.
Обратное преобразование – десериализацией.
#include <QJsonDocument>
QJsonDocument - это класс, который оборачивает полный документ JSON и может считывать
этот документ из и записывать его в текстовое представление в кодировке UTF-8.
Документ JSON может быть преобразован из его текстового представления в QJsonDocument
с помощью QJsonDocument::FromJSON() - десериализация . toJSON() преобразует его обратно
в текст (сериализация). Анализатор работает очень быстро и эффективно и преобразует
JSON в двоичное представление, используемое Qt.
Действительность анализируемого документа может быть запрошена с помощью !isNull().
К документу можно запросить, содержит ли он массив или объект, используя isArray() и
isObject() . Массив или объект, содержащиеся в документе, могут быть извлечены с
помощью array() или object(), а затем прочитаны или обработаны.
#include <QJsonObject>
Класс QJsonObject инкапсулирует объект JSON. Объект JSON представляет собой список пар
ключ-значение, где ключи являются уникальными строками, а значения представлены
значением QJsonValue.
#include <QJsonArray>
Класс QJsonArray инкапсулирует массив JSON. Массив JSON представляет собой список
значений.
Пример. На компьютере установлен локальный Open Server, на котором есть сайт
[Link], возвращающий json-ответы.
Подготовка
Указываем в Pro файле:
QT += network
Переходим к файлу [Link]
#ifndef MAIN_HPP
#define MAIN_HPP
Подключаем необходимые библиотеки:
#include <QUrl>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QObject>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
class Request : public QObject
{
Q_OBJECT
public:
// определяем метод GET
void get(QUrl url)
{
// создаём экземпляр менеджера QNetworkAccessManager и его коннект
QNetworkAccessManager *m = new QNetworkAccessManager();
connect(m, &QNetworkAccessManager::finished, this, &Request::showReply);
// создаём собственно запрос и передаём в него url
QNetworkRequest request;
[Link](url);
// выполняем GET запрос
m->get(request);
};
public slots:
// обработка ответа
void showReply(QNetworkReply *r)
{
// обрабатываем возвращённую ошибку
if (r->error())
{
qDebug() << r->errorString();
return;
}
// если ошибок нет, то загружаем ответ
QString answer = r->readAll();
// вывод в консоль
qDebug() << answer;
qDebug() << "\n-------------------------";
// разбор JSON ответа
// загружаем в QJsonDocument ответ для разбора
QJsonDocument jsonResponse = QJsonDocument::fromJson(answer.toUtf8());
// согласно структуре ответа выделяем главный объект
QJsonObject jsonObject = [Link]();
// согласно структуре ответа содержимое главного
// объекта "response" преобразуем в массив
QJsonArray jsonArray = jsonObject["response"].toArray();
// перебираем элементы массива циклом for
// использование foreach для этой цели не годится,
// т.к. он создаёт копию массива, который может быть большим
int count = [Link]();
for(int i=0; i<count; i++)
{
// создаём объект - елемент массива
QJsonObject obj = jsonArray[i].toObject();
// вывод свойств объекта в консоль
qDebug() << "reg_type =" << obj["reg_type"].toString();
qDebug() << "address =" << obj["address"].toInteger();
qDebug() << "name =" << obj["name"].toString();
qDebug() << "val_type =" << obj["val_type"].toString();
// в зависимости от типа value определяем тип преобразования данных,
// чтобы не было ошибок вывода в консоль
if (obj["val_type"].toString() == "long")
{
qDebug() << "value =" << obj["value"].toInteger();
}
else
{
qDebug() << "value =" << obj["value"].toDouble();
}
qDebug() << "-------------------------";
}
};
};
#endif // MAIN_HPP
Переходим к файлу [Link]
Подключаем необходимые библиотеки:
#include <QCoreApplication>
#include "[Link]"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Request *my_req = new Request();
my_req->get(QUrl::fromUserInput("[Link]
return [Link]();
}
Так выглядит GET запрос в окне браузера, если делать его руками.
Смотрим в консоли, что выводит туда программа.
Сохранение конфигураций в JSON файл
JSON файл очень удобен для сохранения из программы всякой информации, т.к. при
десериализации заполнение свойств объектов происходит автоматически. Напишем
программу, которая сохраняет созданные в программе объект и его свойства в JSON файл.
Не будем нагружать классы лишней информацией, смотрим только принцип.
Задача. Создать устройство, в нём список регистров, всё это сохранить в файл в формате
JSON.
Для регистров и устройства создадим свои классы.
Создадим вспомогательные классы
Класс Register. В нём будем хранить описание регистров:
- тип регистра,
- его modbus адрес,
- наименование,
- тип значения.
Файл register.h
#ifndef REGISTER_H
#define REGISTER_H
Подключаем необходимые библиотеки:
#include <QString>
class Register
{
public:
Register();
QString reg_type; // тип регистра
int addr; // адрес регистра
QString name; // наименование
QString value_type; // тип данных value
};
#endif // REGISTER_H
Файл [Link]
В нём пока ничего нет, кроме пустого конструктора. Пусть пока будет так. В дальнейшем
может быть потребуется дописать какую-нибудь логику.
#include "register.h"
Register::Register()
{
Класс Device.
В нём будем хранить данные, которые относятся к устройству:
- наименование,
- список регистров.
Файл [Link]
#ifndef DEVICE_HPP
#define DEVICE_HPP
Подключаем необходимые библиотеки:
#include <QString>
#include <QObject>
#include "register.h"
class Device : public QObject
{
Q_OBJECT
public:
Device(){};
QString name;
QList<Register> regs;
};
#endif // DEVICE_HPP
Главный модуль программы.
Файл [Link]
В нём мы объявим главный класс программы, в котором объявим переменную device и
метод save для записи информации в файл.
#ifndef MAIN_HPP
#define MAIN_HPP
Подключаем необходимые библиотеки:
#include <QObject>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QFile>
#include <QDebug>
#include "[Link]"
class Prog : public QObject
{
Q_OBJECT
public:
Prog(){};
Device device;
void save(QString file_name)
{
// создаём объект Device
QJsonObject jsonObject;
jsonObject["name"] = [Link];
// создаём массив регистров
QJsonArray reg_arr;
int count = [Link]();
for(int i=0; i<count; i++)
{
// создаём объект регистр
QJsonObject jsonObjectReg;
jsonObjectReg["reg_type"] = [Link][i].reg_type;
jsonObjectReg["addr"] = [Link][i].addr;
jsonObjectReg["name"] = [Link][i].name;
jsonObjectReg["value_type"] = [Link][i].value_type;
// добавляем объект в массив
reg_arr.push_back(jsonObjectReg);
}
// добавляем массив в объект Device
jsonObject["registers"] = reg_arr;
// записываем в файл
QByteArray b_a = QJsonDocument(jsonObject).toJson();
QFile fout(file_name);
[Link](QIODevice::WriteOnly);
[Link](b_a);
[Link]();
};
};
#endif // MAIN_HPP
Файл [Link]
Подключаем необходимые библиотеки:
#include <QCoreApplication>
#include "[Link]"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// объявим имя файла
QString filename = "[Link]";
// создадим экземпляр главного класса
Prog p;
[Link] = "My Device";
// создадим экземпляры регистров и добавим их в массив
Register r1;
[Link] = 41984;
r1.reg_type = "Holding Register";
[Link] = "t_ustavka";
r1.value_type = "long";
[Link].push_back(r1);
Register r2;
[Link] = 41986;
r2.reg_type = "Holding Register";
[Link] = "p_ustavka";
r2.value_type = "real";
[Link].push_back(r2);
// сохраним экземпляр главного класса в файл
[Link](filename);
/* Должно получиться так
*
* {
* "name": "My Device",
* "registers": [
* {
* "addr": 41984,
* "name": "t_ustavka",
* "reg_type": "Holding Register",
* "value_type": "long"
* },
* {
* "addr": 41986,
* "name": "p_ustavka",
* "reg_type": "Holding Register",
* "value_type": "real"
* }
* ]
* }
*/
return [Link]();
}
Сохранённый файл, открытый в текстовом редакторе.
Домашнее задание.
1. Записать/прочитать JSON файл с одним простым объектом.
Структура файла {object}
2. Записать/прочитать JSON файл с массивом простых объектов.
Структура файла
[
{object1},
{object2},
{object3}
]
3. Записать/прочитать JSON файл с массивом объектов, внутри которых есть
другие объекты и массивы объектов.
Структура файла
[
{“property1”: “value”,
“property2”: {object},
“property3”: [
{object1},
{object2},
{object3}
]
},
…
{}
]