1 | #include <string> |
2 | #include <string.h> |
3 | |
4 | #include <Poco/UTF8Encoding.h> |
5 | #include <Poco/NumberParser.h> |
6 | #include <common/JSON.h> |
7 | #include <common/find_symbols.h> |
8 | #include <common/preciseExp10.h> |
9 | |
10 | #include <iostream> |
11 | |
12 | #define JSON_MAX_DEPTH 100 |
13 | |
14 | |
15 | POCO_IMPLEMENT_EXCEPTION(JSONException, Poco::Exception, "JSONException" ) |
16 | |
17 | |
18 | /// Прочитать беззнаковое целое в простом формате из не-0-terminated строки. |
19 | static UInt64 readUIntText(const char * buf, const char * end) |
20 | { |
21 | UInt64 x = 0; |
22 | |
23 | if (buf == end) |
24 | throw JSONException("JSON: cannot parse unsigned integer: unexpected end of data." ); |
25 | |
26 | while (buf != end) |
27 | { |
28 | switch (*buf) |
29 | { |
30 | case '+': |
31 | break; |
32 | case '0': |
33 | case '1': |
34 | case '2': |
35 | case '3': |
36 | case '4': |
37 | case '5': |
38 | case '6': |
39 | case '7': |
40 | case '8': |
41 | case '9': |
42 | x *= 10; |
43 | x += *buf - '0'; |
44 | break; |
45 | default: |
46 | return x; |
47 | } |
48 | ++buf; |
49 | } |
50 | |
51 | return x; |
52 | } |
53 | |
54 | |
55 | /// Прочитать знаковое целое в простом формате из не-0-terminated строки. |
56 | static Int64 readIntText(const char * buf, const char * end) |
57 | { |
58 | bool negative = false; |
59 | UInt64 x = 0; |
60 | |
61 | if (buf == end) |
62 | throw JSONException("JSON: cannot parse signed integer: unexpected end of data." ); |
63 | |
64 | bool run = true; |
65 | while (buf != end && run) |
66 | { |
67 | switch (*buf) |
68 | { |
69 | case '+': |
70 | break; |
71 | case '-': |
72 | negative = true; |
73 | break; |
74 | case '0': |
75 | case '1': |
76 | case '2': |
77 | case '3': |
78 | case '4': |
79 | case '5': |
80 | case '6': |
81 | case '7': |
82 | case '8': |
83 | case '9': |
84 | x *= 10; |
85 | x += *buf - '0'; |
86 | break; |
87 | default: |
88 | run = false; |
89 | break; |
90 | } |
91 | ++buf; |
92 | } |
93 | |
94 | return negative ? -x : x; |
95 | } |
96 | |
97 | |
98 | /// Прочитать число с плавающей запятой в простом формате, с грубым округлением, из не-0-terminated строки. |
99 | static double readFloatText(const char * buf, const char * end) |
100 | { |
101 | bool negative = false; |
102 | double x = 0; |
103 | bool after_point = false; |
104 | double power_of_ten = 1; |
105 | |
106 | if (buf == end) |
107 | throw JSONException("JSON: cannot parse floating point number: unexpected end of data." ); |
108 | |
109 | bool run = true; |
110 | while (buf != end && run) |
111 | { |
112 | switch (*buf) |
113 | { |
114 | case '+': |
115 | break; |
116 | case '-': |
117 | negative = true; |
118 | break; |
119 | case '.': |
120 | after_point = true; |
121 | break; |
122 | case '0': |
123 | case '1': |
124 | case '2': |
125 | case '3': |
126 | case '4': |
127 | case '5': |
128 | case '6': |
129 | case '7': |
130 | case '8': |
131 | case '9': |
132 | if (after_point) |
133 | { |
134 | power_of_ten /= 10; |
135 | x += (*buf - '0') * power_of_ten; |
136 | } |
137 | else |
138 | { |
139 | x *= 10; |
140 | x += *buf - '0'; |
141 | } |
142 | break; |
143 | case 'e': |
144 | case 'E': |
145 | { |
146 | ++buf; |
147 | Int32 exponent = readIntText(buf, end); |
148 | x *= preciseExp10(exponent); |
149 | |
150 | run = false; |
151 | break; |
152 | } |
153 | default: |
154 | run = false; |
155 | break; |
156 | } |
157 | ++buf; |
158 | } |
159 | if (negative) |
160 | x = -x; |
161 | |
162 | return x; |
163 | } |
164 | |
165 | |
166 | void JSON::checkInit() const |
167 | { |
168 | if (!(ptr_begin < ptr_end)) |
169 | throw JSONException("JSON: begin >= end." ); |
170 | |
171 | if (level > JSON_MAX_DEPTH) |
172 | throw JSONException("JSON: too deep." ); |
173 | } |
174 | |
175 | |
176 | JSON::ElementType JSON::getType() const |
177 | { |
178 | switch (*ptr_begin) |
179 | { |
180 | case '{': |
181 | return TYPE_OBJECT; |
182 | case '[': |
183 | return TYPE_ARRAY; |
184 | case 't': |
185 | case 'f': |
186 | return TYPE_BOOL; |
187 | case 'n': |
188 | return TYPE_NULL; |
189 | case '-': |
190 | case '0': |
191 | case '1': |
192 | case '2': |
193 | case '3': |
194 | case '4': |
195 | case '5': |
196 | case '6': |
197 | case '7': |
198 | case '8': |
199 | case '9': |
200 | return TYPE_NUMBER; |
201 | case '"': |
202 | { |
203 | /// Проверим - это просто строка или name-value pair |
204 | Pos after_string = skipString(); |
205 | if (after_string < ptr_end && *after_string == ':') |
206 | return TYPE_NAME_VALUE_PAIR; |
207 | else |
208 | return TYPE_STRING; |
209 | } |
210 | default: |
211 | throw JSONException(std::string("JSON: unexpected char " ) + *ptr_begin + ", expected one of '{[tfn-0123456789\"'" ); |
212 | } |
213 | } |
214 | |
215 | |
216 | void JSON::checkPos(Pos pos) const |
217 | { |
218 | if (pos >= ptr_end) |
219 | throw JSONException("JSON: unexpected end of data." ); |
220 | } |
221 | |
222 | |
223 | JSON::Pos JSON::skipString() const |
224 | { |
225 | //std::cerr << "skipString()\t" << data() << std::endl; |
226 | |
227 | Pos pos = ptr_begin; |
228 | checkPos(pos); |
229 | if (*pos != '"') |
230 | throw JSONException(std::string("JSON: expected \", got " ) + *pos); |
231 | ++pos; |
232 | |
233 | /// fast path: находим следующую двойную кавычку. Если перед ней нет бэкслеша - значит это конец строки (при допущении корректности JSON). |
234 | Pos closing_quote = reinterpret_cast<const char *>(memchr(reinterpret_cast<const void *>(pos), '\"', ptr_end - pos)); |
235 | if (nullptr != closing_quote && closing_quote[-1] != '\\') |
236 | return closing_quote + 1; |
237 | |
238 | /// slow path |
239 | while (pos < ptr_end && *pos != '"') |
240 | { |
241 | if (*pos == '\\') |
242 | { |
243 | ++pos; |
244 | checkPos(pos); |
245 | if (*pos == 'u') |
246 | { |
247 | pos += 4; |
248 | checkPos(pos); |
249 | } |
250 | } |
251 | ++pos; |
252 | } |
253 | |
254 | checkPos(pos); |
255 | if (*pos != '"') |
256 | throw JSONException(std::string("JSON: expected \", got " ) + *pos); |
257 | ++pos; |
258 | |
259 | return pos; |
260 | } |
261 | |
262 | |
263 | JSON::Pos JSON::skipNumber() const |
264 | { |
265 | //std::cerr << "skipNumber()\t" << data() << std::endl; |
266 | |
267 | Pos pos = ptr_begin; |
268 | |
269 | checkPos(pos); |
270 | if (*pos == '-') |
271 | ++pos; |
272 | |
273 | while (pos < ptr_end && *pos >= '0' && *pos <= '9') |
274 | ++pos; |
275 | if (pos < ptr_end && *pos == '.') |
276 | ++pos; |
277 | while (pos < ptr_end && *pos >= '0' && *pos <= '9') |
278 | ++pos; |
279 | if (pos < ptr_end && (*pos == 'e' || *pos == 'E')) |
280 | ++pos; |
281 | if (pos < ptr_end && *pos == '-') |
282 | ++pos; |
283 | while (pos < ptr_end && *pos >= '0' && *pos <= '9') |
284 | ++pos; |
285 | |
286 | return pos; |
287 | } |
288 | |
289 | |
290 | JSON::Pos JSON::skipBool() const |
291 | { |
292 | //std::cerr << "skipBool()\t" << data() << std::endl; |
293 | |
294 | Pos pos = ptr_begin; |
295 | checkPos(pos); |
296 | |
297 | if (*ptr_begin == 't') |
298 | pos += 4; |
299 | else if (*ptr_begin == 'f') |
300 | pos += 5; |
301 | else |
302 | throw JSONException("JSON: expected true or false." ); |
303 | |
304 | return pos; |
305 | } |
306 | |
307 | |
308 | JSON::Pos JSON::skipNull() const |
309 | { |
310 | //std::cerr << "skipNull()\t" << data() << std::endl; |
311 | |
312 | return ptr_begin + 4; |
313 | } |
314 | |
315 | |
316 | JSON::Pos JSON::skipNameValuePair() const |
317 | { |
318 | //std::cerr << "skipNameValuePair()\t" << data() << std::endl; |
319 | |
320 | Pos pos = skipString(); |
321 | checkPos(pos); |
322 | |
323 | if (*pos != ':') |
324 | throw JSONException("JSON: expected :." ); |
325 | ++pos; |
326 | |
327 | return JSON(pos, ptr_end, level + 1).skipElement(); |
328 | |
329 | } |
330 | |
331 | |
332 | JSON::Pos JSON::skipArray() const |
333 | { |
334 | //std::cerr << "skipArray()\t" << data() << std::endl; |
335 | |
336 | if (!isArray()) |
337 | throw JSONException("JSON: expected [" ); |
338 | Pos pos = ptr_begin; |
339 | ++pos; |
340 | checkPos(pos); |
341 | if (*pos == ']') |
342 | return ++pos; |
343 | |
344 | while (1) |
345 | { |
346 | pos = JSON(pos, ptr_end, level + 1).skipElement(); |
347 | |
348 | checkPos(pos); |
349 | |
350 | switch (*pos) |
351 | { |
352 | case ',': |
353 | ++pos; |
354 | break; |
355 | case ']': |
356 | return ++pos; |
357 | default: |
358 | throw JSONException(std::string("JSON: expected one of ',]', got " ) + *pos); |
359 | } |
360 | } |
361 | } |
362 | |
363 | |
364 | JSON::Pos JSON::skipObject() const |
365 | { |
366 | //std::cerr << "skipObject()\t" << data() << std::endl; |
367 | |
368 | if (!isObject()) |
369 | throw JSONException("JSON: expected {" ); |
370 | Pos pos = ptr_begin; |
371 | ++pos; |
372 | checkPos(pos); |
373 | if (*pos == '}') |
374 | return ++pos; |
375 | |
376 | while (1) |
377 | { |
378 | pos = JSON(pos, ptr_end, level + 1).skipNameValuePair(); |
379 | |
380 | checkPos(pos); |
381 | |
382 | switch (*pos) |
383 | { |
384 | case ',': |
385 | ++pos; |
386 | break; |
387 | case '}': |
388 | return ++pos; |
389 | default: |
390 | throw JSONException(std::string("JSON: expected one of ',}', got " ) + *pos); |
391 | } |
392 | } |
393 | } |
394 | |
395 | |
396 | JSON::Pos JSON::skipElement() const |
397 | { |
398 | //std::cerr << "skipElement()\t" << data() << std::endl; |
399 | |
400 | ElementType type = getType(); |
401 | |
402 | switch(type) |
403 | { |
404 | case TYPE_NULL: |
405 | return skipNull(); |
406 | case TYPE_BOOL: |
407 | return skipBool(); |
408 | case TYPE_NUMBER: |
409 | return skipNumber(); |
410 | case TYPE_STRING: |
411 | return skipString(); |
412 | case TYPE_NAME_VALUE_PAIR: |
413 | return skipNameValuePair(); |
414 | case TYPE_ARRAY: |
415 | return skipArray(); |
416 | case TYPE_OBJECT: |
417 | return skipObject(); |
418 | default: |
419 | throw JSONException("Logical error in JSON: unknown element type: " + std::to_string(type)); |
420 | } |
421 | } |
422 | |
423 | size_t JSON::size() const |
424 | { |
425 | size_t i = 0; |
426 | |
427 | for (const_iterator it = begin(); it != end(); ++it) |
428 | ++i; |
429 | |
430 | return i; |
431 | } |
432 | |
433 | |
434 | bool JSON::empty() const |
435 | { |
436 | return size() == 0; |
437 | } |
438 | |
439 | |
440 | JSON JSON::operator[] (size_t n) const |
441 | { |
442 | ElementType type = getType(); |
443 | |
444 | if (type != TYPE_ARRAY) |
445 | throw JSONException("JSON: not array when calling operator[](size_t) method." ); |
446 | |
447 | Pos pos = ptr_begin; |
448 | ++pos; |
449 | checkPos(pos); |
450 | |
451 | size_t i = 0; |
452 | const_iterator it = begin(); |
453 | while (i < n && it != end()) |
454 | ++it, ++i; |
455 | |
456 | if (i != n) |
457 | throw JSONException("JSON: array index " + std::to_string(n) + " out of bounds." ); |
458 | |
459 | return *it; |
460 | } |
461 | |
462 | |
463 | JSON::Pos JSON::searchField(const char * data, size_t size) const |
464 | { |
465 | ElementType type = getType(); |
466 | |
467 | if (type != TYPE_OBJECT) |
468 | throw JSONException("JSON: not object when calling operator[](const char *) or has(const char *) method." ); |
469 | |
470 | const_iterator it = begin(); |
471 | for (; it != end(); ++it) |
472 | { |
473 | if (!it->hasEscapes()) |
474 | { |
475 | if (static_cast<int>(size) + 2 > it->dataEnd() - it->data()) |
476 | continue; |
477 | if (!strncmp(data, it->data() + 1, size)) |
478 | break; |
479 | } |
480 | else |
481 | { |
482 | std::string current_name = it->getName(); |
483 | if (current_name.size() == size && 0 == memcmp(current_name.data(), data, size)) |
484 | break; |
485 | } |
486 | } |
487 | |
488 | if (it == end()) |
489 | return nullptr; |
490 | else |
491 | return it->data(); |
492 | } |
493 | |
494 | |
495 | bool JSON::hasEscapes() const |
496 | { |
497 | Pos pos = ptr_begin + 1; |
498 | while (pos < ptr_end && *pos != '"' && *pos != '\\') |
499 | ++pos; |
500 | |
501 | if (*pos == '"') |
502 | return false; |
503 | else if (*pos == '\\') |
504 | return true; |
505 | throw JSONException("JSON: unexpected end of data." ); |
506 | } |
507 | |
508 | |
509 | bool JSON::hasSpecialChars() const |
510 | { |
511 | Pos pos = ptr_begin + 1; |
512 | while (pos < ptr_end && *pos != '"' |
513 | && *pos != '\\' && *pos != '\r' && *pos != '\n' && *pos != '\t' |
514 | && *pos != '\f' && *pos != '\b' && *pos != '\0' && *pos != '\'') |
515 | ++pos; |
516 | |
517 | if (*pos == '"') |
518 | return false; |
519 | else if (pos < ptr_end) |
520 | return true; |
521 | throw JSONException("JSON: unexpected end of data." ); |
522 | } |
523 | |
524 | |
525 | JSON JSON::operator[] (const std::string & name) const |
526 | { |
527 | Pos pos = searchField(name); |
528 | if (!pos) |
529 | throw JSONException("JSON: there is no element '" + std::string(name) + "' in object." ); |
530 | |
531 | return JSON(pos, ptr_end, level + 1).getValue(); |
532 | } |
533 | |
534 | |
535 | bool JSON::has(const char * data, size_t size) const |
536 | { |
537 | return nullptr != searchField(data, size); |
538 | } |
539 | |
540 | |
541 | double JSON::getDouble() const |
542 | { |
543 | return readFloatText(ptr_begin, ptr_end); |
544 | } |
545 | |
546 | Int64 JSON::getInt() const |
547 | { |
548 | return readIntText(ptr_begin, ptr_end); |
549 | } |
550 | |
551 | UInt64 JSON::getUInt() const |
552 | { |
553 | return readUIntText(ptr_begin, ptr_end); |
554 | } |
555 | |
556 | bool JSON::getBool() const |
557 | { |
558 | if (*ptr_begin == 't') |
559 | return true; |
560 | if (*ptr_begin == 'f') |
561 | return false; |
562 | throw JSONException("JSON: cannot parse boolean." ); |
563 | } |
564 | |
565 | std::string JSON::getString() const |
566 | { |
567 | Pos s = ptr_begin; |
568 | |
569 | if (*s != '"') |
570 | throw JSONException(std::string("JSON: expected \", got " ) + *s); |
571 | ++s; |
572 | checkPos(s); |
573 | |
574 | std::string buf; |
575 | do |
576 | { |
577 | Pos p = find_first_symbols<'\\','"'>(s, ptr_end); |
578 | if (p >= ptr_end) |
579 | { |
580 | break; |
581 | } |
582 | buf.append(s, p); |
583 | s = p; |
584 | switch (*s) |
585 | { |
586 | case '\\': |
587 | ++s; |
588 | checkPos(s); |
589 | |
590 | switch(*s) |
591 | { |
592 | case '"': |
593 | buf += '"'; |
594 | break; |
595 | case '\\': |
596 | buf += '\\'; |
597 | break; |
598 | case '/': |
599 | buf += '/'; |
600 | break; |
601 | case 'b': |
602 | buf += '\b'; |
603 | break; |
604 | case 'f': |
605 | buf += '\f'; |
606 | break; |
607 | case 'n': |
608 | buf += '\n'; |
609 | break; |
610 | case 'r': |
611 | buf += '\r'; |
612 | break; |
613 | case 't': |
614 | buf += '\t'; |
615 | break; |
616 | case 'u': |
617 | { |
618 | Poco::UTF8Encoding utf8; |
619 | |
620 | ++s; |
621 | checkPos(s + 4); |
622 | std::string hex(s, 4); |
623 | s += 3; |
624 | int unicode; |
625 | try |
626 | { |
627 | unicode = Poco::NumberParser::parseHex(hex); |
628 | } |
629 | catch (const Poco::SyntaxException & e) |
630 | { |
631 | throw JSONException("JSON: incorrect syntax: incorrect HEX code." ); |
632 | } |
633 | buf.resize(buf.size() + 6); /// максимальный размер UTF8 многобайтовой последовательности |
634 | int res = utf8.convert(unicode, |
635 | reinterpret_cast<unsigned char *>(const_cast<char*>(buf.data())) + buf.size() - 6, 6); |
636 | if (!res) |
637 | throw JSONException("JSON: cannot convert unicode " + std::to_string(unicode) |
638 | + " to UTF8." ); |
639 | buf.resize(buf.size() - 6 + res); |
640 | break; |
641 | } |
642 | default: |
643 | buf += *s; |
644 | break; |
645 | } |
646 | ++s; |
647 | break; |
648 | case '"': |
649 | return buf; |
650 | default: |
651 | throw JSONException("find_first_symbols<...>() failed in unexpected way" ); |
652 | } |
653 | } while (s < ptr_end); |
654 | throw JSONException("JSON: incorrect syntax (expected end of string, found end of JSON)." ); |
655 | } |
656 | |
657 | std::string JSON::getName() const |
658 | { |
659 | return getString(); |
660 | } |
661 | |
662 | StringRef JSON::getRawString() const |
663 | { |
664 | Pos s = ptr_begin; |
665 | if (*s != '"') |
666 | throw JSONException(std::string("JSON: expected \", got " ) + *s); |
667 | while (++s != ptr_end && *s != '"'); |
668 | if (s != ptr_end ) |
669 | return StringRef(ptr_begin + 1, s - ptr_begin - 1); |
670 | throw JSONException("JSON: incorrect syntax (expected end of string, found end of JSON)." ); |
671 | } |
672 | |
673 | StringRef JSON::getRawName() const |
674 | { |
675 | return getRawString(); |
676 | } |
677 | |
678 | JSON JSON::getValue() const |
679 | { |
680 | Pos pos = skipString(); |
681 | checkPos(pos); |
682 | if (*pos != ':') |
683 | throw JSONException("JSON: expected :." ); |
684 | ++pos; |
685 | checkPos(pos); |
686 | return JSON(pos, ptr_end, level + 1); |
687 | } |
688 | |
689 | |
690 | double JSON::toDouble() const |
691 | { |
692 | ElementType type = getType(); |
693 | |
694 | if (type == TYPE_NUMBER) |
695 | return getDouble(); |
696 | else if (type == TYPE_STRING) |
697 | return JSON(ptr_begin + 1, ptr_end, level + 1).getDouble(); |
698 | else |
699 | throw JSONException("JSON: cannot convert value to double." ); |
700 | } |
701 | |
702 | Int64 JSON::toInt() const |
703 | { |
704 | ElementType type = getType(); |
705 | |
706 | if (type == TYPE_NUMBER) |
707 | return getInt(); |
708 | else if (type == TYPE_STRING) |
709 | return JSON(ptr_begin + 1, ptr_end, level + 1).getInt(); |
710 | else |
711 | throw JSONException("JSON: cannot convert value to signed integer." ); |
712 | } |
713 | |
714 | UInt64 JSON::toUInt() const |
715 | { |
716 | ElementType type = getType(); |
717 | |
718 | if (type == TYPE_NUMBER) |
719 | return getUInt(); |
720 | else if (type == TYPE_STRING) |
721 | return JSON(ptr_begin + 1, ptr_end, level + 1).getUInt(); |
722 | else |
723 | throw JSONException("JSON: cannot convert value to unsigned integer." ); |
724 | } |
725 | |
726 | std::string JSON::toString() const |
727 | { |
728 | ElementType type = getType(); |
729 | |
730 | if (type == TYPE_STRING) |
731 | return getString(); |
732 | else |
733 | { |
734 | Pos pos = skipElement(); |
735 | return std::string(ptr_begin, pos - ptr_begin); |
736 | } |
737 | } |
738 | |
739 | |
740 | JSON::iterator JSON::iterator::begin() const |
741 | { |
742 | ElementType type = getType(); |
743 | |
744 | if (type != TYPE_ARRAY && type != TYPE_OBJECT) |
745 | throw JSONException("JSON: not array or object when calling begin() method." ); |
746 | |
747 | //std::cerr << "begin()\t" << data() << std::endl; |
748 | |
749 | Pos pos = ptr_begin + 1; |
750 | checkPos(pos); |
751 | if (*pos == '}' || *pos == ']') |
752 | return end(); |
753 | |
754 | return JSON(pos, ptr_end, level + 1); |
755 | } |
756 | |
757 | JSON::iterator JSON::iterator::end() const |
758 | { |
759 | return JSON(nullptr, ptr_end, level + 1); |
760 | } |
761 | |
762 | JSON::iterator & JSON::iterator::operator++() |
763 | { |
764 | Pos pos = skipElement(); |
765 | checkPos(pos); |
766 | |
767 | if (*pos != ',') |
768 | ptr_begin = nullptr; |
769 | else |
770 | { |
771 | ++pos; |
772 | checkPos(pos); |
773 | ptr_begin = pos; |
774 | } |
775 | |
776 | return *this; |
777 | } |
778 | |
779 | JSON::iterator JSON::iterator::operator++(int) |
780 | { |
781 | iterator copy(*this); |
782 | ++*this; |
783 | return copy; |
784 | } |
785 | |
786 | template <> |
787 | double JSON::get<double>() const |
788 | { |
789 | return getDouble(); |
790 | } |
791 | |
792 | template <> |
793 | std::string JSON::get<std::string>() const |
794 | { |
795 | return getString(); |
796 | } |
797 | |
798 | template <> |
799 | Int64 JSON::get<Int64>() const |
800 | { |
801 | return getInt(); |
802 | } |
803 | |
804 | template <> |
805 | UInt64 JSON::get<UInt64>() const |
806 | { |
807 | return getUInt(); |
808 | } |
809 | |
810 | template <> |
811 | bool JSON::get<bool>() const |
812 | { |
813 | return getBool(); |
814 | } |
815 | |
816 | template <> |
817 | bool JSON::isType<std::string>() const |
818 | { |
819 | return isString(); |
820 | } |
821 | |
822 | template <> |
823 | bool JSON::isType<UInt64>() const |
824 | { |
825 | return isNumber(); |
826 | } |
827 | |
828 | template <> |
829 | bool JSON::isType<Int64>() const |
830 | { |
831 | return isNumber(); |
832 | } |
833 | |
834 | template <> |
835 | bool JSON::isType<bool>() const |
836 | { |
837 | return isBool(); |
838 | } |
839 | |
840 | |