1 /*!
2 * Copyright (c) by Contributors 2019-2021
3 */
4 #include <cctype>
5 #include <cstddef>
6 #include <iterator>
7 #include <locale>
8 #include <sstream>
9 #include <limits>
10 #include <cmath>
11
12 #include "charconv.h"
13 #include "xgboost/base.h"
14 #include "xgboost/logging.h"
15 #include "xgboost/json.h"
16 #include "xgboost/json_io.h"
17
18 namespace xgboost {
19
Save(Json json)20 void JsonWriter::Save(Json json) {
21 json.ptr_->Save(this);
22 }
23
Visit(JsonArray const * arr)24 void JsonWriter::Visit(JsonArray const* arr) {
25 stream_->emplace_back('[');
26 auto const& vec = arr->GetArray();
27 size_t size = vec.size();
28 for (size_t i = 0; i < size; ++i) {
29 auto const& value = vec[i];
30 this->Save(value);
31 if (i != size - 1) {
32 stream_->emplace_back(',');
33 }
34 }
35 stream_->emplace_back(']');
36 }
37
Visit(JsonObject const * obj)38 void JsonWriter::Visit(JsonObject const* obj) {
39 stream_->emplace_back('{');
40 size_t i = 0;
41 size_t size = obj->GetObject().size();
42
43 for (auto& value : obj->GetObject()) {
44 auto s = String{value.first};
45 this->Visit(&s);
46 stream_->emplace_back(':');
47 this->Save(value.second);
48
49 if (i != size-1) {
50 stream_->emplace_back(',');
51 }
52 i++;
53 }
54
55 stream_->emplace_back('}');
56 }
57
Visit(JsonNumber const * num)58 void JsonWriter::Visit(JsonNumber const* num) {
59 char number[NumericLimits<float>::kToCharsSize];
60 auto res = to_chars(number, number + sizeof(number), num->GetNumber());
61 auto end = res.ptr;
62 auto ori_size = stream_->size();
63 stream_->resize(stream_->size() + end - number);
64 std::memcpy(stream_->data() + ori_size, number, end - number);
65 }
66
Visit(JsonInteger const * num)67 void JsonWriter::Visit(JsonInteger const* num) {
68 char i2s_buffer_[NumericLimits<int64_t>::kToCharsSize];
69 auto i = num->GetInteger();
70 auto ret = to_chars(i2s_buffer_, i2s_buffer_ + NumericLimits<int64_t>::kToCharsSize, i);
71 auto end = ret.ptr;
72 CHECK(ret.ec == std::errc());
73 auto digits = std::distance(i2s_buffer_, end);
74 auto ori_size = stream_->size();
75 stream_->resize(ori_size + digits);
76 std::memcpy(stream_->data() + ori_size, i2s_buffer_, digits);
77 }
78
Visit(JsonNull const *)79 void JsonWriter::Visit(JsonNull const* ) {
80 auto s = stream_->size();
81 stream_->resize(s + 4);
82 auto& buf = (*stream_);
83 buf[s + 0] = 'n';
84 buf[s + 1] = 'u';
85 buf[s + 2] = 'l';
86 buf[s + 3] = 'l';
87 }
88
Visit(JsonString const * str)89 void JsonWriter::Visit(JsonString const* str) {
90 std::string buffer;
91 buffer += '"';
92 auto const& string = str->GetString();
93 for (size_t i = 0; i < string.length(); i++) {
94 const char ch = string[i];
95 if (ch == '\\') {
96 if (i < string.size() && string[i+1] == 'u') {
97 buffer += "\\";
98 } else {
99 buffer += "\\\\";
100 }
101 } else if (ch == '"') {
102 buffer += "\\\"";
103 } else if (ch == '\b') {
104 buffer += "\\b";
105 } else if (ch == '\f') {
106 buffer += "\\f";
107 } else if (ch == '\n') {
108 buffer += "\\n";
109 } else if (ch == '\r') {
110 buffer += "\\r";
111 } else if (ch == '\t') {
112 buffer += "\\t";
113 } else if (static_cast<uint8_t>(ch) <= 0x1f) {
114 // Unit separator
115 char buf[8];
116 snprintf(buf, sizeof buf, "\\u%04x", ch);
117 buffer += buf;
118 } else {
119 buffer += ch;
120 }
121 }
122 buffer += '"';
123
124 auto s = stream_->size();
125 stream_->resize(s + buffer.size());
126 std::memcpy(stream_->data() + s, buffer.data(), buffer.size());
127 }
128
Visit(JsonBoolean const * boolean)129 void JsonWriter::Visit(JsonBoolean const* boolean) {
130 bool val = boolean->GetBoolean();
131 auto s = stream_->size();
132 if (val) {
133 stream_->resize(s + 4);
134 auto& buf = (*stream_);
135 buf[s + 0] = 't';
136 buf[s + 1] = 'r';
137 buf[s + 2] = 'u';
138 buf[s + 3] = 'e';
139 } else {
140 stream_->resize(s + 5);
141 auto& buf = (*stream_);
142 buf[s + 0] = 'f';
143 buf[s + 1] = 'a';
144 buf[s + 2] = 'l';
145 buf[s + 3] = 's';
146 buf[s + 4] = 'e';
147 }
148 }
149
150 // Value
TypeStr() const151 std::string Value::TypeStr() const {
152 switch (kind_) {
153 case ValueKind::kString: return "String"; break;
154 case ValueKind::kNumber: return "Number"; break;
155 case ValueKind::kObject: return "Object"; break;
156 case ValueKind::kArray: return "Array"; break;
157 case ValueKind::kBoolean: return "Boolean"; break;
158 case ValueKind::kNull: return "Null"; break;
159 case ValueKind::kInteger: return "Integer"; break;
160 }
161 return "";
162 }
163
164 // Only used for keeping old compilers happy about non-reaching return
165 // statement.
DummyJsonObject()166 Json& DummyJsonObject() {
167 static Json obj;
168 return obj;
169 }
170
171 // Json Object
JsonObject(JsonObject && that)172 JsonObject::JsonObject(JsonObject && that) noexcept :
173 Value(ValueKind::kObject), object_{std::move(that.object_)} {}
174
JsonObject(std::map<std::string,Json> && object)175 JsonObject::JsonObject(std::map<std::string, Json> &&object) noexcept
176 : Value(ValueKind::kObject), object_{std::move(object)} {}
177
operator [](std::string const & key)178 Json& JsonObject::operator[](std::string const & key) {
179 return object_[key];
180 }
181
operator [](int)182 Json& JsonObject::operator[](int ) {
183 LOG(FATAL) << "Object of type "
184 << Value::TypeStr() << " can not be indexed by Integer.";
185 return DummyJsonObject();
186 }
187
operator ==(Value const & rhs) const188 bool JsonObject::operator==(Value const& rhs) const {
189 if (!IsA<JsonObject>(&rhs)) {
190 return false;
191 }
192 return object_ == Cast<JsonObject const>(&rhs)->GetObject();
193 }
194
operator =(Value const & rhs)195 Value& JsonObject::operator=(Value const &rhs) {
196 JsonObject const* casted = Cast<JsonObject const>(&rhs);
197 object_ = casted->GetObject();
198 return *this;
199 }
200
Save(JsonWriter * writer)201 void JsonObject::Save(JsonWriter* writer) {
202 writer->Visit(this);
203 }
204
205 // Json String
operator [](std::string const &)206 Json& JsonString::operator[](std::string const& ) {
207 LOG(FATAL) << "Object of type "
208 << Value::TypeStr() << " can not be indexed by string.";
209 return DummyJsonObject();
210 }
211
operator [](int)212 Json& JsonString::operator[](int ) {
213 LOG(FATAL) << "Object of type "
214 << Value::TypeStr() << " can not be indexed by Integer."
215 << " Please try obtaining std::string first.";
216 return DummyJsonObject();
217 }
218
operator ==(Value const & rhs) const219 bool JsonString::operator==(Value const& rhs) const {
220 if (!IsA<JsonString>(&rhs)) { return false; }
221 return Cast<JsonString const>(&rhs)->GetString() == str_;
222 }
223
operator =(Value const & rhs)224 Value & JsonString::operator=(Value const &rhs) {
225 JsonString const* casted = Cast<JsonString const>(&rhs);
226 str_ = casted->GetString();
227 return *this;
228 }
229
230 // FIXME: UTF-8 parsing support.
Save(JsonWriter * writer)231 void JsonString::Save(JsonWriter* writer) {
232 writer->Visit(this);
233 }
234
235 // Json Array
JsonArray(JsonArray && that)236 JsonArray::JsonArray(JsonArray && that) noexcept :
237 Value(ValueKind::kArray), vec_{std::move(that.vec_)} {}
238
operator [](std::string const &)239 Json& JsonArray::operator[](std::string const& ) {
240 LOG(FATAL) << "Object of type "
241 << Value::TypeStr() << " can not be indexed by string.";
242 return DummyJsonObject();
243 }
244
operator [](int ind)245 Json& JsonArray::operator[](int ind) {
246 return vec_.at(ind);
247 }
248
operator ==(Value const & rhs) const249 bool JsonArray::operator==(Value const& rhs) const {
250 if (!IsA<JsonArray>(&rhs)) { return false; }
251 auto& arr = Cast<JsonArray const>(&rhs)->GetArray();
252 return std::equal(arr.cbegin(), arr.cend(), vec_.cbegin());
253 }
254
operator =(Value const & rhs)255 Value & JsonArray::operator=(Value const &rhs) {
256 JsonArray const* casted = Cast<JsonArray const>(&rhs);
257 vec_ = casted->GetArray();
258 return *this;
259 }
260
Save(JsonWriter * writer)261 void JsonArray::Save(JsonWriter* writer) {
262 writer->Visit(this);
263 }
264
265 // Json Number
operator [](std::string const &)266 Json& JsonNumber::operator[](std::string const& ) {
267 LOG(FATAL) << "Object of type "
268 << Value::TypeStr() << " can not be indexed by string.";
269 return DummyJsonObject();
270 }
271
operator [](int)272 Json& JsonNumber::operator[](int ) {
273 LOG(FATAL) << "Object of type "
274 << Value::TypeStr() << " can not be indexed by Integer.";
275 return DummyJsonObject();
276 }
277
operator ==(Value const & rhs) const278 bool JsonNumber::operator==(Value const& rhs) const {
279 if (!IsA<JsonNumber>(&rhs)) { return false; }
280 auto r_num = Cast<JsonNumber const>(&rhs)->GetNumber();
281 if (std::isinf(number_)) {
282 return std::isinf(r_num);
283 }
284 if (std::isnan(number_)) {
285 return std::isnan(r_num);
286 }
287 return number_ - r_num == 0;
288 }
289
operator =(Value const & rhs)290 Value & JsonNumber::operator=(Value const &rhs) {
291 JsonNumber const* casted = Cast<JsonNumber const>(&rhs);
292 number_ = casted->GetNumber();
293 return *this;
294 }
295
Save(JsonWriter * writer)296 void JsonNumber::Save(JsonWriter* writer) {
297 writer->Visit(this);
298 }
299
300 // Json Integer
operator [](std::string const &)301 Json& JsonInteger::operator[](std::string const& ) {
302 LOG(FATAL) << "Object of type "
303 << Value::TypeStr() << " can not be indexed by string.";
304 return DummyJsonObject();
305 }
306
operator [](int)307 Json& JsonInteger::operator[](int ) {
308 LOG(FATAL) << "Object of type "
309 << Value::TypeStr() << " can not be indexed by Integer.";
310 return DummyJsonObject();
311 }
312
operator ==(Value const & rhs) const313 bool JsonInteger::operator==(Value const& rhs) const {
314 if (!IsA<JsonInteger>(&rhs)) { return false; }
315 return integer_ == Cast<JsonInteger const>(&rhs)->GetInteger();
316 }
317
operator =(Value const & rhs)318 Value & JsonInteger::operator=(Value const &rhs) {
319 JsonInteger const* casted = Cast<JsonInteger const>(&rhs);
320 integer_ = casted->GetInteger();
321 return *this;
322 }
323
Save(JsonWriter * writer)324 void JsonInteger::Save(JsonWriter* writer) {
325 writer->Visit(this);
326 }
327
328 // Json Null
operator [](std::string const &)329 Json& JsonNull::operator[](std::string const& ) {
330 LOG(FATAL) << "Object of type "
331 << Value::TypeStr() << " can not be indexed by string.";
332 return DummyJsonObject();
333 }
334
operator [](int)335 Json& JsonNull::operator[](int ) {
336 LOG(FATAL) << "Object of type "
337 << Value::TypeStr() << " can not be indexed by Integer.";
338 return DummyJsonObject();
339 }
340
operator ==(Value const & rhs) const341 bool JsonNull::operator==(Value const& rhs) const {
342 if (!IsA<JsonNull>(&rhs)) { return false; }
343 return true;
344 }
345
operator =(Value const & rhs)346 Value & JsonNull::operator=(Value const &rhs) {
347 Cast<JsonNull const>(&rhs); // Checking only.
348 return *this;
349 }
350
Save(JsonWriter * writer)351 void JsonNull::Save(JsonWriter* writer) {
352 writer->Visit(this);
353 }
354
355 // Json Boolean
operator [](std::string const &)356 Json& JsonBoolean::operator[](std::string const& ) {
357 LOG(FATAL) << "Object of type "
358 << Value::TypeStr() << " can not be indexed by string.";
359 return DummyJsonObject();
360 }
361
operator [](int)362 Json& JsonBoolean::operator[](int ) {
363 LOG(FATAL) << "Object of type "
364 << Value::TypeStr() << " can not be indexed by Integer.";
365 return DummyJsonObject();
366 }
367
operator ==(Value const & rhs) const368 bool JsonBoolean::operator==(Value const& rhs) const {
369 if (!IsA<JsonBoolean>(&rhs)) { return false; }
370 return boolean_ == Cast<JsonBoolean const>(&rhs)->GetBoolean();
371 }
372
operator =(Value const & rhs)373 Value & JsonBoolean::operator=(Value const &rhs) {
374 JsonBoolean const* casted = Cast<JsonBoolean const>(&rhs);
375 boolean_ = casted->GetBoolean();
376 return *this;
377 }
378
Save(JsonWriter * writer)379 void JsonBoolean::Save(JsonWriter *writer) {
380 writer->Visit(this);
381 }
382
383 size_t constexpr JsonReader::kMaxNumLength;
384
Parse()385 Json JsonReader::Parse() {
386 while (true) {
387 SkipSpaces();
388 char c = PeekNextChar();
389 if (c == -1) { break; }
390
391 if (c == '{') {
392 return ParseObject();
393 } else if ( c == '[' ) {
394 return ParseArray();
395 } else if ( c == '-' || std::isdigit(c) ||
396 c == 'N' || c == 'I') {
397 // For now we only accept `NaN`, not `nan` as the later violates LR(1) with `null`.
398 return ParseNumber();
399 } else if ( c == '\"' ) {
400 return ParseString();
401 } else if ( c == 't' || c == 'f' ) {
402 return ParseBoolean();
403 } else if (c == 'n') {
404 return ParseNull();
405 } else {
406 Error("Unknown construct");
407 }
408 }
409 return {};
410 }
411
Load()412 Json JsonReader::Load() {
413 Json result = Parse();
414 return result;
415 }
416
Error(std::string msg) const417 void JsonReader::Error(std::string msg) const {
418 // just copy it.
419 std::istringstream str_s(raw_str_.substr(0, raw_str_.size()));
420
421 msg += ", around character position: " + std::to_string(cursor_.Pos());
422 msg += '\n';
423
424 if (cursor_.Pos() == 0) {
425 LOG(FATAL) << msg << ", \"" << str_s.str() << " \"";
426 }
427
428 constexpr size_t kExtend = 8;
429 auto beg = static_cast<int64_t>(cursor_.Pos()) -
430 static_cast<int64_t>(kExtend) < 0 ? 0 : cursor_.Pos() - kExtend;
431 auto end = cursor_.Pos() + kExtend >= raw_str_.size() ?
432 raw_str_.size() : cursor_.Pos() + kExtend;
433
434 std::string const& raw_portion = raw_str_.substr(beg, end - beg);
435 std::string portion;
436 for (auto c : raw_portion) {
437 if (c == '\n') {
438 portion += "\\n";
439 } else if (c == '\0') {
440 portion += "\\0";
441 } else {
442 portion += c;
443 }
444 }
445
446 msg += " ";
447 msg += portion;
448 msg += '\n';
449
450 msg += " ";
451 for (size_t i = beg; i < cursor_.Pos() - 1; ++i) {
452 msg += '~';
453 }
454 msg += '^';
455 for (size_t i = cursor_.Pos(); i < end; ++i) {
456 msg += '~';
457 }
458 LOG(FATAL) << msg;
459 }
460
461 namespace {
IsSpace(char c)462 bool IsSpace(char c) { return c == ' ' || c == '\n' || c == '\r' || c == '\t'; }
463 } // anonymous namespace
464
465 // Json class
SkipSpaces()466 void JsonReader::SkipSpaces() {
467 while (cursor_.Pos() < raw_str_.size()) {
468 char c = raw_str_[cursor_.Pos()];
469 if (IsSpace(c)) {
470 cursor_.Forward();
471 } else {
472 break;
473 }
474 }
475 }
476
ParseStr(std::string const & str)477 void ParseStr(std::string const& str) {
478 size_t end = 0;
479 for (size_t i = 0; i < str.size(); ++i) {
480 if (str[i] == '"' && i > 0 && str[i-1] != '\\') {
481 end = i;
482 break;
483 }
484 }
485 std::string result;
486 result.resize(end);
487 }
488
ParseString()489 Json JsonReader::ParseString() {
490 char ch { GetConsecutiveChar('\"') }; // NOLINT
491 std::ostringstream output;
492 std::string str;
493 while (true) {
494 ch = GetNextChar();
495 if (ch == '\\') {
496 char next = static_cast<char>(GetNextChar());
497 switch (next) {
498 case 'r': str += u8"\r"; break;
499 case 'n': str += u8"\n"; break;
500 case '\\': str += u8"\\"; break;
501 case 't': str += u8"\t"; break;
502 case '\"': str += u8"\""; break;
503 case 'u':
504 str += ch;
505 str += 'u';
506 break;
507 default: Error("Unknown escape");
508 }
509 } else {
510 if (ch == '\"') break;
511 str += ch;
512 }
513 if (ch == EOF || ch == '\r' || ch == '\n') {
514 Expect('\"', ch);
515 }
516 }
517 return Json(std::move(str));
518 }
519
ParseNull()520 Json JsonReader::ParseNull() {
521 char ch = GetNextNonSpaceChar();
522 std::string buffer{ch};
523 for (size_t i = 0; i < 3; ++i) {
524 buffer.push_back(GetNextChar());
525 }
526 if (buffer != "null") {
527 Error("Expecting null value \"null\"");
528 }
529 return Json{JsonNull()};
530 }
531
ParseArray()532 Json JsonReader::ParseArray() {
533 std::vector<Json> data;
534
535 char ch { GetConsecutiveChar('[') }; // NOLINT
536 while (true) {
537 if (PeekNextChar() == ']') {
538 GetConsecutiveChar(']');
539 return Json(std::move(data));
540 }
541 auto obj = Parse();
542 data.emplace_back(obj);
543 ch = GetNextNonSpaceChar();
544 if (ch == ']') break;
545 if (ch != ',') {
546 Expect(',', ch);
547 }
548 }
549
550 return Json(std::move(data));
551 }
552
ParseObject()553 Json JsonReader::ParseObject() {
554 GetConsecutiveChar('{');
555
556 std::map<std::string, Json> data;
557 SkipSpaces();
558 char ch = PeekNextChar();
559
560 if (ch == '}') {
561 GetConsecutiveChar('}');
562 return Json(std::move(data));
563 }
564
565 while (true) {
566 SkipSpaces();
567 ch = PeekNextChar();
568 CHECK_NE(ch, -1) << "cursor_.Pos(): " << cursor_.Pos() << ", "
569 << "raw_str_.size():" << raw_str_.size();
570 if (ch != '"') {
571 Expect('"', ch);
572 }
573 Json key = ParseString();
574
575 ch = GetNextNonSpaceChar();
576
577 if (ch != ':') {
578 Expect(':', ch);
579 }
580
581 Json value { Parse() };
582
583 data[get<String>(key)] = std::move(value);
584
585 ch = GetNextNonSpaceChar();
586
587 if (ch == '}') break;
588 if (ch != ',') {
589 Expect(',', ch);
590 }
591 }
592
593 return Json(std::move(data));
594 }
595
ParseNumber()596 Json JsonReader::ParseNumber() {
597 // Adopted from sajson with some simplifications and small optimizations.
598 char const* p = raw_str_.c_str() + cursor_.Pos();
599 char const* const beg = p; // keep track of current pointer
600
601 // TODO(trivialfis): Add back all the checks for number
602 if (XGBOOST_EXPECT(*p == 'N', false)) {
603 GetConsecutiveChar('N');
604 GetConsecutiveChar('a');
605 GetConsecutiveChar('N');
606 return Json(static_cast<Number::Float>(std::numeric_limits<float>::quiet_NaN()));
607 }
608
609 bool negative = false;
610 switch (*p) {
611 case '-': {
612 negative = true;
613 ++p;
614 break;
615 }
616 case '+': {
617 negative = false;
618 ++p;
619 break;
620 }
621 default: {
622 break;
623 }
624 }
625
626 if (XGBOOST_EXPECT(*p == 'I', false)) {
627 cursor_.Forward(std::distance(beg, p)); // +/-
628 for (auto i : {'I', 'n', 'f', 'i', 'n', 'i', 't', 'y'}) {
629 GetConsecutiveChar(i);
630 }
631 auto f = std::numeric_limits<float>::infinity();
632 if (negative) {
633 f = -f;
634 }
635 return Json(static_cast<Number::Float>(f));
636 }
637
638 bool is_float = false;
639
640 int64_t i = 0;
641
642 if (*p == '0') {
643 i = 0;
644 p++;
645 }
646
647 while (XGBOOST_EXPECT(*p >= '0' && *p <= '9', true)) {
648 i = i * 10 + (*p - '0');
649 p++;
650 }
651
652 if (*p == '.') {
653 p++;
654 is_float = true;
655
656 while (*p >= '0' && *p <= '9') {
657 i = i * 10 + (*p - '0');
658 p++;
659 }
660 }
661
662 if (*p == 'E' || *p == 'e') {
663 is_float = true;
664 p++;
665
666 switch (*p) {
667 case '-':
668 case '+': {
669 p++;
670 break;
671 }
672 default:
673 break;
674 }
675
676 if (XGBOOST_EXPECT(*p >= '0' && *p <= '9', true)) {
677 p++;
678 while (*p >= '0' && *p <= '9') {
679 p++;
680 }
681 } else {
682 Error("Expecting digit");
683 }
684 }
685
686 auto moved = std::distance(beg, p);
687 this->cursor_.Forward(moved);
688
689 if (is_float) {
690 float f;
691 auto ret = from_chars(beg, p, f);
692 if (XGBOOST_EXPECT(ret.ec != std::errc(), false)) {
693 // Compatible with old format that generates very long mantissa from std stream.
694 f = std::strtof(beg, nullptr);
695 }
696 return Json(static_cast<Number::Float>(f));
697 } else {
698 if (negative) {
699 i = -i;
700 }
701 return Json(JsonInteger(i));
702 }
703 }
704
ParseBoolean()705 Json JsonReader::ParseBoolean() {
706 bool result = false;
707 char ch = GetNextNonSpaceChar();
708 std::string const t_value = u8"true";
709 std::string const f_value = u8"false";
710 std::string buffer;
711
712 if (ch == 't') {
713 GetConsecutiveChar('r');
714 GetConsecutiveChar('u');
715 GetConsecutiveChar('e');
716 result = true;
717 } else {
718 GetConsecutiveChar('a');
719 GetConsecutiveChar('l');
720 GetConsecutiveChar('s');
721 GetConsecutiveChar('e');
722 result = false;
723 }
724 return Json{JsonBoolean{result}};
725 }
726
Load(StringView str)727 Json Json::Load(StringView str) {
728 JsonReader reader(str);
729 Json json{reader.Load()};
730 return json;
731 }
732
Load(JsonReader * reader)733 Json Json::Load(JsonReader* reader) {
734 Json json{reader->Load()};
735 return json;
736 }
737
Dump(Json json,std::string * str)738 void Json::Dump(Json json, std::string* str) {
739 std::vector<char> buffer;
740 JsonWriter writer(&buffer);
741 writer.Save(json);
742 str->resize(buffer.size());
743 std::copy(buffer.cbegin(), buffer.cend(), str->begin());
744 }
745
746 Json& Json::operator=(Json const &other) = default;
747
operator <<(std::ostream & os,StringView const v)748 std::ostream &operator<<(std::ostream &os, StringView const v) {
749 for (auto c : v) {
750 os.put(c);
751 }
752 return os;
753 }
754
755 static_assert(std::is_nothrow_move_constructible<Json>::value, "");
756 static_assert(std::is_nothrow_move_constructible<Object>::value, "");
757 static_assert(std::is_nothrow_move_constructible<Array>::value, "");
758 static_assert(std::is_nothrow_move_constructible<String>::value, "");
759 } // namespace xgboost
760