JsonCpp project page Classes Namespace JsonCpp home page

json_writer.cpp
Go to the documentation of this file.
1// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors
2// Distributed under MIT license, or public domain if desired and
3// recognized in your jurisdiction.
4// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
5
6#if !defined(JSON_IS_AMALGAMATION)
7#include "json_tool.h"
8#include <json/writer.h>
9#endif // if !defined(JSON_IS_AMALGAMATION)
10#include <algorithm>
11#include <cassert>
12#include <cctype>
13#include <cmath>
14#include <cstdio>
15#include <cstring>
16#include <iomanip>
17#include <memory>
18#include <set>
19#include <sstream>
20#include <utility>
21
22#if defined(_MSC_VER)
23// Disable warning about strdup being deprecated.
24#pragma warning(disable : 4996)
25#endif
26
27namespace Json {
28
29using StreamWriterPtr = std::unique_ptr<StreamWriter>;
30
32 UIntToStringBuffer buffer;
33 char* current = buffer + sizeof(buffer);
34 if (value == Value::minLargestInt) {
36 *--current = '-';
37 } else if (value < 0) {
38 uintToString(LargestUInt(-value), current);
39 *--current = '-';
40 } else {
41 uintToString(LargestUInt(value), current);
42 }
43 assert(current >= buffer);
44 return current;
45}
46
48 UIntToStringBuffer buffer;
49 char* current = buffer + sizeof(buffer);
50 uintToString(value, current);
51 assert(current >= buffer);
52 return current;
53}
54
55#if defined(JSON_HAS_INT64)
56
58
60
61#endif // # if defined(JSON_HAS_INT64)
62
63namespace {
64String valueToString(double value, bool useSpecialFloats,
65 unsigned int precision, PrecisionType precisionType) {
66 // Print into the buffer. We need not request the alternative representation
67 // that always has a decimal point because JSON doesn't distinguish the
68 // concepts of reals and integers.
69 if (!std::isfinite(value)) {
70 if (std::isnan(value))
71 return useSpecialFloats ? "NaN" : "null";
72 if (value < 0)
73 return useSpecialFloats ? "-Infinity" : "-1e+9999";
74 return useSpecialFloats ? "Infinity" : "1e+9999";
75 }
76
77 String buffer(size_t(36), '\0');
78 while (true) {
79 int len = jsoncpp_snprintf(
80 &*buffer.begin(), buffer.size(),
81 (precisionType == PrecisionType::significantDigits) ? "%.*g" : "%.*f",
82 precision, value);
83 assert(len >= 0);
84 auto wouldPrint = static_cast<size_t>(len);
85 if (wouldPrint >= buffer.size()) {
86 buffer.resize(wouldPrint + 1);
87 continue;
88 }
89 buffer.resize(wouldPrint);
90 break;
91 }
92
93 buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end());
94
95 // try to ensure we preserve the fact that this was given to us as a double on
96 // input
97 if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) {
98 buffer += ".0";
99 }
100
101 // strip the zero padding from the right
102 if (precisionType == PrecisionType::decimalPlaces) {
103 buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end(), precision),
104 buffer.end());
105 }
106
107 return buffer;
108}
109} // namespace
110
111String valueToString(double value, unsigned int precision,
112 PrecisionType precisionType) {
113 return valueToString(value, false, precision, precisionType);
114}
115
116String valueToString(bool value) { return value ? "true" : "false"; }
117
118static bool doesAnyCharRequireEscaping(char const* s, size_t n) {
119 assert(s || !n);
120
121 return std::any_of(s, s + n, [](unsigned char c) {
122 return c == '\\' || c == '"' || c < 0x20 || c > 0x7F;
123 });
124}
125
126static unsigned int utf8ToCodepoint(const char*& s, const char* e) {
127 const unsigned int REPLACEMENT_CHARACTER = 0xFFFD;
128
129 unsigned int firstByte = static_cast<unsigned char>(*s);
130
131 if (firstByte < 0x80)
132 return firstByte;
133
134 if (firstByte < 0xE0) {
135 if (e - s < 2)
136 return REPLACEMENT_CHARACTER;
137
138 unsigned int calculated =
139 ((firstByte & 0x1F) << 6) | (static_cast<unsigned int>(s[1]) & 0x3F);
140 s += 1;
141 // oversized encoded characters are invalid
142 return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated;
143 }
144
145 if (firstByte < 0xF0) {
146 if (e - s < 3)
147 return REPLACEMENT_CHARACTER;
148
149 unsigned int calculated = ((firstByte & 0x0F) << 12) |
150 ((static_cast<unsigned int>(s[1]) & 0x3F) << 6) |
151 (static_cast<unsigned int>(s[2]) & 0x3F);
152 s += 2;
153 // surrogates aren't valid codepoints itself
154 // shouldn't be UTF-8 encoded
155 if (calculated >= 0xD800 && calculated <= 0xDFFF)
156 return REPLACEMENT_CHARACTER;
157 // oversized encoded characters are invalid
158 return calculated < 0x800 ? REPLACEMENT_CHARACTER : calculated;
159 }
160
161 if (firstByte < 0xF8) {
162 if (e - s < 4)
163 return REPLACEMENT_CHARACTER;
164
165 unsigned int calculated = ((firstByte & 0x07) << 18) |
166 ((static_cast<unsigned int>(s[1]) & 0x3F) << 12) |
167 ((static_cast<unsigned int>(s[2]) & 0x3F) << 6) |
168 (static_cast<unsigned int>(s[3]) & 0x3F);
169 s += 3;
170 // oversized encoded characters are invalid
171 return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated;
172 }
173
174 return REPLACEMENT_CHARACTER;
175}
176
177static const char hex2[] = "000102030405060708090a0b0c0d0e0f"
178 "101112131415161718191a1b1c1d1e1f"
179 "202122232425262728292a2b2c2d2e2f"
180 "303132333435363738393a3b3c3d3e3f"
181 "404142434445464748494a4b4c4d4e4f"
182 "505152535455565758595a5b5c5d5e5f"
183 "606162636465666768696a6b6c6d6e6f"
184 "707172737475767778797a7b7c7d7e7f"
185 "808182838485868788898a8b8c8d8e8f"
186 "909192939495969798999a9b9c9d9e9f"
187 "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
188 "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
189 "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
190 "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
191 "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
192 "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
193
194static String toHex16Bit(unsigned int x) {
195 const unsigned int hi = (x >> 8) & 0xff;
196 const unsigned int lo = x & 0xff;
197 String result(4, ' ');
198 result[0] = hex2[2 * hi];
199 result[1] = hex2[2 * hi + 1];
200 result[2] = hex2[2 * lo];
201 result[3] = hex2[2 * lo + 1];
202 return result;
203}
204
205static void appendRaw(String& result, unsigned ch) {
206 result += static_cast<char>(ch);
207}
208
209static void appendHex(String& result, unsigned ch) {
210 result.append("\\u").append(toHex16Bit(ch));
211}
212
213static String valueToQuotedStringN(const char* value, size_t length,
214 bool emitUTF8 = false) {
215 if (value == nullptr)
216 return "";
217
218 if (!doesAnyCharRequireEscaping(value, length))
219 return String("\"") + value + "\"";
220 // We have to walk value and escape any special characters.
221 // Appending to String is not efficient, but this should be rare.
222 // (Note: forward slashes are *not* rare, but I am not escaping them.)
223 String::size_type maxsize = length * 2 + 3; // allescaped+quotes+NULL
224 String result;
225 result.reserve(maxsize); // to avoid lots of mallocs
226 result += "\"";
227 char const* end = value + length;
228 for (const char* c = value; c != end; ++c) {
229 switch (*c) {
230 case '\"':
231 result += "\\\"";
232 break;
233 case '\\':
234 result += "\\\\";
235 break;
236 case '\b':
237 result += "\\b";
238 break;
239 case '\f':
240 result += "\\f";
241 break;
242 case '\n':
243 result += "\\n";
244 break;
245 case '\r':
246 result += "\\r";
247 break;
248 case '\t':
249 result += "\\t";
250 break;
251 // case '/':
252 // Even though \/ is considered a legal escape in JSON, a bare
253 // slash is also legal, so I see no reason to escape it.
254 // (I hope I am not misunderstanding something.)
255 // blep notes: actually escaping \/ may be useful in javascript to avoid </
256 // sequence.
257 // Should add a flag to allow this compatibility mode and prevent this
258 // sequence from occurring.
259 default: {
260 if (emitUTF8) {
261 unsigned codepoint = static_cast<unsigned char>(*c);
262 if (codepoint < 0x20) {
263 appendHex(result, codepoint);
264 } else {
265 appendRaw(result, codepoint);
266 }
267 } else {
268 unsigned codepoint = utf8ToCodepoint(c, end); // modifies `c`
269 if (codepoint < 0x20) {
270 appendHex(result, codepoint);
271 } else if (codepoint < 0x80) {
272 appendRaw(result, codepoint);
273 } else if (codepoint < 0x10000) {
274 // Basic Multilingual Plane
275 appendHex(result, codepoint);
276 } else {
277 // Extended Unicode. Encode 20 bits as a surrogate pair.
278 codepoint -= 0x10000;
279 appendHex(result, 0xd800 + ((codepoint >> 10) & 0x3ff));
280 appendHex(result, 0xdc00 + (codepoint & 0x3ff));
281 }
282 }
283 } break;
284 }
285 }
286 result += "\"";
287 return result;
288}
289
290String valueToQuotedString(const char* value) {
291 return valueToQuotedStringN(value, strlen(value));
292}
293
294String valueToQuotedString(const char* value, size_t length) {
295 return valueToQuotedStringN(value, length);
296}
297
298// Class Writer
299// //////////////////////////////////////////////////////////////////
300Writer::~Writer() = default;
301
302// Class FastWriter
303// //////////////////////////////////////////////////////////////////
304
306
307 = default;
308
309void FastWriter::enableYAMLCompatibility() { yamlCompatibilityEnabled_ = true; }
310
311void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; }
312
313void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; }
314
316 document_.clear();
317 writeValue(root);
318 if (!omitEndingLineFeed_)
319 document_ += '\n';
320 return document_;
321}
322
323void FastWriter::writeValue(const Value& value) {
324 switch (value.type()) {
325 case nullValue:
326 if (!dropNullPlaceholders_)
327 document_ += "null";
328 break;
329 case intValue:
330 document_ += valueToString(value.asLargestInt());
331 break;
332 case uintValue:
333 document_ += valueToString(value.asLargestUInt());
334 break;
335 case realValue:
336 document_ += valueToString(value.asDouble());
337 break;
338 case stringValue: {
339 // Is NULL possible for value.string_? No.
340 char const* str;
341 char const* end;
342 bool ok = value.getString(&str, &end);
343 if (ok)
344 document_ += valueToQuotedStringN(str, static_cast<size_t>(end - str));
345 break;
346 }
347 case booleanValue:
348 document_ += valueToString(value.asBool());
349 break;
350 case arrayValue: {
351 document_ += '[';
352 ArrayIndex size = value.size();
353 for (ArrayIndex index = 0; index < size; ++index) {
354 if (index > 0)
355 document_ += ',';
356 writeValue(value[index]);
357 }
358 document_ += ']';
359 } break;
360 case objectValue: {
361 Value::Members members(value.getMemberNames());
362 document_ += '{';
363 for (auto it = members.begin(); it != members.end(); ++it) {
364 const String& name = *it;
365 if (it != members.begin())
366 document_ += ',';
367 document_ += valueToQuotedStringN(name.data(), name.length());
368 document_ += yamlCompatibilityEnabled_ ? ": " : ":";
369 writeValue(value[name]);
370 }
371 document_ += '}';
372 } break;
373 }
374}
375
376// Class StyledWriter
377// //////////////////////////////////////////////////////////////////
378
380
382 document_.clear();
383 addChildValues_ = false;
384 indentString_.clear();
385 writeCommentBeforeValue(root);
386 writeValue(root);
387 writeCommentAfterValueOnSameLine(root);
388 document_ += '\n';
389 return document_;
390}
391
392void StyledWriter::writeValue(const Value& value) {
393 switch (value.type()) {
394 case nullValue:
395 pushValue("null");
396 break;
397 case intValue:
398 pushValue(valueToString(value.asLargestInt()));
399 break;
400 case uintValue:
401 pushValue(valueToString(value.asLargestUInt()));
402 break;
403 case realValue:
404 pushValue(valueToString(value.asDouble()));
405 break;
406 case stringValue: {
407 // Is NULL possible for value.string_? No.
408 char const* str;
409 char const* end;
410 bool ok = value.getString(&str, &end);
411 if (ok)
412 pushValue(valueToQuotedStringN(str, static_cast<size_t>(end - str)));
413 else
414 pushValue("");
415 break;
416 }
417 case booleanValue:
418 pushValue(valueToString(value.asBool()));
419 break;
420 case arrayValue:
421 writeArrayValue(value);
422 break;
423 case objectValue: {
424 Value::Members members(value.getMemberNames());
425 if (members.empty())
426 pushValue("{}");
427 else {
428 writeWithIndent("{");
429 indent();
430 auto it = members.begin();
431 for (;;) {
432 const String& name = *it;
433 const Value& childValue = value[name];
434 writeCommentBeforeValue(childValue);
435 writeWithIndent(valueToQuotedString(name.c_str(), name.size()));
436 document_ += " : ";
437 writeValue(childValue);
438 if (++it == members.end()) {
439 writeCommentAfterValueOnSameLine(childValue);
440 break;
441 }
442 document_ += ',';
443 writeCommentAfterValueOnSameLine(childValue);
444 }
445 unindent();
446 writeWithIndent("}");
447 }
448 } break;
449 }
450}
451
452void StyledWriter::writeArrayValue(const Value& value) {
453 size_t size = value.size();
454 if (size == 0)
455 pushValue("[]");
456 else {
457 bool isArrayMultiLine = isMultilineArray(value);
458 if (isArrayMultiLine) {
459 writeWithIndent("[");
460 indent();
461 bool hasChildValue = !childValues_.empty();
462 ArrayIndex index = 0;
463 for (;;) {
464 const Value& childValue = value[index];
465 writeCommentBeforeValue(childValue);
466 if (hasChildValue)
467 writeWithIndent(childValues_[index]);
468 else {
469 writeIndent();
470 writeValue(childValue);
471 }
472 if (++index == size) {
473 writeCommentAfterValueOnSameLine(childValue);
474 break;
475 }
476 document_ += ',';
477 writeCommentAfterValueOnSameLine(childValue);
478 }
479 unindent();
480 writeWithIndent("]");
481 } else // output on a single line
482 {
483 assert(childValues_.size() == size);
484 document_ += "[ ";
485 for (size_t index = 0; index < size; ++index) {
486 if (index > 0)
487 document_ += ", ";
488 document_ += childValues_[index];
489 }
490 document_ += " ]";
491 }
492 }
493}
494
495bool StyledWriter::isMultilineArray(const Value& value) {
496 ArrayIndex const size = value.size();
497 bool isMultiLine = size * 3 >= rightMargin_;
498 childValues_.clear();
499 for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
500 const Value& childValue = value[index];
501 isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
502 !childValue.empty());
503 }
504 if (!isMultiLine) // check if line length > max line length
505 {
506 childValues_.reserve(size);
507 addChildValues_ = true;
508 ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
509 for (ArrayIndex index = 0; index < size; ++index) {
510 if (hasCommentForValue(value[index])) {
511 isMultiLine = true;
512 }
513 writeValue(value[index]);
514 lineLength += static_cast<ArrayIndex>(childValues_[index].length());
515 }
516 addChildValues_ = false;
517 isMultiLine = isMultiLine || lineLength >= rightMargin_;
518 }
519 return isMultiLine;
520}
521
522void StyledWriter::pushValue(const String& value) {
523 if (addChildValues_)
524 childValues_.push_back(value);
525 else
526 document_ += value;
527}
528
529void StyledWriter::writeIndent() {
530 if (!document_.empty()) {
531 char last = document_[document_.length() - 1];
532 if (last == ' ') // already indented
533 return;
534 if (last != '\n') // Comments may add new-line
535 document_ += '\n';
536 }
537 document_ += indentString_;
538}
539
540void StyledWriter::writeWithIndent(const String& value) {
541 writeIndent();
542 document_ += value;
543}
544
545void StyledWriter::indent() { indentString_ += String(indentSize_, ' '); }
546
547void StyledWriter::unindent() {
548 assert(indentString_.size() >= indentSize_);
549 indentString_.resize(indentString_.size() - indentSize_);
550}
551
552void StyledWriter::writeCommentBeforeValue(const Value& root) {
553 if (!root.hasComment(commentBefore))
554 return;
555
556 document_ += '\n';
557 writeIndent();
558 const String& comment = root.getComment(commentBefore);
559 String::const_iterator iter = comment.begin();
560 while (iter != comment.end()) {
561 document_ += *iter;
562 if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))
563 writeIndent();
564 ++iter;
565 }
566
567 // Comments are stripped of trailing newlines, so add one here
568 document_ += '\n';
569}
570
571void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) {
572 if (root.hasComment(commentAfterOnSameLine))
573 document_ += " " + root.getComment(commentAfterOnSameLine);
574
575 if (root.hasComment(commentAfter)) {
576 document_ += '\n';
577 document_ += root.getComment(commentAfter);
578 document_ += '\n';
579 }
580}
581
582bool StyledWriter::hasCommentForValue(const Value& value) {
583 return value.hasComment(commentBefore) ||
584 value.hasComment(commentAfterOnSameLine) ||
585 value.hasComment(commentAfter);
586}
587
588// Class StyledStreamWriter
589// //////////////////////////////////////////////////////////////////
590
592 : document_(nullptr), indentation_(std::move(indentation)),
593 addChildValues_(), indented_(false) {}
594
595void StyledStreamWriter::write(OStream& out, const Value& root) {
596 document_ = &out;
597 addChildValues_ = false;
598 indentString_.clear();
599 indented_ = true;
600 writeCommentBeforeValue(root);
601 if (!indented_)
602 writeIndent();
603 indented_ = true;
604 writeValue(root);
605 writeCommentAfterValueOnSameLine(root);
606 *document_ << "\n";
607 document_ = nullptr; // Forget the stream, for safety.
608}
609
610void StyledStreamWriter::writeValue(const Value& value) {
611 switch (value.type()) {
612 case nullValue:
613 pushValue("null");
614 break;
615 case intValue:
616 pushValue(valueToString(value.asLargestInt()));
617 break;
618 case uintValue:
619 pushValue(valueToString(value.asLargestUInt()));
620 break;
621 case realValue:
622 pushValue(valueToString(value.asDouble()));
623 break;
624 case stringValue: {
625 // Is NULL possible for value.string_? No.
626 char const* str;
627 char const* end;
628 bool ok = value.getString(&str, &end);
629 if (ok)
630 pushValue(valueToQuotedStringN(str, static_cast<size_t>(end - str)));
631 else
632 pushValue("");
633 break;
634 }
635 case booleanValue:
636 pushValue(valueToString(value.asBool()));
637 break;
638 case arrayValue:
639 writeArrayValue(value);
640 break;
641 case objectValue: {
642 Value::Members members(value.getMemberNames());
643 if (members.empty())
644 pushValue("{}");
645 else {
646 writeWithIndent("{");
647 indent();
648 auto it = members.begin();
649 for (;;) {
650 const String& name = *it;
651 const Value& childValue = value[name];
652 writeCommentBeforeValue(childValue);
653 writeWithIndent(valueToQuotedString(name.c_str(), name.size()));
654 *document_ << " : ";
655 writeValue(childValue);
656 if (++it == members.end()) {
657 writeCommentAfterValueOnSameLine(childValue);
658 break;
659 }
660 *document_ << ",";
661 writeCommentAfterValueOnSameLine(childValue);
662 }
663 unindent();
664 writeWithIndent("}");
665 }
666 } break;
667 }
668}
669
670void StyledStreamWriter::writeArrayValue(const Value& value) {
671 unsigned size = value.size();
672 if (size == 0)
673 pushValue("[]");
674 else {
675 bool isArrayMultiLine = isMultilineArray(value);
676 if (isArrayMultiLine) {
677 writeWithIndent("[");
678 indent();
679 bool hasChildValue = !childValues_.empty();
680 unsigned index = 0;
681 for (;;) {
682 const Value& childValue = value[index];
683 writeCommentBeforeValue(childValue);
684 if (hasChildValue)
685 writeWithIndent(childValues_[index]);
686 else {
687 if (!indented_)
688 writeIndent();
689 indented_ = true;
690 writeValue(childValue);
691 indented_ = false;
692 }
693 if (++index == size) {
694 writeCommentAfterValueOnSameLine(childValue);
695 break;
696 }
697 *document_ << ",";
698 writeCommentAfterValueOnSameLine(childValue);
699 }
700 unindent();
701 writeWithIndent("]");
702 } else // output on a single line
703 {
704 assert(childValues_.size() == size);
705 *document_ << "[ ";
706 for (unsigned index = 0; index < size; ++index) {
707 if (index > 0)
708 *document_ << ", ";
709 *document_ << childValues_[index];
710 }
711 *document_ << " ]";
712 }
713 }
714}
715
716bool StyledStreamWriter::isMultilineArray(const Value& value) {
717 ArrayIndex const size = value.size();
718 bool isMultiLine = size * 3 >= rightMargin_;
719 childValues_.clear();
720 for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
721 const Value& childValue = value[index];
722 isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
723 !childValue.empty());
724 }
725 if (!isMultiLine) // check if line length > max line length
726 {
727 childValues_.reserve(size);
728 addChildValues_ = true;
729 ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
730 for (ArrayIndex index = 0; index < size; ++index) {
731 if (hasCommentForValue(value[index])) {
732 isMultiLine = true;
733 }
734 writeValue(value[index]);
735 lineLength += static_cast<ArrayIndex>(childValues_[index].length());
736 }
737 addChildValues_ = false;
738 isMultiLine = isMultiLine || lineLength >= rightMargin_;
739 }
740 return isMultiLine;
741}
742
743void StyledStreamWriter::pushValue(const String& value) {
744 if (addChildValues_)
745 childValues_.push_back(value);
746 else
747 *document_ << value;
748}
749
750void StyledStreamWriter::writeIndent() {
751 // blep intended this to look at the so-far-written string
752 // to determine whether we are already indented, but
753 // with a stream we cannot do that. So we rely on some saved state.
754 // The caller checks indented_.
755 *document_ << '\n' << indentString_;
756}
757
758void StyledStreamWriter::writeWithIndent(const String& value) {
759 if (!indented_)
760 writeIndent();
761 *document_ << value;
762 indented_ = false;
763}
764
765void StyledStreamWriter::indent() { indentString_ += indentation_; }
766
767void StyledStreamWriter::unindent() {
768 assert(indentString_.size() >= indentation_.size());
769 indentString_.resize(indentString_.size() - indentation_.size());
770}
771
772void StyledStreamWriter::writeCommentBeforeValue(const Value& root) {
773 if (!root.hasComment(commentBefore))
774 return;
775
776 if (!indented_)
777 writeIndent();
778 const String& comment = root.getComment(commentBefore);
779 String::const_iterator iter = comment.begin();
780 while (iter != comment.end()) {
781 *document_ << *iter;
782 if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))
783 // writeIndent(); // would include newline
784 *document_ << indentString_;
785 ++iter;
786 }
787 indented_ = false;
788}
789
790void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) {
791 if (root.hasComment(commentAfterOnSameLine))
792 *document_ << ' ' << root.getComment(commentAfterOnSameLine);
793
794 if (root.hasComment(commentAfter)) {
795 writeIndent();
796 *document_ << root.getComment(commentAfter);
797 }
798 indented_ = false;
799}
800
801bool StyledStreamWriter::hasCommentForValue(const Value& value) {
802 return value.hasComment(commentBefore) ||
803 value.hasComment(commentAfterOnSameLine) ||
804 value.hasComment(commentAfter);
805}
806
808// BuiltStyledStreamWriter
809
811struct CommentStyle {
813 enum Enum {
814 None,
815 Most,
816 All
817 };
818};
819
820struct BuiltStyledStreamWriter : public StreamWriter {
821 BuiltStyledStreamWriter(String indentation, CommentStyle::Enum cs,
822 String colonSymbol, String nullSymbol,
823 String endingLineFeedSymbol, bool useSpecialFloats,
824 bool emitUTF8, unsigned int precision,
825 PrecisionType precisionType);
826 int write(Value const& root, OStream* sout) override;
827
828private:
829 void writeValue(Value const& value);
830 void writeArrayValue(Value const& value);
831 bool isMultilineArray(Value const& value);
832 void pushValue(String const& value);
833 void writeIndent();
834 void writeWithIndent(String const& value);
835 void indent();
836 void unindent();
837 void writeCommentBeforeValue(Value const& root);
838 void writeCommentAfterValueOnSameLine(Value const& root);
839 static bool hasCommentForValue(const Value& value);
840
841 using ChildValues = std::vector<String>;
842
843 ChildValues childValues_;
844 String indentString_;
845 unsigned int rightMargin_;
846 String indentation_;
847 CommentStyle::Enum cs_;
848 String colonSymbol_;
849 String nullSymbol_;
850 String endingLineFeedSymbol_;
851 bool addChildValues_ : 1;
852 bool indented_ : 1;
853 bool useSpecialFloats_ : 1;
854 bool emitUTF8_ : 1;
855 unsigned int precision_;
856 PrecisionType precisionType_;
857};
858BuiltStyledStreamWriter::BuiltStyledStreamWriter(
859 String indentation, CommentStyle::Enum cs, String colonSymbol,
860 String nullSymbol, String endingLineFeedSymbol, bool useSpecialFloats,
861 bool emitUTF8, unsigned int precision, PrecisionType precisionType)
862 : rightMargin_(74), indentation_(std::move(indentation)), cs_(cs),
863 colonSymbol_(std::move(colonSymbol)), nullSymbol_(std::move(nullSymbol)),
864 endingLineFeedSymbol_(std::move(endingLineFeedSymbol)),
865 addChildValues_(false), indented_(false),
866 useSpecialFloats_(useSpecialFloats), emitUTF8_(emitUTF8),
867 precision_(precision), precisionType_(precisionType) {}
868int BuiltStyledStreamWriter::write(Value const& root, OStream* sout) {
869 sout_ = sout;
870 addChildValues_ = false;
871 indented_ = true;
872 indentString_.clear();
873 writeCommentBeforeValue(root);
874 if (!indented_)
875 writeIndent();
876 indented_ = true;
877 writeValue(root);
878 writeCommentAfterValueOnSameLine(root);
879 *sout_ << endingLineFeedSymbol_;
880 sout_ = nullptr;
881 return 0;
882}
883void BuiltStyledStreamWriter::writeValue(Value const& value) {
884 switch (value.type()) {
885 case nullValue:
886 pushValue(nullSymbol_);
887 break;
888 case intValue:
889 pushValue(valueToString(value.asLargestInt()));
890 break;
891 case uintValue:
892 pushValue(valueToString(value.asLargestUInt()));
893 break;
894 case realValue:
895 pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_,
896 precisionType_));
897 break;
898 case stringValue: {
899 // Is NULL is possible for value.string_? No.
900 char const* str;
901 char const* end;
902 bool ok = value.getString(&str, &end);
903 if (ok)
904 pushValue(
905 valueToQuotedStringN(str, static_cast<size_t>(end - str), emitUTF8_));
906 else
907 pushValue("");
908 break;
909 }
910 case booleanValue:
911 pushValue(valueToString(value.asBool()));
912 break;
913 case arrayValue:
914 writeArrayValue(value);
915 break;
916 case objectValue: {
917 Value::Members members(value.getMemberNames());
918 if (members.empty())
919 pushValue("{}");
920 else {
921 writeWithIndent("{");
922 indent();
923 auto it = members.begin();
924 for (;;) {
925 String const& name = *it;
926 Value const& childValue = value[name];
927 writeCommentBeforeValue(childValue);
928 writeWithIndent(
929 valueToQuotedStringN(name.data(), name.length(), emitUTF8_));
930 *sout_ << colonSymbol_;
931 writeValue(childValue);
932 if (++it == members.end()) {
933 writeCommentAfterValueOnSameLine(childValue);
934 break;
935 }
936 *sout_ << ",";
937 writeCommentAfterValueOnSameLine(childValue);
938 }
939 unindent();
940 writeWithIndent("}");
941 }
942 } break;
943 }
944}
945
946void BuiltStyledStreamWriter::writeArrayValue(Value const& value) {
947 unsigned size = value.size();
948 if (size == 0)
949 pushValue("[]");
950 else {
951 bool isMultiLine = (cs_ == CommentStyle::All) || isMultilineArray(value);
952 if (isMultiLine) {
953 writeWithIndent("[");
954 indent();
955 bool hasChildValue = !childValues_.empty();
956 unsigned index = 0;
957 for (;;) {
958 Value const& childValue = value[index];
959 writeCommentBeforeValue(childValue);
960 if (hasChildValue)
961 writeWithIndent(childValues_[index]);
962 else {
963 if (!indented_)
964 writeIndent();
965 indented_ = true;
966 writeValue(childValue);
967 indented_ = false;
968 }
969 if (++index == size) {
970 writeCommentAfterValueOnSameLine(childValue);
971 break;
972 }
973 *sout_ << ",";
974 writeCommentAfterValueOnSameLine(childValue);
975 }
976 unindent();
977 writeWithIndent("]");
978 } else // output on a single line
979 {
980 assert(childValues_.size() == size);
981 *sout_ << "[";
982 if (!indentation_.empty())
983 *sout_ << " ";
984 for (unsigned index = 0; index < size; ++index) {
985 if (index > 0)
986 *sout_ << ((!indentation_.empty()) ? ", " : ",");
987 *sout_ << childValues_[index];
988 }
989 if (!indentation_.empty())
990 *sout_ << " ";
991 *sout_ << "]";
992 }
993 }
994}
995
996bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) {
997 ArrayIndex const size = value.size();
998 bool isMultiLine = size * 3 >= rightMargin_;
999 childValues_.clear();
1000 for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
1001 Value const& childValue = value[index];
1002 isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
1003 !childValue.empty());
1004 }
1005 if (!isMultiLine) // check if line length > max line length
1006 {
1007 childValues_.reserve(size);
1008 addChildValues_ = true;
1009 ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
1010 for (ArrayIndex index = 0; index < size; ++index) {
1011 if (hasCommentForValue(value[index])) {
1012 isMultiLine = true;
1013 }
1014 writeValue(value[index]);
1015 lineLength += static_cast<ArrayIndex>(childValues_[index].length());
1016 }
1017 addChildValues_ = false;
1018 isMultiLine = isMultiLine || lineLength >= rightMargin_;
1019 }
1020 return isMultiLine;
1021}
1022
1023void BuiltStyledStreamWriter::pushValue(String const& value) {
1024 if (addChildValues_)
1025 childValues_.push_back(value);
1026 else
1027 *sout_ << value;
1028}
1029
1030void BuiltStyledStreamWriter::writeIndent() {
1031 // blep intended this to look at the so-far-written string
1032 // to determine whether we are already indented, but
1033 // with a stream we cannot do that. So we rely on some saved state.
1034 // The caller checks indented_.
1035
1036 if (!indentation_.empty()) {
1037 // In this case, drop newlines too.
1038 *sout_ << '\n' << indentString_;
1039 }
1040}
1041
1042void BuiltStyledStreamWriter::writeWithIndent(String const& value) {
1043 if (!indented_)
1044 writeIndent();
1045 *sout_ << value;
1046 indented_ = false;
1047}
1048
1049void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; }
1050
1051void BuiltStyledStreamWriter::unindent() {
1052 assert(indentString_.size() >= indentation_.size());
1053 indentString_.resize(indentString_.size() - indentation_.size());
1054}
1055
1056void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) {
1057 if (cs_ == CommentStyle::None)
1058 return;
1059 if (!root.hasComment(commentBefore))
1060 return;
1061
1062 if (!indented_)
1063 writeIndent();
1064 const String& comment = root.getComment(commentBefore);
1065 String::const_iterator iter = comment.begin();
1066 while (iter != comment.end()) {
1067 *sout_ << *iter;
1068 if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))
1069 // writeIndent(); // would write extra newline
1070 *sout_ << indentString_;
1071 ++iter;
1072 }
1073 indented_ = false;
1074}
1075
1076void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(
1077 Value const& root) {
1078 if (cs_ == CommentStyle::None)
1079 return;
1080 if (root.hasComment(commentAfterOnSameLine))
1081 *sout_ << " " + root.getComment(commentAfterOnSameLine);
1082
1083 if (root.hasComment(commentAfter)) {
1084 writeIndent();
1085 *sout_ << root.getComment(commentAfter);
1086 }
1087}
1088
1089// static
1090bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) {
1091 return value.hasComment(commentBefore) ||
1092 value.hasComment(commentAfterOnSameLine) ||
1093 value.hasComment(commentAfter);
1094}
1095
1097// StreamWriter
1098
1100StreamWriter::~StreamWriter() = default;
1102StreamWriterBuilder::StreamWriterBuilder() { setDefaults(&settings_); }
1103StreamWriterBuilder::~StreamWriterBuilder() = default;
1105 const String indentation = settings_["indentation"].asString();
1106 const String cs_str = settings_["commentStyle"].asString();
1107 const String pt_str = settings_["precisionType"].asString();
1108 const bool eyc = settings_["enableYAMLCompatibility"].asBool();
1109 const bool dnp = settings_["dropNullPlaceholders"].asBool();
1110 const bool usf = settings_["useSpecialFloats"].asBool();
1111 const bool emitUTF8 = settings_["emitUTF8"].asBool();
1112 unsigned int pre = settings_["precision"].asUInt();
1113 CommentStyle::Enum cs = CommentStyle::All;
1114 if (cs_str == "All") {
1115 cs = CommentStyle::All;
1116 } else if (cs_str == "None") {
1117 cs = CommentStyle::None;
1118 } else {
1119 throwRuntimeError("commentStyle must be 'All' or 'None'");
1120 }
1121 PrecisionType precisionType(significantDigits);
1122 if (pt_str == "significant") {
1123 precisionType = PrecisionType::significantDigits;
1124 } else if (pt_str == "decimal") {
1125 precisionType = PrecisionType::decimalPlaces;
1126 } else {
1127 throwRuntimeError("precisionType must be 'significant' or 'decimal'");
1128 }
1129 String colonSymbol = " : ";
1130 if (eyc) {
1131 colonSymbol = ": ";
1132 } else if (indentation.empty()) {
1133 colonSymbol = ":";
1134 }
1135 String nullSymbol = "null";
1136 if (dnp) {
1137 nullSymbol.clear();
1138 }
1139 if (pre > 17)
1140 pre = 17;
1141 String endingLineFeedSymbol;
1142 return new BuiltStyledStreamWriter(indentation, cs, colonSymbol, nullSymbol,
1143 endingLineFeedSymbol, usf, emitUTF8, pre,
1144 precisionType);
1145}
1146
1148 static const auto& valid_keys = *new std::set<String>{
1149 "indentation",
1150 "commentStyle",
1151 "enableYAMLCompatibility",
1152 "dropNullPlaceholders",
1153 "useSpecialFloats",
1154 "emitUTF8",
1155 "precision",
1156 "precisionType",
1157 };
1158 for (auto si = settings_.begin(); si != settings_.end(); ++si) {
1159 auto key = si.name();
1160 if (valid_keys.count(key))
1161 continue;
1162 if (invalid)
1163 (*invalid)[key] = *si;
1164 else
1165 return false;
1166 }
1167 return invalid ? invalid->empty() : true;
1168}
1169
1171 return settings_[key];
1172}
1173// static
1176 (*settings)["commentStyle"] = "All";
1177 (*settings)["indentation"] = "\t";
1178 (*settings)["enableYAMLCompatibility"] = false;
1179 (*settings)["dropNullPlaceholders"] = false;
1180 (*settings)["useSpecialFloats"] = false;
1181 (*settings)["emitUTF8"] = false;
1182 (*settings)["precision"] = 17;
1183 (*settings)["precisionType"] = "significant";
1185}
1186
1187String writeString(StreamWriter::Factory const& factory, Value const& root) {
1188 OStringStream sout;
1189 StreamWriterPtr const writer(factory.newStreamWriter());
1190 writer->write(root, &sout);
1191 return std::move(sout).str();
1192}
1193
1194OStream& operator<<(OStream& sout, Value const& root) {
1195 StreamWriterBuilder builder;
1196 StreamWriterPtr const writer(builder.newStreamWriter());
1197 writer->write(root, &sout);
1198 return sout;
1199}
1200
1201} // namespace Json
String write(const Value &root) override
void dropNullPlaceholders()
Drop the "null" string from the writer's output for nullValues.
void enableYAMLCompatibility()
A simple abstract factory.
Definition writer.h:59
virtual StreamWriter * newStreamWriter() const =0
Allocate a CharReader via operator new().
Build a StreamWriter implementation.
Definition writer.h:90
StreamWriter * newStreamWriter() const override
Configuration of this builder.
bool validate(Json::Value *invalid) const
static void setDefaults(Json::Value *settings)
Called by ctor, but you can use this to reset settings_.
Value & operator[](const String &key)
A simple way to update a specific setting.
virtual ~StreamWriter()
virtual int write(Value const &root, OStream *sout)=0
Write Value into document as configured in sub-class.
OStream * sout_
Definition writer.h:44
void write(OStream &out, const Value &root)
Serialize a Value in JSON format.
StyledStreamWriter(String indentation="\t")
String write(const Value &root) override
Serialize a Value in JSON format.
Represents a JSON value.
Definition value.h:207
bool empty() const
Return true if empty array, empty object, or null; otherwise, false.
static constexpr LargestInt maxLargestInt
Maximum signed integer value that can be stored in a Json::Value.
Definition value.h:241
ArrayIndex size() const
Number of values in array or object.
bool getString(char const **begin, char const **end) const
Get raw char* of string-value.
std::vector< String > Members
Definition value.h:212
Members getMemberNames() const
Return a list of the member names.
ValueType type() const
LargestInt asLargestInt() const
bool asBool() const
LargestUInt asLargestUInt() const
static constexpr LargestInt minLargestInt
Minimum signed integer value that can be stored in a Json::Value.
Definition value.h:238
double asDouble() const
virtual ~Writer()
#define jsoncpp_snprintf
Definition config.h:63
JSON (JavaScript Object Notation).
Definition allocator.h:16
std::basic_ostringstream< String::value_type, String::traits_type, String::allocator_type > OStringStream
Definition config.h:136
static const char hex2[]
static unsigned int utf8ToCodepoint(const char *&s, const char *e)
std::unique_ptr< StreamWriter > StreamWriterPtr
unsigned int ArrayIndex
Definition forwards.h:32
static void appendRaw(String &result, unsigned ch)
Int64 LargestInt
Definition config.h:123
String writeString(StreamWriter::Factory const &factory, Value const &root)
Write into stringstream, then return string, for convenience.
Iter fixNumericLocale(Iter begin, Iter end)
Change ',' to '.
Definition json_tool.h:94
@ commentAfterOnSameLine
a comment just after a value on the same line
Definition value.h:134
@ commentBefore
a comment placed on the line before a value
Definition value.h:133
@ commentAfter
a comment on the line after a value (only make sense for
Definition value.h:135
static String toHex16Bit(unsigned int x)
static bool doesAnyCharRequireEscaping(char const *s, size_t n)
String valueToQuotedString(const char *value)
static String valueToQuotedStringN(const char *value, size_t length, bool emitUTF8=false)
String valueToString(Int value)
unsigned int UInt
Definition config.h:109
@ booleanValue
bool value
Definition value.h:127
@ nullValue
'null' value
Definition value.h:122
@ stringValue
UTF-8 string value.
Definition value.h:126
@ realValue
double value
Definition value.h:125
@ arrayValue
array value (ordered list)
Definition value.h:128
@ intValue
signed integer value
Definition value.h:123
@ objectValue
object value (collection of name/value pairs).
Definition value.h:129
@ uintValue
unsigned integer value
Definition value.h:124
OStream & operator<<(OStream &, const Value &root)
Output using the StyledStreamWriter.
int Int
Definition config.h:108
static void appendHex(String &result, unsigned ch)
Iter fixZerosInTheEnd(Iter begin, Iter end, unsigned int precision)
Return iterator that would be the new end of the range [begin,end), if we were to delete zeros in the...
Definition json_tool.h:120
std::basic_string< char, std::char_traits< char >, Allocator< char > > String
Definition config.h:132
char[uintToStringBufferSize] UIntToStringBuffer
Definition json_tool.h:74
static void uintToString(LargestUInt value, char *&current)
Converts an unsigned integer to string.
Definition json_tool.h:81
UInt64 LargestUInt
Definition config.h:124
std::ostream OStream
Definition config.h:140
PrecisionType
Type of precision for formatting of real values.
Definition value.h:142
@ decimalPlaces
we set max number of digits after "." in string
Definition value.h:144
@ significantDigits
we set max number of significant digits in string
Definition value.h:143