QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
JsonHelper.cc
Go to the documentation of this file.
1#include "JsonHelper.h"
2
3#include <QtCore/QApplicationStatic>
4#include <QtCore/QFile>
5#include <QtCore/QFileInfo>
6#include <QtCore/QJsonArray>
7#include <QtCore/QJsonParseError>
8#include <QtCore/QObject>
9#include <QtCore/QTranslator>
10
11#include "FactMetaData.h"
12#include "MissionCommandList.h"
13#include "QGCFileHelper.h"
14#include "QGCLoggingCategory.h"
15#include "QGCNetworkHelper.h"
16#include "QGCQGeoCoordinate.h"
17#include "QmlObjectListModel.h"
18#include "JsonParsing.h"
19
20QGC_LOGGING_CATEGORY(JsonHelperLog, "Utilities.JsonHelper")
21
23
24namespace JsonHelper {
25QStringList _addDefaultLocKeys(QJsonObject& jsonObject);
26QJsonObject _translateRoot(QJsonObject& jsonObject, const QString& translateContext, const QStringList& translateKeys);
27QJsonObject _translateObject(QJsonObject& jsonObject, const QString& translateContext,
28 const QStringList& translateKeys);
29QJsonArray _translateArray(QJsonArray& jsonArray, const QString& translateContext, const QStringList& translateKeys);
30
31constexpr const char* _translateKeysKey = "translateKeys";
32constexpr const char* _arrayIDKeysKey = "_arrayIDKeys";
33constexpr const char* _jsonGroundStationKey = "groundStation";
34constexpr const char* _jsonGroundStationValue = "QGroundControl";
35} // namespace JsonHelper
36
37QStringList JsonHelper::_addDefaultLocKeys(QJsonObject& jsonObject)
38{
39 QString translateKeys;
40 const QString fileType = jsonObject[jsonFileTypeKey].toString();
41 if (!fileType.isEmpty()) {
42 if (fileType == MissionCommandList::qgcFileType) {
43 if (jsonObject.contains(_translateKeysKey)) {
44 translateKeys = jsonObject[_translateKeysKey].toString();
45 } else {
46 translateKeys = QStringLiteral("label,enumStrings,friendlyName,description,category");
47 jsonObject[_translateKeysKey] = translateKeys;
48 }
49
50 if (!jsonObject.contains(_arrayIDKeysKey)) {
51 jsonObject[_arrayIDKeysKey] = QStringLiteral("rawName,comment");
52 }
53 } else if (fileType == FactMetaData::qgcFileType) {
54 if (jsonObject.contains(_translateKeysKey)) {
55 translateKeys = jsonObject[_translateKeysKey].toString();
56 } else {
57 translateKeys = QStringLiteral("shortDesc,longDesc,enumStrings");
58 jsonObject[_translateKeysKey] = translateKeys;
59 }
60
61 if (!jsonObject.contains(_arrayIDKeysKey)) {
62 jsonObject[_arrayIDKeysKey] = QStringLiteral("name");
63 }
64 }
65 }
66
67 return translateKeys.split(",");
68}
69
70QJsonObject JsonHelper::_translateRoot(QJsonObject& jsonObject, const QString& translateContext,
71 const QStringList& translateKeys)
72{
73 return _translateObject(jsonObject, translateContext, translateKeys);
74}
75
76QJsonObject JsonHelper::_translateObject(QJsonObject& jsonObject, const QString& translateContext,
77 const QStringList& translateKeys)
78{
79 for (const QString& key : jsonObject.keys()) {
80 if (jsonObject[key].isString()) {
81 QString locString = jsonObject[key].toString();
82 if (!translateKeys.contains(key)) {
83 continue;
84 }
85
86 QString disambiguation;
87 QString disambiguationPrefix("#loc.disambiguation#");
88
89 if (locString.startsWith(disambiguationPrefix)) {
90 locString = locString.right(locString.length() - disambiguationPrefix.length());
91 const int commentEndIndex = locString.indexOf("#");
92 if (commentEndIndex != -1) {
93 disambiguation = locString.left(commentEndIndex);
94 locString = locString.right(locString.length() - disambiguation.length() - 1);
95 }
96 }
97
98 const QString xlatString =
99 translator()->translate(translateContext.toUtf8().constData(), locString.toUtf8().constData(),
100 disambiguation.toUtf8().constData());
101 if (!xlatString.isNull()) {
102 jsonObject[key] = xlatString;
103 }
104 } else if (jsonObject[key].isArray()) {
105 QJsonArray childJsonArray = jsonObject[key].toArray();
106 jsonObject[key] = _translateArray(childJsonArray, translateContext, translateKeys);
107 } else if (jsonObject[key].isObject()) {
108 QJsonObject childJsonObject = jsonObject[key].toObject();
109 jsonObject[key] = _translateObject(childJsonObject, translateContext, translateKeys);
110 }
111 }
112
113 return jsonObject;
114}
115
116QJsonArray JsonHelper::_translateArray(QJsonArray& jsonArray, const QString& translateContext,
117 const QStringList& translateKeys)
118{
119 for (qsizetype i = 0; i < jsonArray.count(); i++) {
120 QJsonObject childJsonObject = jsonArray[i].toObject();
121 jsonArray[i] = _translateObject(childJsonObject, translateContext, translateKeys);
122 }
123
124 return jsonArray;
125}
126
128{
129 return s_jsonTranslator();
130}
131
132bool JsonHelper::loadGeoCoordinate(const QJsonValue& jsonValue, bool altitudeRequired, QGeoCoordinate& coordinate,
133 QString& errorString, bool geoJsonFormat)
134{
135 if (!jsonValue.isArray()) {
136 errorString = QObject::tr("value for coordinate is not array");
137 return false;
138 }
139
140 const QJsonArray coordinateArray = jsonValue.toArray();
141 const int requiredCount = altitudeRequired ? 3 : 2;
142 if (coordinateArray.count() != requiredCount) {
143 errorString = QObject::tr("Coordinate array must contain %1 values").arg(requiredCount);
144 return false;
145 }
146
147 for (const QJsonValue& coordinateValue : coordinateArray) {
148 if ((coordinateValue.type() != QJsonValue::Double) && (coordinateValue.type() != QJsonValue::Null)) {
150 QObject::tr("Coordinate array may only contain double values, found: %1").arg(coordinateValue.type());
151 return false;
152 }
153 }
154
155 if (geoJsonFormat) {
156 coordinate = QGeoCoordinate(coordinateArray[1].toDouble(), coordinateArray[0].toDouble());
157 } else {
158 coordinate = QGeoCoordinate(
159 JsonParsing::possibleNaNJsonValue(coordinateArray[0]),
160 JsonParsing::possibleNaNJsonValue(coordinateArray[1]));
161 }
162
163 if (altitudeRequired) {
164 coordinate.setAltitude(JsonParsing::possibleNaNJsonValue(coordinateArray[2]));
165 }
166
167 return true;
168}
169
170void JsonHelper::saveGeoCoordinate(const QGeoCoordinate& coordinate, bool writeAltitude, QJsonValue& jsonValue,
171 bool geoJsonFormat)
172{
173 QJsonArray coordinateArray;
174
175 if (geoJsonFormat) {
176 coordinateArray << coordinate.longitude() << coordinate.latitude();
177 } else {
178 coordinateArray << coordinate.latitude() << coordinate.longitude();
179 }
180
181 if (writeAltitude) {
182 coordinateArray << coordinate.altitude();
183 }
184
185 jsonValue = QJsonValue(coordinateArray);
186}
187
188bool JsonHelper::validateInternalQGCJsonFile(const QJsonObject& jsonObject, const QString& expectedFileType,
189 int minSupportedVersion, int maxSupportedVersion, int& version,
190 QString& errorString)
191{
192 static const QList<JsonHelper::KeyValidateInfo> requiredKeys = {
193 {jsonFileTypeKey, QJsonValue::String, true},
194 {jsonVersionKey, QJsonValue::Double, true},
195 };
196
197 if (!JsonHelper::validateKeys(jsonObject, requiredKeys, errorString)) {
198 return false;
199 }
200
201 const QString fileTypeValue = jsonObject[jsonFileTypeKey].toString();
202 if (fileTypeValue != expectedFileType) {
203 errorString = QObject::tr("Incorrect file type key expected:%1 actual:%2").arg(expectedFileType, fileTypeValue);
204 return false;
205 }
206
207 version = jsonObject[jsonVersionKey].toInt();
208 if (version < minSupportedVersion) {
209 errorString = QObject::tr("File version %1 is no longer supported").arg(version);
210 return false;
211 }
212
213 if (version > maxSupportedVersion) {
214 errorString = QObject::tr("File version %1 is newer than current supported version %2")
215 .arg(version)
216 .arg(maxSupportedVersion);
217 return false;
218 }
219
220 return true;
221}
222
223bool JsonHelper::validateExternalQGCJsonFile(const QJsonObject& jsonObject, const QString& expectedFileType,
224 int minSupportedVersion, int maxSupportedVersion, int& version,
225 QString& errorString)
226{
227 static const QList<JsonHelper::KeyValidateInfo> requiredKeys = {
228 {_jsonGroundStationKey, QJsonValue::String, true},
229 };
230
231 if (!JsonHelper::validateKeys(jsonObject, requiredKeys, errorString)) {
232 return false;
233 }
234
235 return validateInternalQGCJsonFile(jsonObject, expectedFileType, minSupportedVersion, maxSupportedVersion, version,
237}
238
239QJsonObject JsonHelper::openInternalQGCJsonFile(const QString& jsonFilename, const QString& expectedFileType,
240 int minSupportedVersion, int maxSupportedVersion, int& version,
241 QString& errorString)
242{
243 const QByteArray bytes = QGCFileHelper::readFile(jsonFilename, &errorString);
244 if (bytes.isEmpty() && !errorString.isEmpty()) {
245 return {};
246 }
247
248 QJsonParseError jsonParseError;
249 const QJsonDocument doc = QGCNetworkHelper::parseCompressedJson(bytes, &jsonParseError);
250 if (jsonParseError.error != QJsonParseError::NoError) {
251 errorString = QObject::tr("Unable to parse json file: %1 error: %2 offset: %3")
252 .arg(jsonFilename, jsonParseError.errorString())
253 .arg(jsonParseError.offset);
254 return {};
255 }
256
257 if (!doc.isObject()) {
258 errorString = QObject::tr("Root of json file is not object: %1").arg(jsonFilename);
259 return {};
260 }
261
262 QJsonObject jsonObject = doc.object();
263 const bool success = validateInternalQGCJsonFile(jsonObject, expectedFileType, minSupportedVersion,
264 maxSupportedVersion, version, errorString);
265 if (!success) {
266 errorString = QObject::tr("Json file: '%1'. %2").arg(jsonFilename, errorString);
267 return {};
268 }
269
270 const QStringList translateKeys = _addDefaultLocKeys(jsonObject);
271 const QString context = QFileInfo(jsonFilename).fileName();
272 return _translateRoot(jsonObject, context, translateKeys);
273}
274
275void JsonHelper::saveQGCJsonFileHeader(QJsonObject& jsonObject, const QString& fileType, int version)
276{
278 jsonObject[jsonFileTypeKey] = fileType;
279 jsonObject[jsonVersionKey] = version;
280}
281
282bool JsonHelper::loadGeoCoordinateArray(const QJsonValue& jsonValue, bool altitudeRequired, QVariantList& rgVarPoints,
283 QString& errorString)
284{
285 if (!jsonValue.isArray()) {
286 errorString = QObject::tr("value for coordinate array is not array");
287 return false;
288 }
289
290 const QJsonArray rgJsonPoints = jsonValue.toArray();
291
292 rgVarPoints.clear();
293 for (const QJsonValue& point : rgJsonPoints) {
294 QGeoCoordinate coordinate;
295 if (!JsonHelper::loadGeoCoordinate(point, altitudeRequired, coordinate, errorString)) {
296 return false;
297 }
298 rgVarPoints.append(QVariant::fromValue(coordinate));
299 }
300
301 return true;
302}
303
304bool JsonHelper::loadGeoCoordinateArray(const QJsonValue& jsonValue, bool altitudeRequired,
305 QList<QGeoCoordinate>& rgPoints, QString& errorString)
306{
307 QVariantList rgVarPoints;
308
309 if (!loadGeoCoordinateArray(jsonValue, altitudeRequired, rgVarPoints, errorString)) {
310 return false;
311 }
312
313 rgPoints.clear();
314 for (const QVariant& point : rgVarPoints) {
315 rgPoints.append(point.value<QGeoCoordinate>());
316 }
317
318 return true;
319}
320
321void JsonHelper::saveGeoCoordinateArray(const QVariantList& rgVarPoints, bool writeAltitude, QJsonValue& jsonValue)
322{
323 QJsonArray rgJsonPoints;
324 for (const QVariant& point : rgVarPoints) {
325 QJsonValue jsonPoint;
326 JsonHelper::saveGeoCoordinate(point.value<QGeoCoordinate>(), writeAltitude, jsonPoint);
327 rgJsonPoints.append(jsonPoint);
328 }
329
330 jsonValue = rgJsonPoints;
331}
332
333void JsonHelper::saveGeoCoordinateArray(const QList<QGeoCoordinate>& rgPoints, bool writeAltitude,
334 QJsonValue& jsonValue)
335{
336 QVariantList rgVarPoints;
337 for (const QGeoCoordinate& coord : rgPoints) {
338 rgVarPoints.append(QVariant::fromValue(coord));
339 }
340
341 return saveGeoCoordinateArray(rgVarPoints, writeAltitude, jsonValue);
342}
343
344bool JsonHelper::validateKeys(const QJsonObject& jsonObject, const QList<JsonHelper::KeyValidateInfo>& keyInfo,
345 QString& errorString)
346{
347 QStringList keyList;
348 QList<QJsonValue::Type> typeList;
349
350 for (const JsonHelper::KeyValidateInfo& info : keyInfo) {
351 if (info.required) {
352 keyList.append(info.key);
353 }
354 }
355 if (!JsonParsing::validateRequiredKeys(jsonObject, keyList, errorString)) {
356 return false;
357 }
358
359 keyList.clear();
360 for (const JsonHelper::KeyValidateInfo& info : keyInfo) {
361 keyList.append(info.key);
362 typeList.append(info.type);
363 }
364
365 return JsonParsing::validateKeyTypes(jsonObject, keyList, typeList, errorString);
366}
367
368bool JsonHelper::loadPolygon(const QJsonArray& polygonArray, QmlObjectListModel& list, QObject* parent,
369 QString& errorString)
370{
371 for (const QJsonValue& pointValue : polygonArray) {
372 QGeoCoordinate pointCoord;
373 if (!JsonHelper::loadGeoCoordinate(pointValue, false /* altitudeRequired */, pointCoord, errorString, true)) {
375 return false;
376 }
377 list.append(new QGCQGeoCoordinate(pointCoord, parent));
378 }
379
380 return true;
381}
382
383void JsonHelper::savePolygon(const QmlObjectListModel& list, QJsonArray& polygonArray)
384{
385 for (qsizetype i = 0; i < list.count(); i++) {
386 const QGeoCoordinate vertex = list.value<QGCQGeoCoordinate*>(i)->coordinate();
387
388 QJsonValue jsonValue;
389 JsonHelper::saveGeoCoordinate(vertex, false /* writeAltitude */, jsonValue);
390 polygonArray.append(jsonValue);
391 }
392}
Q_APPLICATION_STATIC(ADSBVehicleManager, _adsbVehicleManager, SettingsManager::instance() ->adsbVehicleManagerSettings())
s_jsonTranslator
Definition JsonHelper.cc:22
QString errorString
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static constexpr const char * qgcFileType
static constexpr const char * qgcFileType
This is a QGeoCoordinate within a QObject such that it can be used on a QmlObjectListModel.
void append(QObject *object)
Caller maintains responsibility for object ownership and deletion.
T value(int index) const
int count() const override final
void clearAndDeleteContents() override final
Clears the list and calls deleteLater on each entry.
bool validateExternalQGCJsonFile(const QJsonObject &jsonObject, const QString &expectedFileType, int minSupportedVersion, int maxSupportedVersion, int &version, QString &errorString)
returned error string if validation fails
void saveGeoCoordinateArray(const QVariantList &rgVarPoints, bool writeAltitude, QJsonValue &jsonValue)
Saves a list of QGeoCoordinates to a json array.
bool loadGeoCoordinateArray(const QJsonValue &jsonValue, bool altitudeRequired, QVariantList &rgVarPoints, QString &errorString)
returned error string if load failure
QJsonObject openInternalQGCJsonFile(const QString &jsonFilename, const QString &expectedFileType, int minSupportedVersion, int maxSupportedVersion, int &version, QString &errorString)
returned error string if validation fails
constexpr const char * _jsonGroundStationKey
Definition JsonHelper.cc:33
QJsonObject _translateRoot(QJsonObject &jsonObject, const QString &translateContext, const QStringList &translateKeys)
Definition JsonHelper.cc:70
constexpr const char * _jsonGroundStationValue
Definition JsonHelper.cc:34
QJsonObject _translateObject(QJsonObject &jsonObject, const QString &translateContext, const QStringList &translateKeys)
Definition JsonHelper.cc:76
constexpr const char * jsonVersionKey
Definition JsonHelper.h:109
QTranslator * translator()
bool loadPolygon(const QJsonArray &polygonArray, QmlObjectListModel &list, QObject *parent, QString &errorString)
Loads a polygon from an array.
constexpr const char * _translateKeysKey
Definition JsonHelper.cc:31
bool validateKeys(const QJsonObject &jsonObject, const QList< KeyValidateInfo > &keyInfo, QString &errorString)
void saveQGCJsonFileHeader(QJsonObject &jsonObject, const QString &fileType, int version)
Saves the standard file header the json object.
QJsonArray _translateArray(QJsonArray &jsonArray, const QString &translateContext, const QStringList &translateKeys)
constexpr const char * jsonFileTypeKey
Definition JsonHelper.h:110
void savePolygon(const QmlObjectListModel &list, QJsonArray &polygonArray)
Saves a polygon to a json array.
bool validateInternalQGCJsonFile(const QJsonObject &jsonObject, const QString &expectedFileType, int minSupportedVersion, int maxSupportedVersion, int &version, QString &errorString)
returned error string if validation fails
constexpr const char * _arrayIDKeysKey
Definition JsonHelper.cc:32
void saveGeoCoordinate(const QGeoCoordinate &coordinate, bool writeAltitude, QJsonValue &jsonValue, bool geoJsonFormat=false)
QStringList _addDefaultLocKeys(QJsonObject &jsonObject)
Definition JsonHelper.cc:37
bool loadGeoCoordinate(const QJsonValue &jsonValue, bool altitudeRequired, QGeoCoordinate &coordinate, QString &errorString, bool geoJsonFormat=false)
if true, use [lon, lat], [lat, lon] otherwise
bool validateKeyTypes(const QJsonObject &jsonObject, const QStringList &keys, const QList< QJsonValue::Type > &types, QString &errorString)
double possibleNaNJsonValue(const QJsonValue &value)
Returns NaN if the value is null, or the value converted to double otherwise.
bool validateRequiredKeys(const QJsonObject &jsonObject, const QStringList &keys, QString &errorString)
Validates that all listed keys are present in the object.
QByteArray readFile(const QString &filePath, QString *errorString, qint64 maxBytes)
QJsonDocument parseCompressedJson(const QByteArray &data, QJsonParseError *error)