1#ifndef MILLIJSON_MILLIJSON_HPP
2#define MILLIJSON_MILLIJSON_HPP
11#include <unordered_map>
12#include <unordered_set>
74 const std::unordered_map<std::string, std::shared_ptr<Base> >&
get_object()
const;
79 const std::vector<std::shared_ptr<Base> >&
get_array()
const;
158 std::vector<std::shared_ptr<Base> >
values;
163 void add(std::shared_ptr<Base> value) {
164 values.push_back(std::move(value));
178 std::unordered_map<std::string, std::shared_ptr<Base> >
values;
184 bool has(
const std::string& key)
const {
192 void add(std::string key, std::shared_ptr<Base> value) {
193 values[std::move(key)] = std::move(value);
202 return static_cast<const Number*
>(
this)->value;
206 return static_cast<const String*
>(
this)->value;
210 return static_cast<const Boolean*
>(
this)->value;
213inline const std::unordered_map<std::string, std::shared_ptr<Base> >&
Base::get_object()
const {
214 return static_cast<const Object*
>(
this)->values;
217inline const std::vector<std::shared_ptr<Base> >&
Base::get_array()
const {
218 return static_cast<const Array*
>(
this)->values;
221inline bool isspace(
char x) {
223 return x ==
' ' || x ==
'\n' || x ==
'\r' || x ==
'\t';
227void chomp(Input& input) {
228 bool ok = input.valid();
229 while (ok && isspace(input.get())) {
230 ok = input.advance();
236bool is_expected_string(Input& input,
const std::string& expected) {
237 for (
auto x : expected) {
238 if (!input.valid()) {
241 if (input.get() != x) {
250std::string extract_string(Input& input) {
251 size_t start = input.position() + 1;
256 char next = input.get();
262 if (!input.advance()) {
263 throw std::runtime_error(
"unterminated string at position " + std::to_string(start));
265 char next2 = input.get();
293 unsigned short mb = 0;
294 for (
size_t i = 0; i < 4; ++i) {
295 if (!input.advance()){
296 throw std::runtime_error(
"unterminated string at position " + std::to_string(start));
299 char val = input.get();
301 case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
304 case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
305 mb += (val -
'a') + 10;
307 case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
308 mb += (val -
'A') + 10;
311 throw std::runtime_error(
"invalid unicode escape detected at position " + std::to_string(input.position() + 1));
318 output +=
static_cast<char>(mb);
319 }
else if (mb <= 2047) {
320 unsigned char left = (mb >> 6) | 0b11000000;
321 output += *(
reinterpret_cast<char*
>(&left));
322 unsigned char right = (mb & 0b00111111) | 0b10000000;
323 output += *(
reinterpret_cast<char*
>(&right));
325 unsigned char left = (mb >> 12) | 0b11100000;
326 output += *(
reinterpret_cast<char*
>(&left));
327 unsigned char middle = ((mb >> 6) & 0b00111111) | 0b10000000;
328 output += *(
reinterpret_cast<char*
>(&middle));
329 unsigned char right = (mb & 0b00111111) | 0b10000000;
330 output += *(
reinterpret_cast<char*
>(&right));
335 throw std::runtime_error(
"unrecognized escape '\\" + std::string(1, next2) +
"'");
339 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:
340 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:
341 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:
342 case (
char)30:
case (
char)31:
343 throw std::runtime_error(
"string contains ASCII control character at position " + std::to_string(input.position() + 1));
349 if (!input.advance()) {
350 throw std::runtime_error(
"unterminated string at position " + std::to_string(start));
358double extract_number(Input& input) {
359 size_t start = input.position() + 1;
361 double fractional = 10;
363 bool negative_exponent =
false;
365 auto is_terminator = [](
char v) ->
bool {
366 return v ==
',' || v ==
']' || v ==
'}' || isspace(v);
369 bool in_fraction =
false;
370 bool in_exponent =
false;
373 char lead = input.get();
375 if (!input.advance()) {
379 char val = input.get();
382 }
else if (val ==
'e' || val ==
'E') {
384 }
else if (is_terminator(val)) {
387 throw std::runtime_error(
"invalid number starting with 0 at position " + std::to_string(start));
390 }
else if (std::isdigit(lead)) {
393 while (input.advance()) {
394 char val = input.get();
398 }
else if (val ==
'e' || val ==
'E') {
401 }
else if (is_terminator(val)) {
403 }
else if (!std::isdigit(val)) {
404 throw std::runtime_error(
"invalid number containing '" + std::string(1, val) +
"' at position " + std::to_string(start));
415 if (!input.advance()) {
416 throw std::runtime_error(
"invalid number with trailing '.' at position " + std::to_string(start));
419 char val = input.get();
420 if (!std::isdigit(val)) {
421 throw std::runtime_error(
"'.' must be followed by at least one digit at position " + std::to_string(start));
423 value += (val -
'0') / fractional;
425 while (input.advance()) {
426 char val = input.get();
427 if (val ==
'e' || val ==
'E') {
430 }
else if (is_terminator(val)) {
432 }
else if (!std::isdigit(val)) {
433 throw std::runtime_error(
"invalid number containing '" + std::string(1, val) +
"' at position " + std::to_string(start));
436 value += (val -
'0') / fractional;
441 if (!input.advance()) {
442 throw std::runtime_error(
"invalid number with trailing 'e/E' at position " + std::to_string(start));
445 char val = input.get();
446 if (!std::isdigit(val)) {
448 negative_exponent =
true;
449 }
else if (val !=
'+') {
450 throw std::runtime_error(
"'e/E' should be followed by a sign or digit in number at position " + std::to_string(start));
453 if (!input.advance()) {
454 throw std::runtime_error(
"invalid number with trailing exponent sign at position " + std::to_string(start));
457 if (!std::isdigit(val)) {
458 throw std::runtime_error(
"exponent sign must be followed by at least one digit in number at position " + std::to_string(start));
462 exponent += (val -
'0');
464 while (input.advance()) {
465 char val = input.get();
466 if (is_terminator(val)) {
468 }
else if (!std::isdigit(val)) {
469 throw std::runtime_error(
"invalid number containing '" + std::string(1, val) +
"' at position " + std::to_string(start));
472 exponent += (val -
'0');
476 if (negative_exponent) {
479 value *= std::pow(10.0, exponent);
486struct DefaultProvisioner {
489 static Boolean* new_boolean(
bool x) {
490 return new Boolean(x);
493 static Number* new_number(
double x) {
494 return new Number(x);
497 static String* new_string(std::string x) {
498 return new String(std::move(x));
501 static Nothing* new_nothing() {
505 static Array* new_array() {
509 static Object* new_object() {
514struct FakeProvisioner {
516 virtual Type type()
const = 0;
517 virtual ~FakeBase() {}
519 typedef FakeBase base;
521 struct FakeBoolean :
public FakeBase {
522 Type type()
const {
return BOOLEAN; }
524 static FakeBoolean* new_boolean(
bool) {
525 return new FakeBoolean;
528 struct FakeNumber :
public FakeBase {
529 Type type()
const {
return NUMBER; }
531 static FakeNumber* new_number(
double) {
532 return new FakeNumber;
535 struct FakeString :
public FakeBase {
536 Type type()
const {
return STRING; }
538 static FakeString* new_string(std::string) {
539 return new FakeString;
542 struct FakeNothing :
public FakeBase {
543 Type type()
const {
return NOTHING; }
545 static FakeNothing* new_nothing() {
546 return new FakeNothing;
549 struct FakeArray :
public FakeBase {
550 Type type()
const {
return ARRAY; }
551 void add(std::shared_ptr<FakeBase>) {}
553 static FakeArray* new_array() {
554 return new FakeArray;
557 struct FakeObject :
public FakeBase {
558 Type type()
const {
return OBJECT; }
559 std::unordered_set<std::string> keys;
560 bool has(
const std::string& key)
const {
561 return keys.find(key) != keys.end();
563 void add(std::string key, std::shared_ptr<FakeBase>) {
564 keys.insert(std::move(key));
567 static FakeObject* new_object() {
568 return new FakeObject;
572template<
class Provisioner,
class Input>
573std::shared_ptr<typename Provisioner::base> parse_thing(Input& input) {
574 std::shared_ptr<typename Provisioner::base> output;
576 size_t start = input.position() + 1;
577 const char current = input.get();
579 if (current ==
't') {
580 if (!is_expected_string(input,
"true")) {
581 throw std::runtime_error(
"expected a 'true' string at position " + std::to_string(start));
583 output.reset(Provisioner::new_boolean(
true));
585 }
else if (current ==
'f') {
586 if (!is_expected_string(input,
"false")) {
587 throw std::runtime_error(
"expected a 'false' string at position " + std::to_string(start));
589 output.reset(Provisioner::new_boolean(
false));
591 }
else if (current ==
'n') {
592 if (!is_expected_string(input,
"null")) {
593 throw std::runtime_error(
"expected a 'null' string at position " + std::to_string(start));
595 output.reset(Provisioner::new_nothing());
597 }
else if (current ==
'"') {
598 output.reset(Provisioner::new_string(extract_string(input)));
600 }
else if (current ==
'[') {
601 auto ptr = Provisioner::new_array();
606 if (!input.valid()) {
607 throw std::runtime_error(
"unterminated array starting at position " + std::to_string(start));
610 if (input.get() !=
']') {
612 ptr->add(parse_thing<Provisioner>(input));
615 if (!input.valid()) {
616 throw std::runtime_error(
"unterminated array starting at position " + std::to_string(start));
619 char next = input.get();
622 }
else if (next !=
',') {
623 throw std::runtime_error(
"unknown character '" + std::string(1, next) +
"' in array at position " + std::to_string(input.position() + 1));
628 if (!input.valid()) {
629 throw std::runtime_error(
"unterminated array starting at position " + std::to_string(start));
636 }
else if (current ==
'{') {
637 auto ptr = Provisioner::new_object();
642 if (!input.valid()) {
643 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
646 if (input.get() !=
'}') {
648 char next = input.get();
650 throw std::runtime_error(
"expected a string as the object key at position " + std::to_string(input.position() + 1));
652 auto key = extract_string(input);
654 throw std::runtime_error(
"detected duplicate keys in the object at position " + std::to_string(input.position() + 1));
658 if (!input.valid()) {
659 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
661 if (input.get() !=
':') {
662 throw std::runtime_error(
"expected ':' to separate keys and values at position " + std::to_string(input.position() + 1));
667 if (!input.valid()) {
668 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
670 ptr->add(std::move(key), parse_thing<Provisioner>(input));
673 if (!input.valid()) {
674 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
680 }
else if (next !=
',') {
681 throw std::runtime_error(
"unknown character '" + std::string(1, next) +
"' in array at position " + std::to_string(input.position() + 1));
686 if (!input.valid()) {
687 throw std::runtime_error(
"unterminated object starting at position " + std::to_string(start));
694 }
else if (current ==
'-') {
695 if (!input.advance()) {
696 throw std::runtime_error(
"incomplete number starting at position " + std::to_string(start));
698 output.reset(Provisioner::new_number(-extract_number(input)));
700 }
else if (std::isdigit(current)) {
701 output.reset(Provisioner::new_number(extract_number(input)));
704 throw std::runtime_error(std::string(
"unknown type starting with '") + std::string(1, current) +
"' at position " + std::to_string(start));
710template<
class Provisioner,
class Input>
711std::shared_ptr<typename Provisioner::base> parse_thing_with_chomp(Input& input) {
713 auto output = parse_thing<Provisioner>(input);
716 throw std::runtime_error(
"invalid json with trailing non-space characters at position " + std::to_string(input.position() + 1));
736std::shared_ptr<Base>
parse(Input& input) {
737 return parse_thing_with_chomp<DefaultProvisioner>(input);
750 auto ptr = parse_thing_with_chomp<FakeProvisioner>(input);
758 RawReader(
const char* p,
size_t n) : ptr_(p), len_(n) {}
776 size_t position()
const {
789inline std::shared_ptr<Base>
parse_string(
const char* ptr,
size_t len) {
790 RawReader input(ptr, len);
802 RawReader input(ptr, len);
810 FileReader(
const char* p,
size_t b) : handle(std::fopen(p,
"rb")), buffer(b) {
812 throw std::runtime_error(
"failed to open file at '" + std::string(p) +
"'");
822 std::vector<char> buffer;
823 size_t available = 0;
826 bool finished =
false;
829 return buffer[index];
833 return index < available;
838 if (index < available) {
843 overall += available;
854 available = std::fread(buffer.data(),
sizeof(
char), buffer.size(), handle);
855 if (available == buffer.size()) {
859 if (std::feof(handle)) {
862 throw std::runtime_error(
"failed to read file (error " + std::to_string(std::ferror(handle)) +
")");
866 size_t position()
const {
867 return overall + index;
879inline std::shared_ptr<Base>
parse_file(
const char* path,
size_t buffer_size = 65536) {
880 FileReader input(path, buffer_size);
892 FileReader input(path, buffer_size);
A lightweight header-only JSON parser.
std::shared_ptr< Base > parse(Input &input)
Definition millijson.hpp:736
Type validate(Input &input)
Definition millijson.hpp:749
std::shared_ptr< Base > parse_string(const char *ptr, size_t len)
Definition millijson.hpp:789
std::shared_ptr< Base > parse_file(const char *path, size_t buffer_size=65536)
Definition millijson.hpp:879
Type validate_file(const char *path, size_t buffer_size=65536)
Definition millijson.hpp:891
Type validate_string(const char *ptr, size_t len)
Definition millijson.hpp:801
Type
Definition millijson.hpp:30
JSON array.
Definition millijson.hpp:152
Type type() const
Definition millijson.hpp:153
void add(std::shared_ptr< Base > value)
Definition millijson.hpp:163
std::vector< std::shared_ptr< Base > > values
Definition millijson.hpp:158
Virtual base class for all JSON types.
Definition millijson.hpp:42
const std::string & get_string() const
const std::unordered_map< std::string, std::shared_ptr< Base > > & get_object() const
virtual Type type() const =0
const std::vector< std::shared_ptr< Base > > & get_array() const
double get_number() const
JSON boolean.
Definition millijson.hpp:125
bool value
Definition millijson.hpp:139
Type type() const
Definition millijson.hpp:134
JSON null.
Definition millijson.hpp:145
Type type() const
Definition millijson.hpp:146
JSON number.
Definition millijson.hpp:85
Type type() const
Definition millijson.hpp:94
double value
Definition millijson.hpp:99
JSON object.
Definition millijson.hpp:172
void add(std::string key, std::shared_ptr< Base > value)
Definition millijson.hpp:192
std::unordered_map< std::string, std::shared_ptr< Base > > values
Definition millijson.hpp:178
Type type() const
Definition millijson.hpp:173
bool has(const std::string &key) const
Definition millijson.hpp:184
JSON string.
Definition millijson.hpp:105
Type type() const
Definition millijson.hpp:114
std::string value
Definition millijson.hpp:119