QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
JsonParsing.cc
Go to the documentation of this file.
1#include "JsonParsing.h"
2
3#include <limits>
4
5#include <QtCore/QApplicationStatic>
6#include <QtCore/QFileInfo>
7#include <QtCore/QJsonArray>
8#include <QtCore/QJsonParseError>
9#include <QtCore/QObject>
10#include <QtCore/QTranslator>
11
12#include "QGCCompression.h"
13#include "QGCLoggingCategory.h"
14
15QGC_LOGGING_CATEGORY(JsonParsingLog, "Utilities.Parsing.Json")
16
17namespace JsonParsing {
18
19bool isJsonFile(const QByteArray& bytes, QJsonDocument& jsonDoc, QString& errorString)
20{
21 QJsonParseError parseError;
22 jsonDoc = QGCCompression::parseCompressedJson(bytes, &parseError);
23
24 if (parseError.error == QJsonParseError::NoError) {
25 return true;
26 }
27
29 // parseError.offset refers to the decompressed text, so a slice of `bytes` would be garbage.
30 qCDebug(JsonParsingLog) << "Json read error (compressed input) offset" << parseError.offset;
31 } else {
32 const int startPos = qMax(0, parseError.offset - 100);
33 const int length = qMin(bytes.length() - startPos, 200);
34 qCDebug(JsonParsingLog) << "Json read error" << bytes.mid(startPos, length).constData();
35 }
36 errorString = QStringLiteral("%1 (offset %2)").arg(parseError.errorString()).arg(parseError.offset);
37
38 return false;
39}
40
41bool isJsonFile(const QString& fileName, QJsonDocument& jsonDoc, QString& errorString)
42{
43 const QByteArray jsonBytes = QGCCompression::readFile(fileName, &errorString);
44 if (jsonBytes.isEmpty() && !errorString.isEmpty()) {
45 jsonDoc = {};
46 return false;
47 }
48
49 return isJsonFile(jsonBytes, jsonDoc, errorString);
50}
51
52double possibleNaNJsonValue(const QJsonValue& value)
53{
54 if (value.type() == QJsonValue::Null) {
55 return std::numeric_limits<double>::quiet_NaN();
56 }
57
58 return value.toDouble();
59}
60
61} // namespace JsonParsing
62
63// ---------------------------------------------------------------------------
64// QGC json file header / translation utilities
65// ---------------------------------------------------------------------------
66
67namespace {
68
69constexpr const char *_translateKeysKey = "translateKeys";
70constexpr const char *_arrayIDKeysKey = "_arrayIDKeys";
71constexpr const char *_jsonGroundStationKey = "groundStation";
72constexpr const char *_jsonGroundStationValue = "QGroundControl";
73
74Q_APPLICATION_STATIC(QTranslator, s_jsonTranslator);
75
76QJsonObject translateObject(QJsonObject &jsonObject, const QString &translateContext, const QStringList &translateKeys);
77
78QJsonArray translateArray(QJsonArray &jsonArray, const QString &translateContext, const QStringList &translateKeys)
79{
80 for (qsizetype i = 0; i < jsonArray.count(); i++) {
81 QJsonObject childJsonObject = jsonArray[i].toObject();
82 jsonArray[i] = translateObject(childJsonObject, translateContext, translateKeys);
83 }
84 return jsonArray;
85}
86
87QJsonObject translateObject(QJsonObject &jsonObject, const QString &translateContext, const QStringList &translateKeys)
88{
89 for (const QString &key : jsonObject.keys()) {
90 if (jsonObject[key].isString()) {
91 QString locString = jsonObject[key].toString();
92 if (!translateKeys.contains(key)) {
93 continue;
94 }
95
96 QString disambiguation;
97 const QString disambiguationPrefix("#loc.disambiguation#");
98 if (locString.startsWith(disambiguationPrefix)) {
99 locString = locString.right(locString.length() - disambiguationPrefix.length());
100 const int commentEndIndex = locString.indexOf("#");
101 if (commentEndIndex != -1) {
102 disambiguation = locString.left(commentEndIndex);
103 locString = locString.right(locString.length() - disambiguation.length() - 1);
104 }
105 }
106
107 const QString xlatString = JsonParsing::translator()->translate(
108 translateContext.toUtf8().constData(),
109 locString.toUtf8().constData(),
110 disambiguation.toUtf8().constData());
111 if (!xlatString.isNull()) {
112 jsonObject[key] = xlatString;
113 }
114 } else if (jsonObject[key].isArray()) {
115 QJsonArray childJsonArray = jsonObject[key].toArray();
116 jsonObject[key] = translateArray(childJsonArray, translateContext, translateKeys);
117 } else if (jsonObject[key].isObject()) {
118 QJsonObject childJsonObject = jsonObject[key].toObject();
119 jsonObject[key] = translateObject(childJsonObject, translateContext, translateKeys);
120 }
121 }
122 return jsonObject;
123}
124
128QStringList resolveTranslateKeys(QJsonObject &jsonObject,
129 const QStringList &defaultTranslateKeys,
130 const QStringList &defaultArrayIDKeys)
131{
132 QString translateKeys;
133 if (jsonObject.contains(_translateKeysKey)) {
134 translateKeys = jsonObject[_translateKeysKey].toString();
135 } else if (!defaultTranslateKeys.isEmpty()) {
136 translateKeys = defaultTranslateKeys.join(",");
137 jsonObject[_translateKeysKey] = translateKeys;
138 }
139
140 if (!jsonObject.contains(_arrayIDKeysKey) && !defaultArrayIDKeys.isEmpty()) {
141 jsonObject[_arrayIDKeysKey] = defaultArrayIDKeys.join(",");
142 }
143
144 if (translateKeys.isEmpty()) {
145 return {};
146 }
147 return translateKeys.split(",");
148}
149
150} // namespace
151
152namespace JsonParsing {
153
154QTranslator *translator()
155{
156 return s_jsonTranslator();
157}
158
159void saveQGCJsonFileHeader(QJsonObject &jsonObject, const QString &fileType, int version)
160{
161 jsonObject[_jsonGroundStationKey] = _jsonGroundStationValue;
162 jsonObject[jsonFileTypeKey] = fileType;
163 jsonObject[jsonVersionKey] = version;
164}
165
166bool validateInternalQGCJsonFile(const QJsonObject &jsonObject, const QString &expectedFileType,
167 int minSupportedVersion, int maxSupportedVersion, int &version,
168 QString &errorString)
169{
170 static const QList<KeyValidateInfo> requiredKeys = {
171 {jsonFileTypeKey, QJsonValue::String, true},
172 {jsonVersionKey, QJsonValue::Double, true},
173 };
174
175 if (!validateKeys(jsonObject, requiredKeys, errorString)) {
176 return false;
177 }
178
179 const QString fileTypeValue = jsonObject[jsonFileTypeKey].toString();
180 if (fileTypeValue != expectedFileType) {
181 errorString = QObject::tr("Incorrect file type key expected:%1 actual:%2").arg(expectedFileType, fileTypeValue);
182 return false;
183 }
184
185 version = jsonObject[jsonVersionKey].toInt();
186 if (version < minSupportedVersion) {
187 errorString = QObject::tr("File version %1 is no longer supported").arg(version);
188 return false;
189 }
190
191 if (version > maxSupportedVersion) {
192 errorString = QObject::tr("File version %1 is newer than current supported version %2")
193 .arg(version)
194 .arg(maxSupportedVersion);
195 return false;
196 }
197
198 return true;
199}
200
201bool validateExternalQGCJsonFile(const QJsonObject &jsonObject, const QString &expectedFileType,
202 int minSupportedVersion, int maxSupportedVersion, int &version,
203 QString &errorString)
204{
205 static const QList<KeyValidateInfo> requiredKeys = {
206 {_jsonGroundStationKey, QJsonValue::String, true},
207 };
208
209 if (!validateKeys(jsonObject, requiredKeys, errorString)) {
210 return false;
211 }
212
213 return validateInternalQGCJsonFile(jsonObject, expectedFileType, minSupportedVersion, maxSupportedVersion, version,
215}
216
217QJsonObject openInternalQGCJsonFile(const QString &jsonFilename, const QString &expectedFileType,
218 int minSupportedVersion, int maxSupportedVersion, int &version,
219 QString &errorString,
220 const QStringList &defaultTranslateKeys,
221 const QStringList &defaultArrayIDKeys)
222{
223 QJsonDocument doc;
224 if (!isJsonFile(jsonFilename, doc, errorString)) {
225 errorString = QObject::tr("Unable to parse json file: %1 error: %2").arg(jsonFilename, errorString);
226 return {};
227 }
228
229 if (!doc.isObject()) {
230 errorString = QObject::tr("Root of json file is not object: %1").arg(jsonFilename);
231 return {};
232 }
233
234 QJsonObject jsonObject = doc.object();
235 const bool success = validateInternalQGCJsonFile(jsonObject, expectedFileType, minSupportedVersion,
236 maxSupportedVersion, version, errorString);
237 if (!success) {
238 errorString = QObject::tr("Json file: '%1'. %2").arg(jsonFilename, errorString);
239 return {};
240 }
241
242 const QStringList translateKeys = resolveTranslateKeys(jsonObject, defaultTranslateKeys, defaultArrayIDKeys);
243 const QString context = QFileInfo(jsonFilename).fileName();
244 return translateObject(jsonObject, context, translateKeys);
245}
246
247} // namespace JsonParsing
Q_APPLICATION_STATIC(ADSBVehicleManager, _adsbVehicleManager, SettingsManager::instance() ->adsbVehicleManagerSettings())
QString errorString
#define QGC_LOGGING_CATEGORY(name, categoryStr)
std::array< MsgOutKey, 4 > keys
Definition UBXPlan.cc:13
bool validateExternalQGCJsonFile(const QJsonObject &jsonObject, const QString &expectedFileType, int minSupportedVersion, int maxSupportedVersion, int &version, QString &errorString)
bool validateInternalQGCJsonFile(const QJsonObject &jsonObject, const QString &expectedFileType, int minSupportedVersion, int maxSupportedVersion, int &version, QString &errorString)
bool validateKeys(const QJsonObject &jsonObject, const QList< KeyValidateInfo > &keyInfo, QString &errorString)
Validates that all required keys are present and that listed keys have the expected type.
bool isJsonFile(const QByteArray &bytes, QJsonDocument &jsonDoc, QString &errorString)
Determines whether an in-memory byte buffer contains parseable JSON content.
constexpr const char * jsonFileTypeKey
Definition JsonParsing.h:15
QJsonObject openInternalQGCJsonFile(const QString &jsonFilename, const QString &expectedFileType, int minSupportedVersion, int maxSupportedVersion, int &version, QString &errorString, const QStringList &defaultTranslateKeys, const QStringList &defaultArrayIDKeys)
double possibleNaNJsonValue(const QJsonValue &value)
Returns NaN if the value is null, or the value converted to double otherwise.
void saveQGCJsonFileHeader(QJsonObject &jsonObject, const QString &fileType, int version)
Saves the standard QGC file header (groundStation, fileType, version) into the json object.
QTranslator * translator()
Translator used by openInternalQGCJsonFile for localized strings.
constexpr const char * jsonVersionKey
Definition JsonParsing.h:14
bool looksLikeCompressedData(const QByteArray &data)
QByteArray readFile(const QString &filePath, QString *errorString, qint64 maxBytes)
Read file contents, transparently decompressing .gz/.xz/.zst/.bz2/.lz4 files.
QJsonDocument parseCompressedJson(const QByteArray &data, QJsonParseError *error)
Parse JSON from data that may be compressed. Auto-detects gzip/xz/zstd/bzip2/lz4.