1#ifndef MILLIJSON_MILLIJSON_HPP
2#define MILLIJSON_MILLIJSON_HPP
11#include <unordered_map>
12#include <unordered_set>
60 Base& operator=(
const Base&) =
default;
83 const double&
value()
const {
return my_value; }
88 double&
value() {
return my_value; }
110 const std::string&
value()
const {
return my_value; }
115 std::string&
value() {
return my_value; }
118 std::string my_value;
129 String(std::string x) : my_value(std::move(x)) {}
137 const std::string&
value()
const {
return my_value; }
142 std::string&
value() {
return my_value; }
145 std::string my_value;
164 const bool&
value()
const {
return my_value; }
191 Array(std::vector<std::shared_ptr<Base> > x) : my_value(std::move(x)) {}
199 const std::vector<std::shared_ptr<Base> >&
value()
const {
206 std::vector<std::shared_ptr<Base> >&
value() {
211 std::vector<std::shared_ptr<Base> > my_value;
222 Object(std::unordered_map<std::string, std::shared_ptr<Base> > x) : my_value(std::move(x)) {}
230 const std::unordered_map<std::string, std::shared_ptr<Base> >&
value()
const {
237 std::unordered_map<std::string, std::shared_ptr<Base> >&
value() {
242 std::unordered_map<std::string, std::shared_ptr<Base> > my_value;
274template<
class Input_>
275bool raw_chomp(Input_& input,
bool ok) {
277 switch(input.get()) {
279 case ' ':
case '\n':
case '\r':
case '\t':
284 ok = input.advance();
289template<
class Input_>
290bool check_and_chomp(Input_& input) {
291 bool ok = input.valid();
292 return raw_chomp(input, ok);
295template<
class Input_>
296bool advance_and_chomp(Input_& input) {
297 bool ok = input.advance();
298 return raw_chomp(input, ok);
301inline bool is_digit(
char val) {
302 return val >=
'0' && val <=
'9';
305template<
class Input_>
306bool is_expected_string(Input_& input,
const char* ptr, std::size_t len) {
308 for (std::size_t i = 1; i < len; ++i) {
312 if (!input.advance()) {
315 if (input.get() != ptr[i]) {
323template<
class Input_>
324std::string extract_string(Input_& input) {
325 unsigned long long start = input.position() + 1;
330 char next = input.get();
337 if (!input.advance()) {
338 throw std::runtime_error(
"unterminated string at position " + std::to_string(start));
340 char next2 = input.get();
368 unsigned short mb = 0;
369 for (
int i = 0; i < 4; ++i) {
370 if (!input.advance()){
371 throw std::runtime_error(
"unterminated string at position " + std::to_string(start));
374 char val = input.get();
376 case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
379 case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
380 mb += (val -
'a') + 10;
382 case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
383 mb += (val -
'A') + 10;
386 throw std::runtime_error(
"invalid unicode escape detected at position " + std::to_string(input.position() + 1));
393 output +=
static_cast<char>(mb);
394 }
else if (mb <= 2047) {
395 unsigned char left = (mb >> 6) | 0b11000000;
396 output += *(
reinterpret_cast<char*
>(&left));
397 unsigned char right = (mb & 0b00111111) | 0b10000000;
398 output += *(
reinterpret_cast<char*
>(&right));
400 unsigned char left = (mb >> 12) | 0b11100000;
401 output += *(
reinterpret_cast<char*
>(&left));
402 unsigned char middle = ((mb >> 6) & 0b00111111) | 0b10000000;
403 output += *(
reinterpret_cast<char*
>(&middle));
404 unsigned char right = (mb & 0b00111111) | 0b10000000;
405 output += *(
reinterpret_cast<char*
>(&right));
410 throw std::runtime_error(
"unrecognized escape '\\" + std::string(1, next2) +
"'");
415 case (
char) 0:
case (
char) 1:
case (
char) 2:
case (
char) 3:
case (
char) 4:
case (
char) 5:
case (
char) 6:
case (
char) 7:
case (
char) 8:
case (
char) 9:
416 case (
char)10:
case (
char)11:
case (
char)12:
case (
char)13:
case (
char)14:
case (
char)15:
case (
char)16:
case (
char)17:
case (
char)18:
case (
char)19:
417 case (
char)20:
case (
char)21:
case (
char)22:
case (
char)23:
case (
char)24:
case (
char)25:
case (
char)26:
case (
char)27:
case (
char)28:
case (
char)29:
418 case (
char)30:
case (
char)31:
420 throw std::runtime_error(
"string contains ASCII control character at position " + std::to_string(input.position() + 1));
427 if (!input.advance()) {
428 throw std::runtime_error(
"unterminated string at position " + std::to_string(start));
435template<
bool as_
string_,
class Input_>
436typename std::conditional<as_string_, std::string, double>::type extract_number(Input_& input) {
437 unsigned long long start = input.position() + 1;
439 if constexpr(as_string_) {
440 return std::string(
"");
442 return static_cast<double>(0);
445 bool in_fraction =
false;
446 bool in_exponent =
false;
448 auto add_string_value = [&](
char x) ->
void {
449 if constexpr(as_string_) {
455 char lead = input.get();
456 add_string_value(lead);
458 if (!input.advance()) {
462 auto after_zero = input.get();
463 switch (after_zero) {
465 add_string_value(after_zero);
469 add_string_value(after_zero);
472 case ',':
case ']':
case '}':
case ' ':
case '\r':
case '\n':
case '\t':
475 throw std::runtime_error(
"invalid number starting with 0 at position " + std::to_string(start));
479 if constexpr(!as_string_) {
483 while (input.advance()) {
484 char val = input.get();
487 add_string_value(val);
491 add_string_value(val);
494 case ',':
case ']':
case '}':
case ' ':
case '\r':
case '\n':
case '\t':
496 case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
497 if constexpr(as_string_) {
505 throw std::runtime_error(
"invalid number containing '" + std::string(1, val) +
"' at position " + std::to_string(start));
513 if (!input.advance()) {
514 throw std::runtime_error(
"invalid number with trailing '.' at position " + std::to_string(start));
517 char val = input.get();
518 if (!is_digit(val)) {
519 throw std::runtime_error(
"'.' must be followed by at least one digit at position " + std::to_string(start));
522 double fractional = 10;
523 if constexpr(as_string_) {
526 value += (val -
'0') / fractional;
529 while (input.advance()) {
530 char val = input.get();
534 add_string_value(val);
536 case ',':
case ']':
case '}':
case ' ':
case '\r':
case '\n':
case '\t':
538 case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
539 if constexpr(as_string_) {
543 value += (val -
'0') / fractional;
547 throw std::runtime_error(
"invalid number containing '" + std::string(1, val) +
"' at position " + std::to_string(start));
556 bool negative_exponent =
false;
558 if (!input.advance()) {
559 throw std::runtime_error(
"invalid number with trailing 'e/E' at position " + std::to_string(start));
562 char val = input.get();
563 if (!is_digit(val)) {
565 negative_exponent =
true;
566 add_string_value(val);
567 }
else if (val !=
'+') {
568 throw std::runtime_error(
"'e/E' should be followed by a sign or digit in number at position " + std::to_string(start));
571 if (!input.advance()) {
572 throw std::runtime_error(
"invalid number with trailing exponent sign at position " + std::to_string(start));
575 if (!is_digit(val)) {
576 throw std::runtime_error(
"exponent sign must be followed by at least one digit in number at position " + std::to_string(start));
580 if constexpr(as_string_) {
583 exponent += (val -
'0');
586 while (input.advance()) {
587 char val = input.get();
589 case ',':
case ']':
case '}':
case ' ':
case '\r':
case '\n':
case '\t':
591 case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
592 if constexpr(as_string_) {
596 exponent += (val -
'0');
600 throw std::runtime_error(
"invalid number containing '" + std::string(1, val) +
"' at position " + std::to_string(start));
605 if constexpr(!as_string_) {
607 if (negative_exponent) {
610 value *= std::pow(10.0, exponent);
619struct FakeProvisioner {
622 virtual Type type()
const = 0;
623 virtual ~FakeBase() {}
625 typedef FakeBase Base;
627 class FakeBoolean final :
public FakeBase {
629 Type type()
const {
return BOOLEAN; }
631 static FakeBoolean* new_boolean(
bool) {
632 return new FakeBoolean;
635 class FakeNumber final :
public FakeBase {
637 Type type()
const {
return NUMBER; }
639 static FakeNumber* new_number(
double) {
640 return new FakeNumber;
643 class FakeNumberAsString final :
public FakeBase {
645 Type type()
const {
return NUMBER_AS_STRING; }
647 static FakeNumberAsString* new_number_as_string(std::string) {
648 return new FakeNumberAsString;
651 class FakeString final :
public FakeBase {
653 Type type()
const {
return STRING; }
655 static FakeString* new_string(std::string) {
656 return new FakeString;
659 class FakeNothing final :
public FakeBase {
661 Type type()
const {
return NOTHING; }
663 static FakeNothing* new_nothing() {
664 return new FakeNothing;
667 class FakeArray final :
public FakeBase {
669 Type type()
const {
return ARRAY; }
671 static FakeArray* new_array(std::vector<std::shared_ptr<FakeBase> >) {
672 return new FakeArray;
675 class FakeObject final :
public FakeBase {
677 Type type()
const {
return OBJECT; }
679 static FakeObject* new_object(std::unordered_map<std::string, std::shared_ptr<FakeBase> >) {
680 return new FakeObject;
684template<
class Provisioner_,
class Input_>
685std::shared_ptr<typename Provisioner_::Base> parse_internal(Input_& input,
const ParseOptions& options) {
686 if (!check_and_chomp(input)) {
687 throw std::runtime_error(
"invalid JSON with no contents");
694 std::vector<Type> stack;
695 typedef std::vector<std::shared_ptr<typename Provisioner_::Base> > ArrayContents;
696 std::vector<ArrayContents> array_stack;
697 struct ObjectContents {
698 ObjectContents() =
default;
699 ObjectContents(std::string key) : key(std::move(key)) {}
700 std::unordered_map<std::string, std::shared_ptr<typename Provisioner_::Base> > mapping;
703 std::vector<ObjectContents> object_stack;
705 unsigned long long start = input.position() + 1;
706 auto extract_object_key = [&]() -> std::string {
707 char next = input.get();
709 throw std::runtime_error(
"expected a string as the object key at position " + std::to_string(input.position() + 1));
711 auto key = extract_string(input);
712 if (!check_and_chomp(input)) {
713 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
715 if (input.get() !=
':') {
716 throw std::runtime_error(
"expected ':' to separate keys and values at position " + std::to_string(input.position() + 1));
718 if (!advance_and_chomp(input)) {
719 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
724 std::shared_ptr<typename Provisioner_::Base> output;
726 const char current = input.get();
729 if (!is_expected_string(input,
"true", 4)) {
730 throw std::runtime_error(
"expected a 'true' string at position " + std::to_string(start));
732 output.reset(Provisioner_::new_boolean(
true));
736 if (!is_expected_string(input,
"false", 5)) {
737 throw std::runtime_error(
"expected a 'false' string at position " + std::to_string(start));
739 output.reset(Provisioner_::new_boolean(
false));
743 if (!is_expected_string(input,
"null", 4)) {
744 throw std::runtime_error(
"expected a 'null' string at position " + std::to_string(start));
746 output.reset(Provisioner_::new_nothing());
750 output.reset(Provisioner_::new_string(extract_string(input)));
754 if (!advance_and_chomp(input)) {
755 throw std::runtime_error(
"unterminated array starting at position " + std::to_string(start));
757 if (input.get() !=
']') {
758 stack.push_back(ARRAY);
759 array_stack.emplace_back();
763 output.reset(Provisioner_::new_array(std::vector<std::shared_ptr<typename Provisioner_::Base> >{}));
767 if (!advance_and_chomp(input)) {
768 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
770 if (input.get() !=
'}') {
771 stack.push_back(OBJECT);
772 object_stack.emplace_back(extract_object_key());
776 output.reset(Provisioner_::new_object(std::unordered_map<std::string, std::shared_ptr<typename Provisioner_::Base> >{}));
780 if (!input.advance()) {
781 throw std::runtime_error(
"incomplete number starting at position " + std::to_string(start));
783 if (!is_digit(input.get())) {
784 throw std::runtime_error(
"invalid number starting at position " + std::to_string(start));
786 if (options.number_as_string) {
787 output.reset(Provisioner_::new_number_as_string(
"-" + extract_number<true>(input)));
789 output.reset(Provisioner_::new_number(-extract_number<false>(input)));
793 case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
794 if (options.number_as_string) {
795 output.reset(Provisioner_::new_number_as_string(extract_number<true>(input)));
797 output.reset(Provisioner_::new_number(extract_number<false>(input)));
802 throw std::runtime_error(std::string(
"unknown type starting with '") + std::string(1, current) +
"' at position " + std::to_string(start));
810 if (stack.back() == ARRAY) {
811 auto& contents = array_stack.back();
812 contents.emplace_back(std::move(output));
814 if (!check_and_chomp(input)) {
815 throw std::runtime_error(
"unterminated array starting at position " + std::to_string(start));
818 char next = input.get();
820 if (!advance_and_chomp(input)) {
821 throw std::runtime_error(
"unterminated array starting at position " + std::to_string(start));
826 throw std::runtime_error(
"unknown character '" + std::string(1, next) +
"' in array at position " + std::to_string(input.position() + 1));
830 output.reset(Provisioner_::new_array(std::move(contents)));
832 array_stack.pop_back();
835 auto& mapping = object_stack.back().mapping;
836 auto& key = object_stack.back().key;
837 if (mapping.find(key) != mapping.end()) {
838 throw std::runtime_error(
"detected duplicate keys in the object at position " + std::to_string(input.position() + 1));
840 mapping[std::move(key)] = std::move(output);
842 if (!check_and_chomp(input)) {
843 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
846 char next = input.get();
848 if (!advance_and_chomp(input)) {
849 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
851 key = extract_object_key();
855 throw std::runtime_error(
"unknown character '" + std::string(1, next) +
"' in array at position " + std::to_string(input.position() + 1));
859 output.reset(Provisioner_::new_object(std::move(mapping)));
861 object_stack.pop_back();
867 if (check_and_chomp(input)) {
868 throw std::runtime_error(
"invalid JSON with trailing non-space characters at position " + std::to_string(input.position() + 1));
915 return new String(std::move(x));
930 return new Array(std::move(x));
938 return new Object(std::move(x));
945template<
typename Input_>
946auto setup_buffered_reader(Input_& input,
const ParseOptions& options) {
947 std::unique_ptr<byteme::BufferedReader<char> > ptr;
948 if (options.parallel) {
976template<
class Provisioner_ = DefaultProvisioner,
class Input_ =
byteme::Reader>
977std::shared_ptr<typename DefaultProvisioner::Base>
parse(Input_& input,
const ParseOptions& options) {
978 auto iptr = setup_buffered_reader(input, options);
979 return parse_internal<Provisioner_>(*iptr, options);
994template<
class Input_ =
byteme::Reader>
996 auto iptr = setup_buffered_reader(input, options);
997 auto ptr = parse_internal<FakeProvisioner>(*iptr, options);
1011template<
class Provisioner_ = DefaultProvisioner>
1040template<
class Provisioner_ = DefaultProvisioner>
1043 return parse(input, options);
1064typedef ParseOptions FileReadOptions;
1066template<
class Provisioner_ = DefaultProvisioner,
class Input_>
1067std::shared_ptr<typename DefaultProvisioner::Base>
parse(Input_& input) {
1071template<
class Input_>
1076template<
class Provisioner_ = DefaultProvisioner>
1077inline std::shared_ptr<typename Provisioner_::Base>
parse_string(
const char* ptr, std::size_t len) {
JSON array.
Definition millijson.hpp:186
Type type() const
Definition millijson.hpp:193
std::vector< std::shared_ptr< Base > > & value()
Definition millijson.hpp:206
const std::vector< std::shared_ptr< Base > > & value() const
Definition millijson.hpp:199
Array(std::vector< std::shared_ptr< Base > > x)
Definition millijson.hpp:191
Virtual base class for all JSON types.
Definition millijson.hpp:46
virtual Type type() const =0
JSON boolean.
Definition millijson.hpp:151
Boolean(bool x)
Definition millijson.hpp:156
Type type() const
Definition millijson.hpp:158
const bool & value() const
Definition millijson.hpp:164
bool & value()
Definition millijson.hpp:169
JSON null.
Definition millijson.hpp:178
Type type() const
Definition millijson.hpp:180
JSON number as a string.
Definition millijson.hpp:97
const std::string & value() const
Definition millijson.hpp:110
Type type() const
Definition millijson.hpp:104
NumberAsString(std::string x)
Definition millijson.hpp:102
std::string & value()
Definition millijson.hpp:115
JSON number.
Definition millijson.hpp:70
double & value()
Definition millijson.hpp:88
Type type() const
Definition millijson.hpp:77
const double & value() const
Definition millijson.hpp:83
Number(double x)
Definition millijson.hpp:75
JSON object.
Definition millijson.hpp:217
const std::unordered_map< std::string, std::shared_ptr< Base > > & value() const
Definition millijson.hpp:230
std::unordered_map< std::string, std::shared_ptr< Base > > & value()
Definition millijson.hpp:237
Object(std::unordered_map< std::string, std::shared_ptr< Base > > x)
Definition millijson.hpp:222
Type type() const
Definition millijson.hpp:224
JSON string.
Definition millijson.hpp:124
const std::string & value() const
Definition millijson.hpp:137
std::string & value()
Definition millijson.hpp:142
Type type() const
Definition millijson.hpp:131
String(std::string x)
Definition millijson.hpp:129
A lightweight header-only JSON parser.
Type validate_string(const char *ptr, std::size_t len, const ParseOptions &options)
Definition millijson.hpp:1027
std::shared_ptr< typename DefaultProvisioner::Base > parse(Input_ &input, const ParseOptions &options)
Definition millijson.hpp:977
std::shared_ptr< Base > parse_file(const char *path, const ParseOptions &options)
Definition millijson.hpp:1041
Type validate(Input_ &input, const ParseOptions &options)
Definition millijson.hpp:995
std::shared_ptr< typename Provisioner_::Base > parse_string(const char *ptr, std::size_t len, const ParseOptions &options)
Definition millijson.hpp:1012
Type validate_file(const char *path, const ParseOptions &options)
Definition millijson.hpp:1055
Type
Definition millijson.hpp:33
constexpr Dest_ cap(Value_ x)
Default methods to provision representations of JSON types.
Definition millijson.hpp:879
static Array * new_array(std::vector< std::shared_ptr< Base > > x)
Definition millijson.hpp:929
static NumberAsString * new_number_as_string(std::string x)
Definition millijson.hpp:906
static Number * new_number(double x)
Definition millijson.hpp:898
static Object * new_object(std::unordered_map< std::string, std::shared_ptr< Base > > x)
Definition millijson.hpp:937
static Nothing * new_nothing()
Definition millijson.hpp:921
static Boolean * new_boolean(bool x)
Definition millijson.hpp:890
::millijson::Base Base
Definition millijson.hpp:884
static String * new_string(std::string x)
Definition millijson.hpp:914
Options for parse().
Definition millijson.hpp:248
bool number_as_string
Definition millijson.hpp:254
bool parallel
Definition millijson.hpp:266
std::size_t buffer_size
Definition millijson.hpp:260