1#ifndef MILLIJSON_MILLIJSON_HPP
2#define MILLIJSON_MILLIJSON_HPP
11#include <unordered_map>
12#include <unordered_set>
57 Base& operator=(
const Base&) =
default;
80 const double&
value()
const {
return my_value; }
85 double&
value() {
return my_value; }
107 const std::string&
value()
const {
return my_value; }
112 std::string&
value() {
return my_value; }
115 std::string my_value;
126 String(std::string x) : my_value(std::move(x)) {}
134 const std::string&
value()
const {
return my_value; }
139 std::string&
value() {
return my_value; }
142 std::string my_value;
161 const bool&
value()
const {
return my_value; }
188 Array(std::vector<std::shared_ptr<Base> > x) : my_value(std::move(x)) {}
196 const std::vector<std::shared_ptr<Base> >&
value()
const {
203 std::vector<std::shared_ptr<Base> >&
value() {
208 std::vector<std::shared_ptr<Base> > my_value;
219 Object(std::unordered_map<std::string, std::shared_ptr<Base> > x) : my_value(std::move(x)) {}
227 const std::unordered_map<std::string, std::shared_ptr<Base> >&
value()
const {
234 std::unordered_map<std::string, std::shared_ptr<Base> >&
value() {
239 std::unordered_map<std::string, std::shared_ptr<Base> > my_value;
259template<
class Input_>
260bool raw_chomp(Input_& input,
bool ok) {
262 switch(input.get()) {
264 case ' ':
case '\n':
case '\r':
case '\t':
269 ok = input.advance();
274template<
class Input_>
275bool check_and_chomp(Input_& input) {
276 bool ok = input.valid();
277 return raw_chomp(input, ok);
280template<
class Input_>
281bool advance_and_chomp(Input_& input) {
282 bool ok = input.advance();
283 return raw_chomp(input, ok);
286inline bool is_digit(
char val) {
287 return val >=
'0' && val <=
'9';
290template<
class Input_>
291bool is_expected_string(Input_& input,
const char* ptr, std::size_t len) {
293 for (std::size_t i = 1; i < len; ++i) {
297 if (!input.advance()) {
300 if (input.get() != ptr[i]) {
308template<
class Input_>
309std::string extract_string(Input_& input) {
310 unsigned long long start = input.position() + 1;
315 char next = input.get();
322 if (!input.advance()) {
323 throw std::runtime_error(
"unterminated string at position " + std::to_string(start));
325 char next2 = input.get();
353 unsigned short mb = 0;
354 for (
int i = 0; i < 4; ++i) {
355 if (!input.advance()){
356 throw std::runtime_error(
"unterminated string at position " + std::to_string(start));
359 char val = input.get();
361 case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
364 case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
365 mb += (val -
'a') + 10;
367 case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
368 mb += (val -
'A') + 10;
371 throw std::runtime_error(
"invalid unicode escape detected at position " + std::to_string(input.position() + 1));
378 output +=
static_cast<char>(mb);
379 }
else if (mb <= 2047) {
380 unsigned char left = (mb >> 6) | 0b11000000;
381 output += *(
reinterpret_cast<char*
>(&left));
382 unsigned char right = (mb & 0b00111111) | 0b10000000;
383 output += *(
reinterpret_cast<char*
>(&right));
385 unsigned char left = (mb >> 12) | 0b11100000;
386 output += *(
reinterpret_cast<char*
>(&left));
387 unsigned char middle = ((mb >> 6) & 0b00111111) | 0b10000000;
388 output += *(
reinterpret_cast<char*
>(&middle));
389 unsigned char right = (mb & 0b00111111) | 0b10000000;
390 output += *(
reinterpret_cast<char*
>(&right));
395 throw std::runtime_error(
"unrecognized escape '\\" + std::string(1, next2) +
"'");
400 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:
401 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:
402 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:
403 case (
char)30:
case (
char)31:
405 throw std::runtime_error(
"string contains ASCII control character at position " + std::to_string(input.position() + 1));
412 if (!input.advance()) {
413 throw std::runtime_error(
"unterminated string at position " + std::to_string(start));
420template<
bool as_
string_,
class Input_>
421typename std::conditional<as_string_, std::string, double>::type extract_number(Input_& input) {
422 unsigned long long start = input.position() + 1;
424 if constexpr(as_string_) {
425 return std::string(
"");
427 return static_cast<double>(0);
430 bool in_fraction =
false;
431 bool in_exponent =
false;
433 auto add_string_value = [&](
char x) ->
void {
434 if constexpr(as_string_) {
440 char lead = input.get();
441 add_string_value(lead);
443 if (!input.advance()) {
447 auto after_zero = input.get();
448 switch (after_zero) {
450 add_string_value(after_zero);
454 add_string_value(after_zero);
457 case ',':
case ']':
case '}':
case ' ':
case '\r':
case '\n':
case '\t':
460 throw std::runtime_error(
"invalid number starting with 0 at position " + std::to_string(start));
464 if constexpr(!as_string_) {
468 while (input.advance()) {
469 char val = input.get();
472 add_string_value(val);
476 add_string_value(val);
479 case ',':
case ']':
case '}':
case ' ':
case '\r':
case '\n':
case '\t':
481 case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
482 if constexpr(as_string_) {
490 throw std::runtime_error(
"invalid number containing '" + std::string(1, val) +
"' at position " + std::to_string(start));
498 if (!input.advance()) {
499 throw std::runtime_error(
"invalid number with trailing '.' at position " + std::to_string(start));
502 char val = input.get();
503 if (!is_digit(val)) {
504 throw std::runtime_error(
"'.' must be followed by at least one digit at position " + std::to_string(start));
507 double fractional = 10;
508 if constexpr(as_string_) {
511 value += (val -
'0') / fractional;
514 while (input.advance()) {
515 char val = input.get();
519 add_string_value(val);
521 case ',':
case ']':
case '}':
case ' ':
case '\r':
case '\n':
case '\t':
523 case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
524 if constexpr(as_string_) {
528 value += (val -
'0') / fractional;
532 throw std::runtime_error(
"invalid number containing '" + std::string(1, val) +
"' at position " + std::to_string(start));
541 bool negative_exponent =
false;
543 if (!input.advance()) {
544 throw std::runtime_error(
"invalid number with trailing 'e/E' at position " + std::to_string(start));
547 char val = input.get();
548 if (!is_digit(val)) {
550 negative_exponent =
true;
551 add_string_value(val);
552 }
else if (val !=
'+') {
553 throw std::runtime_error(
"'e/E' should be followed by a sign or digit in number at position " + std::to_string(start));
556 if (!input.advance()) {
557 throw std::runtime_error(
"invalid number with trailing exponent sign at position " + std::to_string(start));
560 if (!is_digit(val)) {
561 throw std::runtime_error(
"exponent sign must be followed by at least one digit in number at position " + std::to_string(start));
565 if constexpr(as_string_) {
568 exponent += (val -
'0');
571 while (input.advance()) {
572 char val = input.get();
574 case ',':
case ']':
case '}':
case ' ':
case '\r':
case '\n':
case '\t':
576 case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
577 if constexpr(as_string_) {
581 exponent += (val -
'0');
585 throw std::runtime_error(
"invalid number containing '" + std::string(1, val) +
"' at position " + std::to_string(start));
590 if constexpr(!as_string_) {
592 if (negative_exponent) {
595 value *= std::pow(10.0, exponent);
604struct FakeProvisioner {
607 virtual Type type()
const = 0;
608 virtual ~FakeBase() {}
610 typedef FakeBase Base;
612 class FakeBoolean final :
public FakeBase {
614 Type type()
const {
return BOOLEAN; }
616 static FakeBoolean* new_boolean(
bool) {
617 return new FakeBoolean;
620 class FakeNumber final :
public FakeBase {
622 Type type()
const {
return NUMBER; }
624 static FakeNumber* new_number(
double) {
625 return new FakeNumber;
628 class FakeNumberAsString final :
public FakeBase {
630 Type type()
const {
return NUMBER_AS_STRING; }
632 static FakeNumberAsString* new_number_as_string(std::string) {
633 return new FakeNumberAsString;
636 class FakeString final :
public FakeBase {
638 Type type()
const {
return STRING; }
640 static FakeString* new_string(std::string) {
641 return new FakeString;
644 class FakeNothing final :
public FakeBase {
646 Type type()
const {
return NOTHING; }
648 static FakeNothing* new_nothing() {
649 return new FakeNothing;
652 class FakeArray final :
public FakeBase {
654 Type type()
const {
return ARRAY; }
656 static FakeArray* new_array(std::vector<std::shared_ptr<FakeBase> >) {
657 return new FakeArray;
660 class FakeObject final :
public FakeBase {
662 Type type()
const {
return OBJECT; }
664 static FakeObject* new_object(std::unordered_map<std::string, std::shared_ptr<FakeBase> >) {
665 return new FakeObject;
669template<
class Provisioner_,
class Input_>
670std::shared_ptr<typename Provisioner_::Base> parse_internal(Input_& input,
const ParseOptions& options) {
671 if (!check_and_chomp(input)) {
672 throw std::runtime_error(
"invalid JSON with no contents");
679 std::vector<Type> stack;
680 typedef std::vector<std::shared_ptr<typename Provisioner_::Base> > ArrayContents;
681 std::vector<ArrayContents> array_stack;
682 struct ObjectContents {
683 ObjectContents() =
default;
684 ObjectContents(std::string key) : key(std::move(key)) {}
685 std::unordered_map<std::string, std::shared_ptr<typename Provisioner_::Base> > mapping;
688 std::vector<ObjectContents> object_stack;
690 unsigned long long start = input.position() + 1;
691 auto extract_object_key = [&]() -> std::string {
692 char next = input.get();
694 throw std::runtime_error(
"expected a string as the object key at position " + std::to_string(input.position() + 1));
696 auto key = extract_string(input);
697 if (!check_and_chomp(input)) {
698 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
700 if (input.get() !=
':') {
701 throw std::runtime_error(
"expected ':' to separate keys and values at position " + std::to_string(input.position() + 1));
703 if (!advance_and_chomp(input)) {
704 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
709 std::shared_ptr<typename Provisioner_::Base> output;
711 const char current = input.get();
714 if (!is_expected_string(input,
"true", 4)) {
715 throw std::runtime_error(
"expected a 'true' string at position " + std::to_string(start));
717 output.reset(Provisioner_::new_boolean(
true));
721 if (!is_expected_string(input,
"false", 5)) {
722 throw std::runtime_error(
"expected a 'false' string at position " + std::to_string(start));
724 output.reset(Provisioner_::new_boolean(
false));
728 if (!is_expected_string(input,
"null", 4)) {
729 throw std::runtime_error(
"expected a 'null' string at position " + std::to_string(start));
731 output.reset(Provisioner_::new_nothing());
735 output.reset(Provisioner_::new_string(extract_string(input)));
739 if (!advance_and_chomp(input)) {
740 throw std::runtime_error(
"unterminated array starting at position " + std::to_string(start));
742 if (input.get() !=
']') {
743 stack.push_back(ARRAY);
744 array_stack.emplace_back();
748 output.reset(Provisioner_::new_array(std::vector<std::shared_ptr<typename Provisioner_::Base> >{}));
752 if (!advance_and_chomp(input)) {
753 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
755 if (input.get() !=
'}') {
756 stack.push_back(OBJECT);
757 object_stack.emplace_back(extract_object_key());
761 output.reset(Provisioner_::new_object(std::unordered_map<std::string, std::shared_ptr<typename Provisioner_::Base> >{}));
765 if (!input.advance()) {
766 throw std::runtime_error(
"incomplete number starting at position " + std::to_string(start));
768 if (!is_digit(input.get())) {
769 throw std::runtime_error(
"invalid number starting at position " + std::to_string(start));
771 if (options.number_as_string) {
772 output.reset(Provisioner_::new_number_as_string(
"-" + extract_number<true>(input)));
774 output.reset(Provisioner_::new_number(-extract_number<false>(input)));
778 case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
779 if (options.number_as_string) {
780 output.reset(Provisioner_::new_number_as_string(extract_number<true>(input)));
782 output.reset(Provisioner_::new_number(extract_number<false>(input)));
787 throw std::runtime_error(std::string(
"unknown type starting with '") + std::string(1, current) +
"' at position " + std::to_string(start));
795 if (stack.back() == ARRAY) {
796 auto& contents = array_stack.back();
797 contents.emplace_back(std::move(output));
799 if (!check_and_chomp(input)) {
800 throw std::runtime_error(
"unterminated array starting at position " + std::to_string(start));
803 char next = input.get();
805 if (!advance_and_chomp(input)) {
806 throw std::runtime_error(
"unterminated array starting at position " + std::to_string(start));
811 throw std::runtime_error(
"unknown character '" + std::string(1, next) +
"' in array at position " + std::to_string(input.position() + 1));
815 output.reset(Provisioner_::new_array(std::move(contents)));
817 array_stack.pop_back();
820 auto& mapping = object_stack.back().mapping;
821 auto& key = object_stack.back().key;
822 if (mapping.find(key) != mapping.end()) {
823 throw std::runtime_error(
"detected duplicate keys in the object at position " + std::to_string(input.position() + 1));
825 mapping[std::move(key)] = std::move(output);
827 if (!check_and_chomp(input)) {
828 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
831 char next = input.get();
833 if (!advance_and_chomp(input)) {
834 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
836 key = extract_object_key();
840 throw std::runtime_error(
"unknown character '" + std::string(1, next) +
"' in array at position " + std::to_string(input.position() + 1));
844 output.reset(Provisioner_::new_object(std::move(mapping)));
846 object_stack.pop_back();
852 if (check_and_chomp(input)) {
853 throw std::runtime_error(
"invalid JSON with trailing non-space characters at position " + std::to_string(input.position() + 1));
900 return new String(std::move(x));
915 return new Array(std::move(x));
923 return new Object(std::move(x));
949template<
class Provisioner_ = DefaultProvisioner,
class Input_>
950std::shared_ptr<typename DefaultProvisioner::Base>
parse(Input_& input,
const ParseOptions& options) {
951 return parse_internal<Provisioner_>(input, options);
966template<
class Input_>
968 auto ptr = parse_internal<FakeProvisioner>(input, options);
977 RawReader(
const char* ptr, std::size_t len) : my_ptr(ptr), my_len(len) {}
980 unsigned long long my_pos = 0;
986 return my_ptr[my_pos];
990 return my_pos < my_len;
998 unsigned long long position()
const {
1016template<
class Provisioner_ = DefaultProvisioner>
1018 RawReader input(ptr, len);
1033 RawReader input(ptr, len);
1042 FileReader(
const char* path, std::size_t buffer_size) : my_handle(std::fopen(path,
"rb")), my_buffer(check_buffer_size(buffer_size)) {
1044 throw std::runtime_error(
"failed to open file at '" + std::string(path) +
"'");
1050 std::fclose(my_handle);
1054 typedef typename std::vector<char>::size_type Size;
1056 static Size check_buffer_size(std::size_t buffer_size) {
1059 constexpr Size max_size = std::numeric_limits<Size>::max();
1060 if (buffer_size >= max_size) {
1068 std::FILE* my_handle;
1069 std::vector<char> my_buffer;
1070 Size my_available = 0;
1072 unsigned long long my_overall = 0;
1073 bool my_finished =
false;
1077 return my_buffer[my_index];
1080 bool valid()
const {
1081 return my_index < my_available;
1086 if (my_index < my_available) {
1091 my_overall += my_available;
1102 my_available = std::fread(my_buffer.data(),
sizeof(
char), my_buffer.size(), my_handle);
1103 if (my_available == my_buffer.size()) {
1107 if (std::feof(my_handle)) {
1110 throw std::runtime_error(
"failed to read file (error " + std::to_string(std::ferror(my_handle)) +
")");
1114 unsigned long long position()
const {
1115 return my_overall + my_index;
1145template<
class Provisioner_ = DefaultProvisioner>
1169template<
class Provisioner_ = DefaultProvisioner,
class Input_>
1170std::shared_ptr<typename DefaultProvisioner::Base>
parse(Input_& input) {
1174template<
class Input_>
1179template<
class Provisioner_ = DefaultProvisioner>
1180inline std::shared_ptr<typename Provisioner_::Base>
parse_string(
const char* ptr, std::size_t len) {
1185 RawReader input(ptr, len);
JSON array.
Definition millijson.hpp:183
Type type() const
Definition millijson.hpp:190
std::vector< std::shared_ptr< Base > > & value()
Definition millijson.hpp:203
const std::vector< std::shared_ptr< Base > > & value() const
Definition millijson.hpp:196
Array(std::vector< std::shared_ptr< Base > > x)
Definition millijson.hpp:188
Virtual base class for all JSON types.
Definition millijson.hpp:43
virtual Type type() const =0
JSON boolean.
Definition millijson.hpp:148
Boolean(bool x)
Definition millijson.hpp:153
Type type() const
Definition millijson.hpp:155
const bool & value() const
Definition millijson.hpp:161
bool & value()
Definition millijson.hpp:166
JSON null.
Definition millijson.hpp:175
Type type() const
Definition millijson.hpp:177
JSON number as a string.
Definition millijson.hpp:94
const std::string & value() const
Definition millijson.hpp:107
Type type() const
Definition millijson.hpp:101
NumberAsString(std::string x)
Definition millijson.hpp:99
std::string & value()
Definition millijson.hpp:112
JSON number.
Definition millijson.hpp:67
double & value()
Definition millijson.hpp:85
Type type() const
Definition millijson.hpp:74
const double & value() const
Definition millijson.hpp:80
Number(double x)
Definition millijson.hpp:72
JSON object.
Definition millijson.hpp:214
const std::unordered_map< std::string, std::shared_ptr< Base > > & value() const
Definition millijson.hpp:227
std::unordered_map< std::string, std::shared_ptr< Base > > & value()
Definition millijson.hpp:234
Object(std::unordered_map< std::string, std::shared_ptr< Base > > x)
Definition millijson.hpp:219
Type type() const
Definition millijson.hpp:221
JSON string.
Definition millijson.hpp:121
const std::string & value() const
Definition millijson.hpp:134
std::string & value()
Definition millijson.hpp:139
Type type() const
Definition millijson.hpp:128
String(std::string x)
Definition millijson.hpp:126
A lightweight header-only JSON parser.
Type validate_file(const char *path, const FileReadOptions &options)
Definition millijson.hpp:1160
Type validate_string(const char *ptr, std::size_t len, const ParseOptions &options)
Definition millijson.hpp:1032
std::shared_ptr< typename DefaultProvisioner::Base > parse(Input_ &input, const ParseOptions &options)
Definition millijson.hpp:950
Type validate(Input_ &input, const ParseOptions &options)
Definition millijson.hpp:967
std::shared_ptr< typename Provisioner_::Base > parse_string(const char *ptr, std::size_t len, const ParseOptions &options)
Definition millijson.hpp:1017
Type
Definition millijson.hpp:30
std::shared_ptr< Base > parse_file(const char *path, const FileReadOptions &options)
Definition millijson.hpp:1146
Default methods to provision representations of JSON types.
Definition millijson.hpp:864
static Array * new_array(std::vector< std::shared_ptr< Base > > x)
Definition millijson.hpp:914
static NumberAsString * new_number_as_string(std::string x)
Definition millijson.hpp:891
static Number * new_number(double x)
Definition millijson.hpp:883
static Object * new_object(std::unordered_map< std::string, std::shared_ptr< Base > > x)
Definition millijson.hpp:922
static Nothing * new_nothing()
Definition millijson.hpp:906
static Boolean * new_boolean(bool x)
Definition millijson.hpp:875
::millijson::Base Base
Definition millijson.hpp:869
static String * new_string(std::string x)
Definition millijson.hpp:899
Options for parse_file() and validate_file().
Definition millijson.hpp:1125
std::size_t buffer_size
Definition millijson.hpp:1129
ParseOptions parse_options
Definition millijson.hpp:1134
Options for parse().
Definition millijson.hpp:245
bool number_as_string
Definition millijson.hpp:251