1 /* This file is part of qjson
2 *
3 * Copyright (C) 2009 Till Adam <adam@kde.org>
4 * Copyright (C) 2009 Flavio Castelli <flavio@castelli.name>
5 * Copyright (C) 2016 Anton Kudryavtsev <a.kudryavtsev@netris.ru>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License version 2.1, as published by the Free Software Foundation.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this library; see the file COPYING.LIB. If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22 #include "serializer.h"
23
24 #include <QtCore/QDataStream>
25 #include <QtCore/QStringList>
26 #include <QtCore/QVariant>
27
28 // cmath does #undef for isnan and isinf macroses what can be defined in math.h
29 #if defined(Q_OS_SYMBIAN) || defined(Q_OS_ANDROID) || defined(Q_OS_BLACKBERRY) || defined(Q_OS_SOLARIS)
30 # include <math.h>
31 #else
32 # include <cmath>
33 #endif
34
35 #ifdef Q_OS_SOLARIS
36 # ifndef isinf
37 # include <ieeefp.h>
38 # define isinf(x) (!finite((x)) && (x)==(x))
39 # endif
40 #endif
41
42 #ifdef _MSC_VER // using MSVC compiler
43 #include <float.h>
44 #endif
45
46 using namespace QJson;
47
48 class Serializer::SerializerPrivate {
49 public:
SerializerPrivate()50 SerializerPrivate() :
51 specialNumbersAllowed(false),
52 indentMode(QJson::IndentNone),
53 doublePrecision(6) {
54 errorMessage.clear();
55 }
56 QString errorMessage;
57 bool specialNumbersAllowed;
58 IndentMode indentMode;
59 int doublePrecision;
60
61 QByteArray serialize( const QVariant &v, bool *ok, int indentLevel = 0);
62
63 static QByteArray buildIndent(int spaces);
64 static QByteArray escapeString( const QString& str );
65 static QByteArray join( const QList<QByteArray>& list, const QByteArray& sep );
66 static QByteArray join( const QList<QByteArray>& list, char sep );
67 };
68
join(const QList<QByteArray> & list,const QByteArray & sep)69 QByteArray Serializer::SerializerPrivate::join( const QList<QByteArray>& list, const QByteArray& sep ) {
70 QByteArray res;
71 Q_FOREACH( const QByteArray& i, list ) {
72 if ( !res.isEmpty() )
73 res += sep;
74 res += i;
75 }
76 return res;
77 }
78
join(const QList<QByteArray> & list,char sep)79 QByteArray Serializer::SerializerPrivate::join( const QList<QByteArray>& list, char sep ) {
80 QByteArray res;
81 Q_FOREACH( const QByteArray& i, list ) {
82 if ( !res.isEmpty() )
83 res += sep;
84 res += i;
85 }
86 return res;
87 }
88
serialize(const QVariant & v,bool * ok,int indentLevel)89 QByteArray Serializer::SerializerPrivate::serialize( const QVariant &v, bool *ok, int indentLevel)
90 {
91 QByteArray str;
92 const QVariant::Type type = v.type();
93
94 if ( ! v.isValid() ) { // invalid or null?
95 str = "null";
96 } else if (( type == QVariant::List ) || ( type == QVariant::StringList )) { // an array or a stringlist?
97 const QVariantList list = v.toList();
98 QList<QByteArray> values;
99 Q_FOREACH( const QVariant& var, list )
100 {
101 QByteArray serializedValue;
102
103 serializedValue = serialize( var, ok, indentLevel+1);
104
105 if ( !*ok ) {
106 break;
107 }
108 switch(indentMode) {
109 case QJson::IndentFull :
110 case QJson::IndentMedium :
111 case QJson::IndentMinimum :
112 values << serializedValue;
113 break;
114 case QJson::IndentCompact :
115 case QJson::IndentNone :
116 default:
117 values << serializedValue.trimmed();
118 break;
119 }
120 }
121
122 if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull ) {
123 QByteArray indent = buildIndent(indentLevel);
124 str = indent + "[\n" + join( values, ",\n" ) + '\n' + indent + ']';
125 }
126 else if (indentMode == QJson::IndentMinimum) {
127 QByteArray indent = buildIndent(indentLevel);
128 str = indent + "[\n" + join( values, ",\n" ) + '\n' + indent + ']';
129 }
130 else if (indentMode == QJson::IndentCompact) {
131 str = '[' + join( values, "," ) + ']';
132 }
133 else {
134 str = "[ " + join( values, ", " ) + " ]";
135 }
136
137 } else if ( type == QVariant::Map ) { // variant is a map?
138 const QVariantMap vmap = v.toMap();
139
140 if (indentMode == QJson::IndentMinimum) {
141 QByteArray indent = buildIndent(indentLevel);
142 str = indent + "{ ";
143 }
144 else if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull) {
145 QByteArray indent = buildIndent(indentLevel);
146 QByteArray nextindent = buildIndent(indentLevel + 1);
147 str = indent + "{\n" + nextindent;
148 }
149 else if (indentMode == QJson::IndentCompact) {
150 str = "{";
151 }
152 else {
153 str = "{ ";
154 }
155
156 QList<QByteArray> pairs;
157 for (QVariantMap::const_iterator it = vmap.begin(), end = vmap.end(); it != end; ++it) {
158 indentLevel++;
159 QByteArray serializedValue = serialize( it.value(), ok, indentLevel);
160 indentLevel--;
161 if ( !*ok ) {
162 break;
163 }
164 QByteArray key = escapeString( it.key() );
165 QByteArray value = serializedValue.trimmed();
166 if (indentMode == QJson::IndentCompact) {
167 pairs << key + ':' + value;
168 } else {
169 pairs << key + " : " + value;
170 }
171 }
172
173 if (indentMode == QJson::IndentFull) {
174 QByteArray indent = buildIndent(indentLevel + 1);
175 str += join( pairs, ",\n" + indent);
176 }
177 else if (indentMode == QJson::IndentCompact) {
178 str += join( pairs, ',' );
179 }
180 else {
181 str += join( pairs, ", " );
182 }
183
184 if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull) {
185 QByteArray indent = buildIndent(indentLevel);
186 str += '\n' + indent + '}';
187 }
188 else if (indentMode == QJson::IndentCompact) {
189 str += '}';
190 }
191 else {
192 str += " }";
193 }
194
195 } else if ( type == QVariant::Hash ) { // variant is a hash?
196 const QVariantHash vhash = v.toHash();
197
198 if (indentMode == QJson::IndentMinimum) {
199 QByteArray indent = buildIndent(indentLevel);
200 str = indent + "{ ";
201 }
202 else if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull) {
203 QByteArray indent = buildIndent(indentLevel);
204 QByteArray nextindent = buildIndent(indentLevel + 1);
205 str = indent + "{\n" + nextindent;
206 }
207 else if (indentMode == QJson::IndentCompact) {
208 str = "{";
209 }
210 else {
211 str = "{ ";
212 }
213
214 QList<QByteArray> pairs;
215 for (QVariantHash::const_iterator it = vhash.begin(), end = vhash.end(); it != end; ++it) {
216 QByteArray serializedValue = serialize( it.value(), ok, indentLevel + 1);
217
218 if ( !*ok ) {
219 break;
220 }
221 QByteArray key = escapeString( it.key() );
222 QByteArray value = serializedValue.trimmed();
223 if (indentMode == QJson::IndentCompact) {
224 pairs << key + ':' + value;
225 } else {
226 pairs << key + " : " + value;
227 }
228 }
229
230 if (indentMode == QJson::IndentFull) {
231 QByteArray indent = buildIndent(indentLevel + 1);
232 str += join( pairs, ",\n" + indent);
233 }
234 else if (indentMode == QJson::IndentCompact) {
235 str += join( pairs, ',' );
236 }
237 else {
238 str += join( pairs, ", " );
239 }
240
241 if (indentMode == QJson::IndentMedium || indentMode == QJson::IndentFull) {
242 QByteArray indent = buildIndent(indentLevel);
243 str += '\n' + indent + '}';
244 }
245 else if (indentMode == QJson::IndentCompact) {
246 str += '}';
247 }
248 else {
249 str += " }";
250 }
251
252 } else {
253 // Add indent, we may need to remove it later for some layouts
254 switch(indentMode) {
255 case QJson::IndentFull :
256 case QJson::IndentMedium :
257 case QJson::IndentMinimum :
258 str += buildIndent(indentLevel);
259 break;
260 case QJson::IndentCompact :
261 case QJson::IndentNone :
262 default:
263 break;
264 }
265
266 if (( type == QVariant::String ) || ( type == QVariant::ByteArray )) { // a string or a byte array?
267 str += escapeString( v.toString() );
268 } else if (( type == QVariant::Double) || ((QMetaType::Type)type == QMetaType::Float)) { // a double or a float?
269 const double value = v.toDouble();
270 #if defined _WIN32 && !defined(Q_OS_SYMBIAN)
271 const bool special = _isnan(value) || !_finite(value);
272 #elif defined(Q_OS_SYMBIAN) || defined(Q_OS_ANDROID) || defined(Q_OS_BLACKBERRY) || defined(Q_OS_SOLARIS)
273 const bool special = isnan(value) || isinf(value);
274 #else
275 const bool special = std::isnan(value) || std::isinf(value);
276 #endif
277 if (special) {
278 if (specialNumbersAllowed) {
279 #if defined _WIN32 && !defined(Q_OS_SYMBIAN)
280 if (_isnan(value)) {
281 #elif defined(Q_OS_SYMBIAN) || defined(Q_OS_ANDROID) || defined(Q_OS_BLACKBERRY) || defined(Q_OS_SOLARIS)
282 if (isnan(value)) {
283 #else
284 if (std::isnan(value)) {
285 #endif
286 str += "NaN";
287 } else {
288 if (value<0) {
289 str += '-';
290 }
291 str += "Infinity";
292 }
293 } else {
294 errorMessage += QLatin1String("Attempt to write NaN or infinity, which is not supported by json\n");
295 *ok = false;
296 }
297 } else {
298 str = QByteArray::number( value , 'g', doublePrecision);
299 if( !str.contains( '.' ) && !str.contains( 'e' ) ) {
300 str += ".0";
301 }
302 }
303 } else if ( type == QVariant::Bool ) { // boolean value?
304 str += ( v.toBool() ? "true" : "false" );
305 } else if ( type == QVariant::ULongLong ) { // large unsigned number?
306 str += QByteArray::number( v.value<qulonglong>() );
307 } else if ( type == QVariant::UInt ) { // unsigned int number?
308 str += QByteArray::number( v.value<quint32>() );
309 } else if ( v.canConvert<qlonglong>() ) { // any signed number?
310 str += QByteArray::number( v.value<qlonglong>() );
311 } else if ( v.canConvert<int>() ) { // unsigned short number?
312 str += QByteArray::number( v.value<int>() );
313 } else if ( v.canConvert<QString>() ){ // can value be converted to string?
314 // this will catch QDate, QDateTime, QUrl, ...
315 str += escapeString( v.toString() );
316 //TODO: catch other values like QImage, QRect, ...
317 } else {
318 *ok = false;
319 errorMessage += QLatin1String("Cannot serialize ");
320 errorMessage += v.toString();
321 errorMessage += QLatin1String(" because type ");
322 errorMessage += QLatin1String(v.typeName());
323 errorMessage += QLatin1String(" is not supported by QJson\n");
324 }
325 }
326 if ( *ok )
327 {
328 return str;
329 }
330 else
331 return QByteArray();
332 }
333
334 QByteArray Serializer::SerializerPrivate::buildIndent(int spaces)
335 {
336 QByteArray indent;
337 if (spaces < 0) {
338 spaces = 0;
339 }
340 for (int i = 0; i < spaces; i++ ) {
341 indent += ' ';
342 }
343 return indent;
344 }
345
346 QByteArray Serializer::SerializerPrivate::escapeString( const QString& str )
347 {
348 QByteArray result;
349 result.reserve(str.size() + 2);
350 result.append('\"');
351 for (QString::const_iterator it = str.begin(), end = str.end(); it != end; ++it) {
352 ushort unicode = it->unicode();
353 switch ( unicode ) {
354 case '\"':
355 result.append("\\\"");
356 break;
357 case '\\':
358 result.append("\\\\");
359 break;
360 case '\b':
361 result.append("\\b");
362 break;
363 case '\f':
364 result.append("\\f");
365 break;
366 case '\n':
367 result.append("\\n");
368 break;
369 case '\r':
370 result.append("\\r");
371 break;
372 case '\t':
373 result.append("\\t");
374 break;
375 default:
376 if ( unicode > 0x1F && unicode < 128 ) {
377 result.append(static_cast<char>(unicode));
378 } else {
379 char escaped[7];
380 qsnprintf(escaped, sizeof(escaped)/sizeof(char), "\\u%04x", unicode);
381 result.append(escaped);
382 }
383 }
384 }
385 result.append('\"');
386 return result;
387 }
388
389 Serializer::Serializer()
390 : d( new SerializerPrivate )
391 {
392 }
393
394 Serializer::~Serializer() {
395 delete d;
396 }
397
398 void Serializer::serialize( const QVariant& v, QIODevice* io, bool* ok)
399 {
400 Q_ASSERT( io );
401 *ok = true;
402
403 if (!io->isOpen()) {
404 if (!io->open(QIODevice::WriteOnly)) {
405 d->errorMessage = QLatin1String("Error opening device");
406 *ok = false;
407 return;
408 }
409 }
410
411 if (!io->isWritable()) {
412 d->errorMessage = QLatin1String("Device is not readable");
413 io->close();
414 *ok = false;
415 return;
416 }
417
418 const QByteArray str = serialize( v, ok);
419 if (*ok && (io->write(str) != str.count())) {
420 *ok = false;
421 d->errorMessage = QLatin1String("Something went wrong while writing to IO device");
422 }
423 }
424
425 QByteArray Serializer::serialize( const QVariant &v)
426 {
427 bool ok;
428
429 return serialize(v, &ok);
430 }
431
432 QByteArray Serializer::serialize( const QVariant &v, bool *ok)
433 {
434 bool _ok = true;
435 d->errorMessage.clear();
436
437 if (ok) {
438 *ok = true;
439 } else {
440 ok = &_ok;
441 }
442
443 return d->serialize(v, ok);
444 }
445
446 void QJson::Serializer::allowSpecialNumbers(bool allow) {
447 d->specialNumbersAllowed = allow;
448 }
449
450 bool QJson::Serializer::specialNumbersAllowed() const {
451 return d->specialNumbersAllowed;
452 }
453
454 void QJson::Serializer::setIndentMode(IndentMode mode) {
455 d->indentMode = mode;
456 }
457
458 void QJson::Serializer::setDoublePrecision(int precision) {
459 d->doublePrecision = precision;
460 }
461
462 IndentMode QJson::Serializer::indentMode() const {
463 return d->indentMode;
464 }
465
466 QString QJson::Serializer::errorMessage() const {
467 return d->errorMessage;
468 }
469
470