• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

open-source-parsers / jsoncpp / 13935989780

19 Mar 2025 12:16AM UTC coverage: 93.285% (-2.0%) from 95.292%
13935989780

push

github

web-flow
Merge 0383d14b7 into ca98c9845

5251 of 5629 relevant lines covered (93.28%)

319.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.48
/src/lib_json/json_writer.cpp
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

27
namespace Json {
28

29
using StreamWriterPtr = std::unique_ptr<StreamWriter>;
30

31
String valueToString(LargestInt value) {
23✔
32
  UIntToStringBuffer buffer;
33
  char* current = buffer + sizeof(buffer);
34
  if (value == Value::minLargestInt) {
23✔
35
    uintToString(LargestUInt(Value::maxLargestInt) + 1, current);
36
    *--current = '-';
1✔
37
  } else if (value < 0) {
22✔
38
    uintToString(LargestUInt(-value), current);
4✔
39
    *--current = '-';
4✔
40
  } else {
41
    uintToString(LargestUInt(value), current);
18✔
42
  }
43
  assert(current >= buffer);
23✔
44
  return current;
23✔
45
}
46

47
String valueToString(LargestUInt value) {
152✔
48
  UIntToStringBuffer buffer;
49
  char* current = buffer + sizeof(buffer);
50
  uintToString(value, current);
51
  assert(current >= buffer);
152✔
52
  return current;
152✔
53
}
54

55
#if defined(JSON_HAS_INT64)
56

57
String valueToString(Int value) { return valueToString(LargestInt(value)); }
×
58

59
String valueToString(UInt value) { return valueToString(LargestUInt(value)); }
×
60

61
#endif // # if defined(JSON_HAS_INT64)
62

63
namespace {
64
String valueToString(double value, bool useSpecialFloats,
42✔
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)) {
42✔
70
    if (std::isnan(value))
3✔
71
      return useSpecialFloats ? "NaN" : "null";
1✔
72
    if (value < 0)
2✔
73
      return useSpecialFloats ? "-Infinity" : "-1e+9999";
1✔
74
    return useSpecialFloats ? "Infinity" : "1e+9999";
1✔
75
  }
76

77
  String buffer(size_t(36), '\0');
78
  while (true) {
79
    int len = jsoncpp_snprintf(
39✔
80
        &*buffer.begin(), buffer.size(),
81
        (precisionType == PrecisionType::significantDigits) ? "%.*g" : "%.*f",
82
        precision, value);
83
    assert(len >= 0);
39✔
84
    auto wouldPrint = static_cast<size_t>(len);
39✔
85
    if (wouldPrint >= buffer.size()) {
39✔
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());
39✔
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) {
39✔
98
    buffer += ".0";
99
  }
100

101
  // strip the zero padding from the right
102
  if (precisionType == PrecisionType::decimalPlaces) {
39✔
103
    buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end(), precision),
7✔
104
                 buffer.end());
105
  }
106

107
  return buffer;
39✔
108
}
109
} // namespace
110

111
String valueToString(double value, unsigned int precision,
24✔
112
                     PrecisionType precisionType) {
113
  return valueToString(value, false, precision, precisionType);
24✔
114
}
115

116
String valueToString(bool value) { return value ? "true" : "false"; }
12✔
117

118
static bool doesAnyCharRequireEscaping(char const* s, size_t n) {
887✔
119
  assert(s || !n);
887✔
120

121
  return std::any_of(s, s + n, [](unsigned char c) {
887✔
122
    return c == '\\' || c == '"' || c < 0x20 || c > 0x7F;
1,041✔
123
  });
887✔
124
}
125

126
static unsigned int utf8ToCodepoint(const char*& s, const char* e) {
48✔
127
  const unsigned int REPLACEMENT_CHARACTER = 0xFFFD;
128

129
  unsigned int firstByte = static_cast<unsigned char>(*s);
48✔
130

131
  if (firstByte < 0x80)
48✔
132
    return firstByte;
133

134
  if (firstByte < 0xE0) {
13✔
135
    if (e - s < 2)
3✔
136
      return REPLACEMENT_CHARACTER;
137

138
    unsigned int calculated =
139
        ((firstByte & 0x1F) << 6) | (static_cast<unsigned int>(s[1]) & 0x3F);
3✔
140
    s += 1;
3✔
141
    // oversized encoded characters are invalid
142
    return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated;
6✔
143
  }
144

145
  if (firstByte < 0xF0) {
10✔
146
    if (e - s < 3)
5✔
147
      return REPLACEMENT_CHARACTER;
148

149
    unsigned int calculated = ((firstByte & 0x0F) << 12) |
5✔
150
                              ((static_cast<unsigned int>(s[1]) & 0x3F) << 6) |
5✔
151
                              (static_cast<unsigned int>(s[2]) & 0x3F);
5✔
152
    s += 2;
5✔
153
    // surrogates aren't valid codepoints itself
154
    // shouldn't be UTF-8 encoded
155
    if (calculated >= 0xD800 && calculated <= 0xDFFF)
5✔
156
      return REPLACEMENT_CHARACTER;
157
    // oversized encoded characters are invalid
158
    return calculated < 0x800 ? REPLACEMENT_CHARACTER : calculated;
10✔
159
  }
160

161
  if (firstByte < 0xF8) {
5✔
162
    if (e - s < 4)
5✔
163
      return REPLACEMENT_CHARACTER;
164

165
    unsigned int calculated = ((firstByte & 0x07) << 18) |
5✔
166
                              ((static_cast<unsigned int>(s[1]) & 0x3F) << 12) |
5✔
167
                              ((static_cast<unsigned int>(s[2]) & 0x3F) << 6) |
5✔
168
                              (static_cast<unsigned int>(s[3]) & 0x3F);
5✔
169
    s += 3;
5✔
170
    // oversized encoded characters are invalid
171
    return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated;
10✔
172
  }
173

174
  return REPLACEMENT_CHARACTER;
175
}
176

177
static 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

194
static String toHex16Bit(unsigned int x) {
74✔
195
  const unsigned int hi = (x >> 8) & 0xff;
74✔
196
  const unsigned int lo = x & 0xff;
74✔
197
  String result(4, ' ');
198
  result[0] = hex2[2 * hi];
74✔
199
  result[1] = hex2[2 * hi + 1];
74✔
200
  result[2] = hex2[2 * lo];
74✔
201
  result[3] = hex2[2 * lo + 1];
74✔
202
  return result;
74✔
203
}
204

205
static void appendRaw(String& result, unsigned ch) {
206
  result += static_cast<char>(ch);
207
}
148✔
208

209
static void appendHex(String& result, unsigned ch) {
74✔
210
  result.append("\\u").append(toHex16Bit(ch));
74✔
211
}
74✔
212

213
static String valueToQuotedStringN(const char* value, size_t length,
887✔
214
                                   bool emitUTF8 = false) {
215
  if (value == nullptr)
887✔
216
    return "";
×
217

218
  if (!doesAnyCharRequireEscaping(value, length))
887✔
219
    return String("\"") + value + "\"";
1,352✔
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
211✔
224
  String result;
225
  result.reserve(maxsize); // to avoid lots of mallocs
211✔
226
  result += "\"";
227
  char const* end = value + length;
211✔
228
  for (const char* c = value; c != end; ++c) {
455✔
229
    switch (*c) {
244✔
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: {
217✔
260
      if (emitUTF8) {
217✔
261
        unsigned codepoint = static_cast<unsigned char>(*c);
169✔
262
        if (codepoint < 0x20) {
169✔
263
          appendHex(result, codepoint);
27✔
264
        } else {
265
          appendRaw(result, codepoint);
266
        }
267
      } else {
268
        unsigned codepoint = utf8ToCodepoint(c, end); // modifies `c`
48✔
269
        if (codepoint < 0x20) {
48✔
270
          appendHex(result, codepoint);
29✔
271
        } else if (codepoint < 0x80) {
19✔
272
          appendRaw(result, codepoint);
273
        } else if (codepoint < 0x10000) {
13✔
274
          // Basic Multilingual Plane
275
          appendHex(result, codepoint);
8✔
276
        } else {
277
          // Extended Unicode. Encode 20 bits as a surrogate pair.
278
          codepoint -= 0x10000;
5✔
279
          appendHex(result, 0xd800 + ((codepoint >> 10) & 0x3ff));
5✔
280
          appendHex(result, 0xdc00 + (codepoint & 0x3ff));
5✔
281
        }
282
      }
283
    } break;
284
    }
285
  }
286
  result += "\"";
287
  return result;
211✔
288
}
289

290
String valueToQuotedString(const char* value) {
×
291
  return valueToQuotedStringN(value, strlen(value));
×
292
}
293

294
String valueToQuotedString(const char* value, size_t length) {
24✔
295
  return valueToQuotedStringN(value, length);
24✔
296
}
297

298
// Class Writer
299
// //////////////////////////////////////////////////////////////////
300
Writer::~Writer() = default;
26✔
301

302
// Class FastWriter
303
// //////////////////////////////////////////////////////////////////
304

305
FastWriter::FastWriter()
8✔
306

307
    = default;
308

309
void FastWriter::enableYAMLCompatibility() { yamlCompatibilityEnabled_ = true; }
1✔
310

311
void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; }
1✔
312

313
void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; }
1✔
314

315
String FastWriter::write(const Value& root) {
13✔
316
  document_.clear();
317
  writeValue(root);
13✔
318
  if (!omitEndingLineFeed_)
13✔
319
    document_ += '\n';
12✔
320
  return document_;
13✔
321
}
322

323
void FastWriter::writeValue(const Value& value) {
55✔
324
  switch (value.type()) {
55✔
325
  case nullValue:
7✔
326
    if (!dropNullPlaceholders_)
7✔
327
      document_ += "null";
6✔
328
    break;
329
  case intValue:
7✔
330
    document_ += valueToString(value.asLargestInt());
7✔
331
    break;
7✔
332
  case uintValue:
1✔
333
    document_ += valueToString(value.asLargestUInt());
1✔
334
    break;
1✔
335
  case realValue:
2✔
336
    document_ += valueToString(value.asDouble());
2✔
337
    break;
2✔
338
  case stringValue: {
17✔
339
    // Is NULL possible for value.string_? No.
340
    char const* str;
341
    char const* end;
342
    bool ok = value.getString(&str, &end);
17✔
343
    if (ok)
17✔
344
      document_ += valueToQuotedStringN(str, static_cast<size_t>(end - str));
34✔
345
    break;
346
  }
347
  case booleanValue:
2✔
348
    document_ += valueToString(value.asBool());
2✔
349
    break;
2✔
350
  case arrayValue: {
6✔
351
    document_ += '[';
6✔
352
    ArrayIndex size = value.size();
6✔
353
    for (ArrayIndex index = 0; index < size; ++index) {
24✔
354
      if (index > 0)
18✔
355
        document_ += ',';
356
      writeValue(value[index]);
18✔
357
    }
358
    document_ += ']';
359
  } break;
360
  case objectValue: {
13✔
361
    Value::Members members(value.getMemberNames());
13✔
362
    document_ += '{';
13✔
363
    for (auto it = members.begin(); it != members.end(); ++it) {
37✔
364
      const String& name = *it;
365
      if (it != members.begin())
24✔
366
        document_ += ',';
367
      document_ += valueToQuotedStringN(name.data(), name.length());
24✔
368
      document_ += yamlCompatibilityEnabled_ ? ": " : ":";
24✔
369
      writeValue(value[name]);
24✔
370
    }
371
    document_ += '}';
372
  } break;
13✔
373
  }
374
}
55✔
375

376
// Class StyledWriter
377
// //////////////////////////////////////////////////////////////////
378

379
StyledWriter::StyledWriter() = default;
5✔
380

381
String StyledWriter::write(const Value& root) {
8✔
382
  document_.clear();
383
  addChildValues_ = false;
8✔
384
  indentString_.clear();
385
  writeCommentBeforeValue(root);
8✔
386
  writeValue(root);
8✔
387
  writeCommentAfterValueOnSameLine(root);
8✔
388
  document_ += '\n';
8✔
389
  return document_;
8✔
390
}
391

392
void StyledWriter::writeValue(const Value& value) {
53✔
393
  switch (value.type()) {
53✔
394
  case nullValue:
395
    pushValue("null");
1✔
396
    break;
1✔
397
  case intValue:
1✔
398
    pushValue(valueToString(value.asLargestInt()));
1✔
399
    break;
1✔
400
  case uintValue:
32✔
401
    pushValue(valueToString(value.asLargestUInt()));
32✔
402
    break;
32✔
403
  case realValue:
2✔
404
    pushValue(valueToString(value.asDouble()));
2✔
405
    break;
2✔
406
  case stringValue: {
6✔
407
    // Is NULL possible for value.string_? No.
408
    char const* str;
409
    char const* end;
410
    bool ok = value.getString(&str, &end);
6✔
411
    if (ok)
6✔
412
      pushValue(valueToQuotedStringN(str, static_cast<size_t>(end - str)));
12✔
413
    else
414
      pushValue("");
×
415
    break;
416
  }
417
  case booleanValue:
2✔
418
    pushValue(valueToString(value.asBool()));
2✔
419
    break;
2✔
420
  case arrayValue:
4✔
421
    writeArrayValue(value);
4✔
422
    break;
4✔
423
  case objectValue: {
5✔
424
    Value::Members members(value.getMemberNames());
5✔
425
    if (members.empty())
5✔
426
      pushValue("{}");
2✔
427
    else {
428
      writeWithIndent("{");
4✔
429
      indent();
4✔
430
      auto it = members.begin();
431
      for (;;) {
432
        const String& name = *it;
433
        const Value& childValue = value[name];
12✔
434
        writeCommentBeforeValue(childValue);
12✔
435
        writeWithIndent(valueToQuotedString(name.c_str(), name.size()));
12✔
436
        document_ += " : ";
12✔
437
        writeValue(childValue);
12✔
438
        if (++it == members.end()) {
12✔
439
          writeCommentAfterValueOnSameLine(childValue);
4✔
440
          break;
441
        }
442
        document_ += ',';
443
        writeCommentAfterValueOnSameLine(childValue);
8✔
444
      }
445
      unindent();
4✔
446
      writeWithIndent("}");
8✔
447
    }
448
  } break;
5✔
449
  }
450
}
53✔
451

452
void StyledWriter::writeArrayValue(const Value& value) {
4✔
453
  size_t size = value.size();
4✔
454
  if (size == 0)
4✔
455
    pushValue("[]");
2✔
456
  else {
457
    bool isArrayMultiLine = isMultilineArray(value);
3✔
458
    if (isArrayMultiLine) {
3✔
459
      writeWithIndent("[");
1✔
460
      indent();
1✔
461
      bool hasChildValue = !childValues_.empty();
462
      ArrayIndex index = 0;
463
      for (;;) {
464
        const Value& childValue = value[index];
21✔
465
        writeCommentBeforeValue(childValue);
21✔
466
        if (hasChildValue)
21✔
467
          writeWithIndent(childValues_[index]);
21✔
468
        else {
469
          writeIndent();
×
470
          writeValue(childValue);
×
471
        }
472
        if (++index == size) {
21✔
473
          writeCommentAfterValueOnSameLine(childValue);
1✔
474
          break;
475
        }
476
        document_ += ',';
20✔
477
        writeCommentAfterValueOnSameLine(childValue);
20✔
478
      }
20✔
479
      unindent();
1✔
480
      writeWithIndent("]");
2✔
481
    } else // output on a single line
482
    {
483
      assert(childValues_.size() == size);
2✔
484
      document_ += "[ ";
2✔
485
      for (size_t index = 0; index < size; ++index) {
14✔
486
        if (index > 0)
12✔
487
          document_ += ", ";
488
        document_ += childValues_[index];
12✔
489
      }
490
      document_ += " ]";
491
    }
492
  }
493
}
4✔
494

495
bool StyledWriter::isMultilineArray(const Value& value) {
3✔
496
  ArrayIndex const size = value.size();
3✔
497
  bool isMultiLine = size * 3 >= rightMargin_;
3✔
498
  childValues_.clear();
499
  for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
36✔
500
    const Value& childValue = value[index];
33✔
501
    isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
33✔
502
                   !childValue.empty());
×
503
  }
504
  if (!isMultiLine) // check if line length > max line length
3✔
505
  {
506
    childValues_.reserve(size);
3✔
507
    addChildValues_ = true;
3✔
508
    ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
3✔
509
    for (ArrayIndex index = 0; index < size; ++index) {
36✔
510
      if (hasCommentForValue(value[index])) {
33✔
511
        isMultiLine = true;
512
      }
513
      writeValue(value[index]);
33✔
514
      lineLength += static_cast<ArrayIndex>(childValues_[index].length());
33✔
515
    }
516
    addChildValues_ = false;
3✔
517
    isMultiLine = isMultiLine || lineLength >= rightMargin_;
3✔
518
  }
519
  return isMultiLine;
3✔
520
}
521

522
void StyledWriter::pushValue(const String& value) {
46✔
523
  if (addChildValues_)
46✔
524
    childValues_.push_back(value);
33✔
525
  else
526
    document_ += value;
13✔
527
}
46✔
528

529
void StyledWriter::writeIndent() {
44✔
530
  if (!document_.empty()) {
44✔
531
    char last = document_[document_.length() - 1];
40✔
532
    if (last == ' ') // already indented
40✔
533
      return;
534
    if (last != '\n') // Comments may add new-line
39✔
535
      document_ += '\n';
38✔
536
  }
537
  document_ += indentString_;
43✔
538
}
539

540
void StyledWriter::writeWithIndent(const String& value) {
43✔
541
  writeIndent();
43✔
542
  document_ += value;
43✔
543
}
43✔
544

545
void StyledWriter::indent() { indentString_ += String(indentSize_, ' '); }
10✔
546

547
void StyledWriter::unindent() {
5✔
548
  assert(indentString_.size() >= indentSize_);
5✔
549
  indentString_.resize(indentString_.size() - indentSize_);
5✔
550
}
5✔
551

552
void StyledWriter::writeCommentBeforeValue(const Value& root) {
41✔
553
  if (!root.hasComment(commentBefore))
41✔
554
    return;
40✔
555

556
  document_ += '\n';
1✔
557
  writeIndent();
1✔
558
  const String& comment = root.getComment(commentBefore);
1✔
559
  String::const_iterator iter = comment.begin();
560
  while (iter != comment.end()) {
21✔
561
    document_ += *iter;
20✔
562
    if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))
20✔
563
      writeIndent();
×
564
    ++iter;
565
  }
566

567
  // Comments are stripped of trailing newlines, so add one here
568
  document_ += '\n';
569
}
570

571
void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) {
41✔
572
  if (root.hasComment(commentAfterOnSameLine))
41✔
573
    document_ += " " + root.getComment(commentAfterOnSameLine);
3✔
574

575
  if (root.hasComment(commentAfter)) {
41✔
576
    document_ += '\n';
1✔
577
    document_ += root.getComment(commentAfter);
2✔
578
    document_ += '\n';
579
  }
580
}
41✔
581

582
bool StyledWriter::hasCommentForValue(const Value& value) {
33✔
583
  return value.hasComment(commentBefore) ||
66✔
584
         value.hasComment(commentAfterOnSameLine) ||
66✔
585
         value.hasComment(commentAfter);
33✔
586
}
587

588
// Class StyledStreamWriter
589
// //////////////////////////////////////////////////////////////////
590

591
StyledStreamWriter::StyledStreamWriter(String indentation)
6✔
592
    : document_(nullptr), indentation_(std::move(indentation)),
6✔
593
      addChildValues_(), indented_(false) {}
6✔
594

595
void StyledStreamWriter::write(OStream& out, const Value& root) {
8✔
596
  document_ = &out;
8✔
597
  addChildValues_ = false;
8✔
598
  indentString_.clear();
599
  indented_ = true;
8✔
600
  writeCommentBeforeValue(root);
8✔
601
  if (!indented_)
8✔
602
    writeIndent();
1✔
603
  indented_ = true;
8✔
604
  writeValue(root);
8✔
605
  writeCommentAfterValueOnSameLine(root);
8✔
606
  *document_ << "\n";
8✔
607
  document_ = nullptr; // Forget the stream, for safety.
8✔
608
}
8✔
609

610
void StyledStreamWriter::writeValue(const Value& value) {
53✔
611
  switch (value.type()) {
53✔
612
  case nullValue:
613
    pushValue("null");
1✔
614
    break;
1✔
615
  case intValue:
1✔
616
    pushValue(valueToString(value.asLargestInt()));
1✔
617
    break;
1✔
618
  case uintValue:
32✔
619
    pushValue(valueToString(value.asLargestUInt()));
32✔
620
    break;
32✔
621
  case realValue:
2✔
622
    pushValue(valueToString(value.asDouble()));
2✔
623
    break;
2✔
624
  case stringValue: {
6✔
625
    // Is NULL possible for value.string_? No.
626
    char const* str;
627
    char const* end;
628
    bool ok = value.getString(&str, &end);
6✔
629
    if (ok)
6✔
630
      pushValue(valueToQuotedStringN(str, static_cast<size_t>(end - str)));
12✔
631
    else
632
      pushValue("");
×
633
    break;
634
  }
635
  case booleanValue:
2✔
636
    pushValue(valueToString(value.asBool()));
2✔
637
    break;
2✔
638
  case arrayValue:
4✔
639
    writeArrayValue(value);
4✔
640
    break;
4✔
641
  case objectValue: {
5✔
642
    Value::Members members(value.getMemberNames());
5✔
643
    if (members.empty())
5✔
644
      pushValue("{}");
2✔
645
    else {
646
      writeWithIndent("{");
4✔
647
      indent();
4✔
648
      auto it = members.begin();
649
      for (;;) {
650
        const String& name = *it;
651
        const Value& childValue = value[name];
12✔
652
        writeCommentBeforeValue(childValue);
12✔
653
        writeWithIndent(valueToQuotedString(name.c_str(), name.size()));
12✔
654
        *document_ << " : ";
12✔
655
        writeValue(childValue);
12✔
656
        if (++it == members.end()) {
12✔
657
          writeCommentAfterValueOnSameLine(childValue);
4✔
658
          break;
659
        }
660
        *document_ << ",";
8✔
661
        writeCommentAfterValueOnSameLine(childValue);
8✔
662
      }
663
      unindent();
4✔
664
      writeWithIndent("}");
8✔
665
    }
666
  } break;
5✔
667
  }
668
}
53✔
669

670
void StyledStreamWriter::writeArrayValue(const Value& value) {
4✔
671
  unsigned size = value.size();
4✔
672
  if (size == 0)
4✔
673
    pushValue("[]");
2✔
674
  else {
675
    bool isArrayMultiLine = isMultilineArray(value);
3✔
676
    if (isArrayMultiLine) {
3✔
677
      writeWithIndent("[");
1✔
678
      indent();
1✔
679
      bool hasChildValue = !childValues_.empty();
680
      unsigned index = 0;
681
      for (;;) {
682
        const Value& childValue = value[index];
21✔
683
        writeCommentBeforeValue(childValue);
21✔
684
        if (hasChildValue)
21✔
685
          writeWithIndent(childValues_[index]);
21✔
686
        else {
687
          if (!indented_)
×
688
            writeIndent();
×
689
          indented_ = true;
×
690
          writeValue(childValue);
×
691
          indented_ = false;
×
692
        }
693
        if (++index == size) {
21✔
694
          writeCommentAfterValueOnSameLine(childValue);
1✔
695
          break;
696
        }
697
        *document_ << ",";
20✔
698
        writeCommentAfterValueOnSameLine(childValue);
20✔
699
      }
20✔
700
      unindent();
1✔
701
      writeWithIndent("]");
2✔
702
    } else // output on a single line
703
    {
704
      assert(childValues_.size() == size);
2✔
705
      *document_ << "[ ";
2✔
706
      for (unsigned index = 0; index < size; ++index) {
14✔
707
        if (index > 0)
12✔
708
          *document_ << ", ";
10✔
709
        *document_ << childValues_[index];
12✔
710
      }
711
      *document_ << " ]";
2✔
712
    }
713
  }
714
}
4✔
715

716
bool StyledStreamWriter::isMultilineArray(const Value& value) {
3✔
717
  ArrayIndex const size = value.size();
3✔
718
  bool isMultiLine = size * 3 >= rightMargin_;
3✔
719
  childValues_.clear();
720
  for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
36✔
721
    const Value& childValue = value[index];
33✔
722
    isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
33✔
723
                   !childValue.empty());
×
724
  }
725
  if (!isMultiLine) // check if line length > max line length
3✔
726
  {
727
    childValues_.reserve(size);
3✔
728
    addChildValues_ = true;
3✔
729
    ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
3✔
730
    for (ArrayIndex index = 0; index < size; ++index) {
36✔
731
      if (hasCommentForValue(value[index])) {
33✔
732
        isMultiLine = true;
733
      }
734
      writeValue(value[index]);
33✔
735
      lineLength += static_cast<ArrayIndex>(childValues_[index].length());
33✔
736
    }
737
    addChildValues_ = false;
3✔
738
    isMultiLine = isMultiLine || lineLength >= rightMargin_;
3✔
739
  }
740
  return isMultiLine;
3✔
741
}
742

743
void StyledStreamWriter::pushValue(const String& value) {
46✔
744
  if (addChildValues_)
46✔
745
    childValues_.push_back(value);
33✔
746
  else
747
    *document_ << value;
13✔
748
}
46✔
749

750
void StyledStreamWriter::writeIndent() {
41✔
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_;
41✔
756
}
41✔
757

758
void StyledStreamWriter::writeWithIndent(const String& value) {
43✔
759
  if (!indented_)
43✔
760
    writeIndent();
39✔
761
  *document_ << value;
43✔
762
  indented_ = false;
43✔
763
}
43✔
764

765
void StyledStreamWriter::indent() { indentString_ += indentation_; }
5✔
766

767
void StyledStreamWriter::unindent() {
5✔
768
  assert(indentString_.size() >= indentation_.size());
5✔
769
  indentString_.resize(indentString_.size() - indentation_.size());
5✔
770
}
5✔
771

772
void StyledStreamWriter::writeCommentBeforeValue(const Value& root) {
41✔
773
  if (!root.hasComment(commentBefore))
41✔
774
    return;
40✔
775

776
  if (!indented_)
1✔
777
    writeIndent();
×
778
  const String& comment = root.getComment(commentBefore);
1✔
779
  String::const_iterator iter = comment.begin();
780
  while (iter != comment.end()) {
21✔
781
    *document_ << *iter;
20✔
782
    if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))
20✔
783
      // writeIndent();  // would include newline
784
      *document_ << indentString_;
×
785
    ++iter;
786
  }
787
  indented_ = false;
1✔
788
}
789

790
void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) {
41✔
791
  if (root.hasComment(commentAfterOnSameLine))
41✔
792
    *document_ << ' ' << root.getComment(commentAfterOnSameLine);
2✔
793

794
  if (root.hasComment(commentAfter)) {
41✔
795
    writeIndent();
1✔
796
    *document_ << root.getComment(commentAfter);
2✔
797
  }
798
  indented_ = false;
41✔
799
}
41✔
800

801
bool StyledStreamWriter::hasCommentForValue(const Value& value) {
33✔
802
  return value.hasComment(commentBefore) ||
66✔
803
         value.hasComment(commentAfterOnSameLine) ||
66✔
804
         value.hasComment(commentAfter);
33✔
805
}
806

807
//////////////////////////
808
// BuiltStyledStreamWriter
809

810
/// Scoped enums are not available until C++11.
811
struct CommentStyle {
812
  /// Decide whether to write comments.
813
  enum Enum {
814
    None, ///< Drop all comments.
815
    Most, ///< Recover odd behavior of previous versions (not implemented yet).
816
    All   ///< Keep all comments.
817
  };
818
};
819

820
struct 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

828
private:
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
};
858
BuiltStyledStreamWriter::BuiltStyledStreamWriter(
428✔
859
    String indentation, CommentStyle::Enum cs, String colonSymbol,
860
    String nullSymbol, String endingLineFeedSymbol, bool useSpecialFloats,
861
    bool emitUTF8, unsigned int precision, PrecisionType precisionType)
428✔
862
    : rightMargin_(74), indentation_(std::move(indentation)), cs_(cs),
428✔
863
      colonSymbol_(std::move(colonSymbol)), nullSymbol_(std::move(nullSymbol)),
428✔
864
      endingLineFeedSymbol_(std::move(endingLineFeedSymbol)),
428✔
865
      addChildValues_(false), indented_(false),
428✔
866
      useSpecialFloats_(useSpecialFloats), emitUTF8_(emitUTF8),
428✔
867
      precision_(precision), precisionType_(precisionType) {}
428✔
868
int BuiltStyledStreamWriter::write(Value const& root, OStream* sout) {
428✔
869
  sout_ = sout;
428✔
870
  addChildValues_ = false;
428✔
871
  indented_ = true;
428✔
872
  indentString_.clear();
873
  writeCommentBeforeValue(root);
428✔
874
  if (!indented_)
428✔
875
    writeIndent();
4✔
876
  indented_ = true;
428✔
877
  writeValue(root);
428✔
878
  writeCommentAfterValueOnSameLine(root);
428✔
879
  *sout_ << endingLineFeedSymbol_;
428✔
880
  sout_ = nullptr;
428✔
881
  return 0;
428✔
882
}
883
void BuiltStyledStreamWriter::writeValue(Value const& value) {
866✔
884
  switch (value.type()) {
866✔
885
  case nullValue:
6✔
886
    pushValue(nullSymbol_);
6✔
887
    break;
6✔
888
  case intValue:
1✔
889
    pushValue(valueToString(value.asLargestInt()));
1✔
890
    break;
1✔
891
  case uintValue:
32✔
892
    pushValue(valueToString(value.asLargestUInt()));
32✔
893
    break;
32✔
894
  case realValue:
18✔
895
    pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_,
18✔
896
                            precisionType_));
897
    break;
18✔
898
  case stringValue: {
405✔
899
    // Is NULL is possible for value.string_? No.
900
    char const* str;
901
    char const* end;
902
    bool ok = value.getString(&str, &end);
405✔
903
    if (ok)
405✔
904
      pushValue(
405✔
905
          valueToQuotedStringN(str, static_cast<size_t>(end - str), emitUTF8_));
810✔
906
    else
907
      pushValue("");
×
908
    break;
909
  }
910
  case booleanValue:
2✔
911
    pushValue(valueToString(value.asBool()));
2✔
912
    break;
2✔
913
  case arrayValue:
4✔
914
    writeArrayValue(value);
4✔
915
    break;
4✔
916
  case objectValue: {
398✔
917
    Value::Members members(value.getMemberNames());
398✔
918
    if (members.empty())
398✔
919
      pushValue("{}");
2✔
920
    else {
921
      writeWithIndent("{");
397✔
922
      indent();
397✔
923
      auto it = members.begin();
924
      for (;;) {
925
        String const& name = *it;
926
        Value const& childValue = value[name];
405✔
927
        writeCommentBeforeValue(childValue);
405✔
928
        writeWithIndent(
405✔
929
            valueToQuotedStringN(name.data(), name.length(), emitUTF8_));
405✔
930
        *sout_ << colonSymbol_;
405✔
931
        writeValue(childValue);
405✔
932
        if (++it == members.end()) {
405✔
933
          writeCommentAfterValueOnSameLine(childValue);
397✔
934
          break;
935
        }
936
        *sout_ << ",";
8✔
937
        writeCommentAfterValueOnSameLine(childValue);
8✔
938
      }
939
      unindent();
397✔
940
      writeWithIndent("}");
794✔
941
    }
942
  } break;
398✔
943
  }
944
}
866✔
945

946
void BuiltStyledStreamWriter::writeArrayValue(Value const& value) {
4✔
947
  unsigned size = value.size();
4✔
948
  if (size == 0)
4✔
949
    pushValue("[]");
2✔
950
  else {
951
    bool isMultiLine = (cs_ == CommentStyle::All) || isMultilineArray(value);
3✔
952
    if (isMultiLine) {
953
      writeWithIndent("[");
2✔
954
      indent();
2✔
955
      bool hasChildValue = !childValues_.empty();
956
      unsigned index = 0;
957
      for (;;) {
958
        Value const& childValue = value[index];
23✔
959
        writeCommentBeforeValue(childValue);
23✔
960
        if (hasChildValue)
23✔
961
          writeWithIndent(childValues_[index]);
21✔
962
        else {
963
          if (!indented_)
2✔
964
            writeIndent();
2✔
965
          indented_ = true;
2✔
966
          writeValue(childValue);
2✔
967
          indented_ = false;
2✔
968
        }
969
        if (++index == size) {
23✔
970
          writeCommentAfterValueOnSameLine(childValue);
2✔
971
          break;
972
        }
973
        *sout_ << ",";
21✔
974
        writeCommentAfterValueOnSameLine(childValue);
21✔
975
      }
21✔
976
      unindent();
2✔
977
      writeWithIndent("]");
4✔
978
    } else // output on a single line
979
    {
980
      assert(childValues_.size() == size);
1✔
981
      *sout_ << "[";
1✔
982
      if (!indentation_.empty())
1✔
983
        *sout_ << " ";
1✔
984
      for (unsigned index = 0; index < size; ++index) {
11✔
985
        if (index > 0)
10✔
986
          *sout_ << ((!indentation_.empty()) ? ", " : ",");
9✔
987
        *sout_ << childValues_[index];
10✔
988
      }
989
      if (!indentation_.empty())
1✔
990
        *sout_ << " ";
1✔
991
      *sout_ << "]";
1✔
992
    }
993
  }
994
}
4✔
995

996
bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) {
2✔
997
  ArrayIndex const size = value.size();
2✔
998
  bool isMultiLine = size * 3 >= rightMargin_;
2✔
999
  childValues_.clear();
1000
  for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
33✔
1001
    Value const& childValue = value[index];
31✔
1002
    isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
31✔
1003
                   !childValue.empty());
×
1004
  }
1005
  if (!isMultiLine) // check if line length > max line length
2✔
1006
  {
1007
    childValues_.reserve(size);
2✔
1008
    addChildValues_ = true;
2✔
1009
    ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
2✔
1010
    for (ArrayIndex index = 0; index < size; ++index) {
33✔
1011
      if (hasCommentForValue(value[index])) {
31✔
1012
        isMultiLine = true;
1013
      }
1014
      writeValue(value[index]);
31✔
1015
      lineLength += static_cast<ArrayIndex>(childValues_[index].length());
31✔
1016
    }
1017
    addChildValues_ = false;
2✔
1018
    isMultiLine = isMultiLine || lineLength >= rightMargin_;
2✔
1019
  }
1020
  return isMultiLine;
2✔
1021
}
1022

1023
void BuiltStyledStreamWriter::pushValue(String const& value) {
466✔
1024
  if (addChildValues_)
466✔
1025
    childValues_.push_back(value);
31✔
1026
  else
1027
    *sout_ << value;
435✔
1028
}
466✔
1029

1030
void BuiltStyledStreamWriter::writeIndent() {
833✔
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()) {
833✔
1037
    // In this case, drop newlines too.
1038
    *sout_ << '\n' << indentString_;
825✔
1039
  }
1040
}
833✔
1041

1042
void BuiltStyledStreamWriter::writeWithIndent(String const& value) {
1,224✔
1043
  if (!indented_)
1,224✔
1044
    writeIndent();
827✔
1045
  *sout_ << value;
1,224✔
1046
  indented_ = false;
1,224✔
1047
}
1,224✔
1048

1049
void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; }
399✔
1050

1051
void BuiltStyledStreamWriter::unindent() {
399✔
1052
  assert(indentString_.size() >= indentation_.size());
399✔
1053
  indentString_.resize(indentString_.size() - indentation_.size());
399✔
1054
}
399✔
1055

1056
void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) {
856✔
1057
  if (cs_ == CommentStyle::None)
856✔
1058
    return;
852✔
1059
  if (!root.hasComment(commentBefore))
833✔
1060
    return;
1061

1062
  if (!indented_)
4✔
1063
    writeIndent();
×
1064
  const String& comment = root.getComment(commentBefore);
4✔
1065
  String::const_iterator iter = comment.begin();
1066
  while (iter != comment.end()) {
148✔
1067
    *sout_ << *iter;
144✔
1068
    if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))
144✔
1069
      // writeIndent();  // would write extra newline
1070
      *sout_ << indentString_;
×
1071
    ++iter;
1072
  }
1073
  indented_ = false;
4✔
1074
}
1075

1076
void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(
856✔
1077
    Value const& root) {
1078
  if (cs_ == CommentStyle::None)
856✔
1079
    return;
1080
  if (root.hasComment(commentAfterOnSameLine))
833✔
1081
    *sout_ << " " + root.getComment(commentAfterOnSameLine);
×
1082

1083
  if (root.hasComment(commentAfter)) {
833✔
1084
    writeIndent();
×
1085
    *sout_ << root.getComment(commentAfter);
×
1086
  }
1087
}
1088

1089
// static
1090
bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) {
31✔
1091
  return value.hasComment(commentBefore) ||
62✔
1092
         value.hasComment(commentAfterOnSameLine) ||
62✔
1093
         value.hasComment(commentAfter);
31✔
1094
}
1095

1096
///////////////
1097
// StreamWriter
1098

1099
StreamWriter::StreamWriter() : sout_(nullptr) {}
428✔
1100
StreamWriter::~StreamWriter() = default;
856✔
1101
StreamWriter::Factory::~Factory() = default;
46✔
1102
StreamWriterBuilder::StreamWriterBuilder() { setDefaults(&settings_); }
23✔
1103
StreamWriterBuilder::~StreamWriterBuilder() = default;
46✔
1104
StreamWriter* StreamWriterBuilder::newStreamWriter() const {
428✔
1105
  const String indentation = settings_["indentation"].asString();
428✔
1106
  const String cs_str = settings_["commentStyle"].asString();
428✔
1107
  const String pt_str = settings_["precisionType"].asString();
428✔
1108
  const bool eyc = settings_["enableYAMLCompatibility"].asBool();
428✔
1109
  const bool dnp = settings_["dropNullPlaceholders"].asBool();
428✔
1110
  const bool usf = settings_["useSpecialFloats"].asBool();
428✔
1111
  const bool emitUTF8 = settings_["emitUTF8"].asBool();
428✔
1112
  unsigned int pre = settings_["precision"].asUInt();
428✔
1113
  CommentStyle::Enum cs = CommentStyle::All;
1114
  if (cs_str == "All") {
428✔
1115
    cs = CommentStyle::All;
1116
  } else if (cs_str == "None") {
2✔
1117
    cs = CommentStyle::None;
1118
  } else {
1119
    throwRuntimeError("commentStyle must be 'All' or 'None'");
×
1120
  }
1121
  PrecisionType precisionType(significantDigits);
1122
  if (pt_str == "significant") {
428✔
1123
    precisionType = PrecisionType::significantDigits;
1124
  } else if (pt_str == "decimal") {
7✔
1125
    precisionType = PrecisionType::decimalPlaces;
1126
  } else {
1127
    throwRuntimeError("precisionType must be 'significant' or 'decimal'");
×
1128
  }
1129
  String colonSymbol = " : ";
428✔
1130
  if (eyc) {
428✔
1131
    colonSymbol = ": ";
1132
  } else if (indentation.empty()) {
427✔
1133
    colonSymbol = ":";
1134
  }
1135
  String nullSymbol = "null";
428✔
1136
  if (dnp) {
428✔
1137
    nullSymbol.clear();
1138
  }
1139
  if (pre > 17)
428✔
1140
    pre = 17;
1141
  String endingLineFeedSymbol;
1142
  return new BuiltStyledStreamWriter(indentation, cs, colonSymbol, nullSymbol,
1143
                                     endingLineFeedSymbol, usf, emitUTF8, pre,
1144
                                     precisionType);
1,712✔
1145
}
1146

1147
bool StreamWriterBuilder::validate(Json::Value* invalid) const {
2✔
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
  };
11✔
1158
  for (auto si = settings_.begin(); si != settings_.end(); ++si) {
38✔
1159
    auto key = si.name();
17✔
1160
    if (valid_keys.count(key))
17✔
1161
      continue;
1162
    if (invalid)
1✔
1163
      (*invalid)[key] = *si;
1✔
1164
    else
1165
      return false;
1166
  }
1167
  return invalid ? invalid->empty() : true;
2✔
1168
}
2✔
1169

1170
Value& StreamWriterBuilder::operator[](const String& key) {
1✔
1171
  return settings_[key];
1✔
1172
}
1173
// static
1174
void StreamWriterBuilder::setDefaults(Json::Value* settings) {
23✔
1175
  //! [StreamWriterBuilderDefaults]
1176
  (*settings)["commentStyle"] = "All";
23✔
1177
  (*settings)["indentation"] = "\t";
23✔
1178
  (*settings)["enableYAMLCompatibility"] = false;
23✔
1179
  (*settings)["dropNullPlaceholders"] = false;
23✔
1180
  (*settings)["useSpecialFloats"] = false;
23✔
1181
  (*settings)["emitUTF8"] = false;
23✔
1182
  (*settings)["precision"] = 17;
23✔
1183
  (*settings)["precisionType"] = "significant";
23✔
1184
  //! [StreamWriterBuilderDefaults]
1185
}
23✔
1186

1187
String writeString(StreamWriter::Factory const& factory, Value const& root) {
425✔
1188
  OStringStream sout;
425✔
1189
  StreamWriterPtr const writer(factory.newStreamWriter());
425✔
1190
  writer->write(root, &sout);
425✔
1191
  return std::move(sout).str();
425✔
1192
}
425✔
1193

1194
OStream& operator<<(OStream& sout, Value const& root) {
3✔
1195
  StreamWriterBuilder builder;
3✔
1196
  StreamWriterPtr const writer(builder.newStreamWriter());
3✔
1197
  writer->write(root, &sout);
3✔
1198
  return sout;
3✔
1199
}
3✔
1200

1201
} // namespace Json
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc