ChaiScript / ChaiScript
1
// From github.com/nbsdx/SimpleJSON. 
2
// Released under the DWTFYW PL
3
//
4

5

6

7
#ifndef SIMPLEJSON_HPP
8
#define SIMPLEJSON_HPP
9

10

11
#include <cstdint>
12
#include <cmath>
13
#include <cctype>
14
#include <string>
15
#include <vector>
16
#include <map>
17
#include <type_traits>
18
#include <initializer_list>
19
#include <ostream>
20
#include <iostream>
21
#include <variant>
22
#include "../chaiscript_defines.hpp"
23
#include "quick_flat_map.hpp"
24

25
namespace json {
26

27
using std::enable_if;
28
using std::initializer_list;
29
using std::is_same;
30
using std::is_convertible;
31
using std::is_integral;
32
using std::is_floating_point;
33

34

35

36

37
class JSON
38
{
39
  public:
40
    enum class Class {
41
      Null = 0,
42
      Object,
43
      Array,
44
      String,
45
      Floating,
46
      Integral,
47
      Boolean
48
    };
49

50
  private:
51

52

53
    using Data = std::variant<std::nullptr_t, chaiscript::utility::QuickFlatMap<std::string, JSON>, std::vector<JSON>, std::string, double, std::int64_t, bool>;
54

55
    struct Internal
56
    {
57
      Internal(std::nullptr_t) : d(nullptr) { }
58 1
      Internal() : d(nullptr) { }
59 1
      Internal(Class c) : d(make_type(c)) { }
60 1
      template<typename T> Internal(T t) : d(std::move(t)) { }
61

62 1
      static Data make_type(Class c) {
63 1
        switch (c) {
64 0
          case Class::Null:     return nullptr;
65 1
          case Class::Object:   return chaiscript::utility::QuickFlatMap<std::string, JSON>{};
66 1
          case Class::Array:    return std::vector<JSON>{};
67 0
          case Class::String:   return std::string{};
68 0
          case Class::Floating: return double{};
69 0
          case Class::Integral: return std::int64_t{};
70 0
          case Class::Boolean:  return bool{};
71
        }
72 0
        throw std::runtime_error("unknown type");
73
      }
74

75 1
      void set_type(Class c) {
76 1
        if (type() != c) {
77 0
          d = make_type(c);
78
        }
79
      }
80

81 1
      Class type() const noexcept {
82 1
        return Class(d.index());
83
      }
84

85

86
      template<auto ClassValue, typename Visitor, typename Or>
87 1
      decltype(auto) visit_or(Visitor &&visitor, Or &&other) const
88
      {
89 1
        if (type() == Class(ClassValue)) {
90 1
          return visitor(std::get<static_cast<std::size_t>(ClassValue)>(d));
91
        } else {
92 0
          return other();
93
        }
94
      }
95

96
      template<auto ClassValue>
97 1
      auto &get_set_type() {
98 1
        set_type(ClassValue);
99 1
        return (std::get<static_cast<std::size_t>(ClassValue)>(d));
100
      }
101

102 1
      auto &Map() {
103 1
        return get_set_type<Class::Object>();
104
      }
105 1
      auto &Vector() {
106 1
        return get_set_type<Class::Array>();
107
      }
108
      auto &String() {
109
        return get_set_type<Class::String>();
110
      }
111
      auto &Int() {
112
        return get_set_type<Class::Integral>();
113
      }
114
      auto &Float() {
115
        return get_set_type<Class::Floating>();
116
      }
117
      auto &Bool() {
118
        return get_set_type<Class::Boolean>();
119
      }
120

121 1
      auto Map() const noexcept {
122 1
        return std::get_if<static_cast<std::size_t>(Class::Object)>(&d);
123
      }
124 1
      auto Vector() const noexcept {
125 1
        return std::get_if<static_cast<std::size_t>(Class::Array)>(&d);
126
      }
127 1
      auto String() const noexcept {
128 1
        return std::get_if<static_cast<std::size_t>(Class::String)>(&d);
129
      }
130 1
      auto Int() const noexcept {
131 1
        return std::get_if<static_cast<std::size_t>(Class::Integral)>(&d);
132
      }
133 1
      auto Float() const noexcept {
134 1
        return std::get_if<static_cast<std::size_t>(Class::Floating)>(&d);
135
      }
136 1
      auto Bool() const noexcept {
137 1
        return std::get_if<static_cast<std::size_t>(Class::Boolean)>(&d);
138
      }
139

140
      Data d;
141
    };
142

143

144
    Internal internal;
145

146
  public:
147

148
    template <typename Container>
149
      class JSONWrapper {
150
        Container *object = nullptr;
151

152
        public:
153
        JSONWrapper( Container *val ) : object( val ) {}
154
        JSONWrapper( std::nullptr_t ) {}
155

156
        typename Container::iterator begin() { return object ? object->begin() : typename Container::iterator(); }
157
        typename Container::iterator end() { return object ? object->end() : typename Container::iterator(); }
158
        typename Container::const_iterator begin() const { return object ? object->begin() : typename Container::iterator(); }
159
        typename Container::const_iterator end() const { return object ? object->end() : typename Container::iterator(); }
160
      };
161

162
    template <typename Container>
163
      class JSONConstWrapper {
164
        const Container *object = nullptr;
165

166
        public:
167 1
        JSONConstWrapper( const Container *val ) : object( val ) {}
168
        JSONConstWrapper( std::nullptr_t ) {}
169

170 0
        typename Container::const_iterator begin() const noexcept { return object ? object->begin() : typename Container::const_iterator(); }
171 0
        typename Container::const_iterator end() const noexcept { return object ? object->end() : typename Container::const_iterator(); }
172
      };
173

174 1
    JSON() = default;
175
    JSON( std::nullptr_t ) {} 
176

177 1
    explicit JSON(Class type)
178 1
      : internal(type)
179
    {
180
    }
181

182
    JSON( initializer_list<JSON> list ) 
183
      : internal(Class::Object)
184
    {
185
      for( auto i = list.begin(), e = list.end(); i != e; ++i, ++i ) {
186
        operator[]( i->to_string() ) = *std::next( i );
187
      }
188
    }
189

190
    template <typename T>
191 1
      explicit JSON( T b, typename enable_if<is_same<T,bool>::value>::type* = nullptr ) noexcept : internal( static_cast<bool>(b) ) {}
192

193
    template <typename T>
194 1
      explicit JSON( T i, typename enable_if<is_integral<T>::value && !is_same<T,bool>::value>::type* = nullptr ) noexcept : internal( static_cast<std::int64_t>(i) ) {}
195

196
    template <typename T>
197 1
      explicit JSON( T f, typename enable_if<is_floating_point<T>::value>::type* = nullptr ) noexcept : internal( static_cast<double>(f) ) {}
198

199
    template <typename T>
200 1
      explicit JSON( T s, typename enable_if<is_convertible<T,std::string>::value>::type* = nullptr ) : internal( static_cast<std::string>(s) ) {}
201

202

203

204
    static JSON Load( const std::string & );
205

206 1
    JSON& operator[]( const std::string &key ) {
207 1
      return internal.Map().operator[]( key );
208
    }
209

210 1
    JSON& operator[]( const size_t index ) {
211 1
      auto &vec = internal.Vector();
212 1
      if( index >= vec.size() ) {
213 1
        vec.resize( index + 1 );
214
      }
215

216 1
      return vec.operator[]( index );
217
    }
218

219

220
    JSON &at( const std::string &key ) {
221
      return operator[]( key );
222
    }
223

224
    const JSON &at( const std::string &key ) const {
225
      return internal.visit_or<Class::Object>(
226
          [&](const auto &m)->const JSON &{ return m.at(key); },
227
          []()->const JSON &{ throw std::range_error("Not an object, no keys"); }
228
        );
229
    }
230

231
    JSON &at( size_t index ) {
232
      return operator[]( index );
233
    }
234

235
    const JSON &at( size_t index ) const {
236
      return internal.visit_or<Class::Array>(
237
          [&](const auto &m)->const JSON&{ return m.at(index); },
238
          []()->const JSON &{ throw std::range_error("Not an array, no indexes"); }
239
        );
240
    }
241

242
    auto length() const noexcept {
243
      return internal.visit_or<Class::Array>(
244
          [&](const auto &m){ return static_cast<int>(m.size()); },
245
          [](){ return -1; }
246
        );
247
    }
248

249
    bool has_key( const std::string &key ) const noexcept {
250
      return internal.visit_or<Class::Object>(
251
          [&](const auto &m){ return m.count(key) != 0; },
252
          [](){ return false; }
253
        );
254
    }
255

256
    int size() const noexcept {
257
      if (auto m = internal.Map(); m != nullptr) {
258
        return static_cast<int>(m->size());
259
      } if (auto v = internal.Vector(); v != nullptr) {
260
        return static_cast<int>(v->size());
261
      } else {
262
        return -1;
263
      }
264
    }
265

266 1
    Class JSONType() const noexcept { return internal.type(); }
267

268
    /// Functions for getting primitives from the JSON object.
269
    bool is_null() const noexcept { return internal.type() == Class::Null; }
270

271 1
    std::string to_string() const noexcept { 
272
      return internal.visit_or<Class::String>(
273
          [](const auto &o){ return o; },
274 0
          [](){ return std::string{}; }
275 1
        );
276
    }
277 1
    double to_float() const noexcept { 
278 1
      return internal.visit_or<Class::Floating>(
279
          [](const auto &o){ return o; },
280 0
          [](){ return double{}; }
281 1
        );
282
    }
283 1
    std::int64_t to_int() const noexcept { 
284 1
      return internal.visit_or<Class::Integral>(
285
          [](const auto &o){ return o; },
286 0
          [](){ return std::int64_t{}; }
287 1
        );
288
    }
289 1
    bool to_bool() const noexcept { 
290 1
      return internal.visit_or<Class::Boolean>(
291
          [](const auto &o){ return o; },
292 0
          [](){ return false; }
293 1
        );
294
    }
295

296
    JSONWrapper<chaiscript::utility::QuickFlatMap<std::string, JSON>> object_range() {
297
      return std::get_if<static_cast<std::size_t>(Class::Object)>(&internal.d);
298
    }
299

300
    JSONWrapper<std::vector<JSON>> array_range() {
301
      return std::get_if<static_cast<std::size_t>(Class::Array)>(&internal.d);
302
    }
303

304 1
    JSONConstWrapper<chaiscript::utility::QuickFlatMap<std::string, JSON>> object_range() const {
305 1
      return std::get_if<static_cast<std::size_t>(Class::Object)>(&internal.d);
306
    }
307

308 1
    JSONConstWrapper<std::vector<JSON>> array_range() const { 
309 1
      return std::get_if<static_cast<std::size_t>(Class::Array)>(&internal.d);
310
    }
311

312 1
    std::string dump( long depth = 1, std::string tab = "  ") const {
313 1
      switch( internal.type() ) {
314 1
        case Class::Null:
315 1
          return "null";
316 1
        case Class::Object: {
317 1
                              std::string pad = "";
318 1
                              for( long i = 0; i < depth; ++i, pad += tab ) { }
319

320 1
                              std::string s = "{\n";
321 1
                              bool skip = true;
322 1
                              for( auto &p : *internal.Map() ) {
323 1
                                if( !skip ) { s += ",\n"; }
324 1
                                s += ( pad + "\"" + json_escape(p.first) + "\" : " + p.second.dump( depth + 1, tab ) );
325 1
                                skip = false;
326
                              }
327 1
                              s += ( "\n" + pad.erase( 0, 2 ) + "}" ) ;
328 1
                              return s;
329
                            }
330 1
        case Class::Array: {
331 1
                             std::string s = "[";
332 1
                             bool skip = true;
333 1
                             for( auto &p : *internal.Vector() ) {
334 1
                               if( !skip ) { s += ", "; }
335 1
                               s += p.dump( depth + 1, tab );
336 1
                               skip = false;
337
                             }
338 1
                             s += "]";
339 1
                             return s;
340
                           }
341 1
        case Class::String:
342 1
                           return "\"" + json_escape( *internal.String() ) + "\"";
343 1
        case Class::Floating:
344 1
                           return std::to_string( *internal.Float() );
345 1
        case Class::Integral:
346 1
                           return std::to_string( *internal.Int() );
347 1
        case Class::Boolean:
348 1
                           return *internal.Bool() ? "true" : "false";
349
      }
350

351 0
      throw std::runtime_error("Unhandled JSON type");
352
    }
353

354

355
  private:
356 1
    static std::string json_escape( const std::string &str ) {
357 1
      std::string output;
358 1
      for(char i : str) {
359 1
        switch( i ) {
360 0
          case '\"': output += "\\\""; break;
361 1
          case '\\': output += "\\\\"; break;
362 1
          case '\b': output += "\\b";  break;
363 0
          case '\f': output += "\\f";  break;
364 1
          case '\n': output += "\\n";  break;
365 1
          case '\r': output += "\\r";  break;
366 1
          case '\t': output += "\\t";  break;
367 1
          default  : output += i; break;
368
        }
369
}
370 1
      return output;
371
    }
372

373

374
  private:
375
};
376

377

378
struct JSONParser {
379 1
  static bool isspace(const char c) noexcept
380
  {
381
#ifdef CHAISCRIPT_MSVC
382
    // MSVC warns on these line in some circumstances
383
#pragma warning(push)
384
#pragma warning(disable : 6330)
385
#endif
386 1
    return ::isspace(c) != 0;
387
#ifdef CHAISCRIPT_MSVC
388
#pragma warning(pop)
389
#endif
390

391

392
  }
393

394 1
  static void consume_ws( const std::string &str, size_t &offset ) {
395 1
    while( isspace( str.at(offset) ) && offset <= str.size() ) { ++offset; }
396
  }
397

398 1
  static JSON parse_object( const std::string &str, size_t &offset ) {
399 1
    JSON Object( JSON::Class::Object );
400

401 1
    ++offset;
402 1
    consume_ws( str, offset );
403 1
    if( str.at(offset) == '}' ) {
404 1
      ++offset; return Object;
405
    }
406

407 1
    for (;offset<str.size();) {
408 1
      JSON Key = parse_next( str, offset );
409 1
      consume_ws( str, offset );
410 1
      if( str.at(offset) != ':' ) {
411 1
        throw std::runtime_error(std::string("JSON ERROR: Object: Expected colon, found '") + str.at(offset) + "'\n");
412
      }
413 1
      consume_ws( str, ++offset );
414 1
      JSON Value = parse_next( str, offset );
415 1
      Object[Key.to_string()] = Value;
416

417 1
      consume_ws( str, offset );
418 1
      if( str.at(offset) == ',' ) {
419 1
        ++offset; continue;
420
      }
421 1
      else if( str.at(offset) == '}' ) {
422 1
        ++offset; break;
423
      }
424
      else {
425 1
        throw std::runtime_error(std::string("JSON ERROR: Object: Expected comma, found '") + str.at(offset) + "'\n");
426
      }
427
    }
428

429 1
    return Object;
430
  }
431

432 1
  static JSON parse_array( const std::string &str, size_t &offset ) {
433 1
    JSON Array( JSON::Class::Array );
434 1
    size_t index = 0;
435

436 1
    ++offset;
437 1
    consume_ws( str, offset );
438 1
    if( str.at(offset) == ']' ) {
439 1
      ++offset; return Array;
440
    }
441

442 1
    for (;offset < str.size();) {
443 1
      Array[index++] = parse_next( str, offset );
444 1
      consume_ws( str, offset );
445

446 1
      if( str.at(offset) == ',' ) {
447 1
        ++offset; continue;
448
      }
449 1
      else if( str.at(offset) == ']' ) {
450 1
        ++offset; break;
451
      }
452
      else {
453 1
        throw std::runtime_error(std::string("JSON ERROR: Array: Expected ',' or ']', found '") + str.at(offset) + "'\n");
454
      }
455
    }
456

457 1
    return Array;
458
  }
459

460 1
  static JSON parse_string( const std::string &str, size_t &offset ) {
461 1
    std::string val;
462 1
    for( char c = str.at(++offset); c != '\"' ; c = str.at(++offset) ) {
463 1
      if( c == '\\' ) {
464 1
        switch( str.at(++offset) ) {
465 1
          case '\"': val += '\"'; break;
466 1
          case '\\': val += '\\'; break;
467 1
          case '/' : val += '/' ; break;
468 1
          case 'b' : val += '\b'; break;
469 1
          case 'f' : val += '\f'; break;
470 1
          case 'n' : val += '\n'; break;
471 1
          case 'r' : val += '\r'; break;
472 1
          case 't' : val += '\t'; break;
473 0
          case 'u' : {
474 0
                       val += "\\u" ;
475 0
                       for( size_t i = 1; i <= 4; ++i ) {
476 0
                         c = str.at(offset+i);
477 0
                         if( (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ) {
478 0
                           val += c;
479
                         } else {
480 0
                           throw std::runtime_error(std::string("JSON ERROR: String: Expected hex character in unicode escape, found '") + c + "'");
481
                         }
482
                       }
483 0
                       offset += 4;
484 0
                     } break;
485 1
          default  : val += '\\'; break;
486
        }
487
      } else {
488 1
        val += c;
489
      }
490
    }
491 1
    ++offset;
492 1
    return JSON(val);
493
  }
494

495 1
  static JSON parse_number( const std::string &str, size_t &offset ) {
496 1
    std::string val, exp_str;
497 1
    char c = '\0';
498 1
    bool isDouble = false;
499 1
    bool isNegative = false;
500 1
    std::int64_t exp = 0;
501 1
    bool isExpNegative = false;
502 1
    if( offset < str.size() && str.at(offset) == '-' ) {
503 1
      isNegative = true;
504 1
      ++offset;
505
    }
506 1
    for (; offset < str.size() ;) {
507 1
      c = str.at(offset++);
508 1
      if( c >= '0' && c <= '9' ) {
509 1
        val += c;
510 1
      } else if( c == '.' && !isDouble ) {
511 1
        val += c; 
512 1
        isDouble = true;
513
      } else {
514
        break;
515
      }
516
    }
517 1
    if( offset < str.size() && (c == 'E' || c == 'e' )) {
518 1
      c = str.at(offset++);
519 1
      if( c == '-' ) { 
520 1
        isExpNegative = true;
521 1
      } else if( c == '+' ) {
522
        // do nothing
523
      } else { 
524 1
        --offset; 
525
      }
526

527 1
      for (; offset < str.size() ;) {
528 1
        c = str.at(offset++);
529 1
        if( c >= '0' && c <= '9' ) {
530 1
          exp_str += c;
531 0
        } else if( !isspace( c ) && c != ',' && c != ']' && c != '}' ) {
532 1
          throw std::runtime_error(std::string("JSON ERROR: Number: Expected a number for exponent, found '") + c + "'");
533
        }
534
        else {
535 0
          break;
536
        }
537
      }
538 1
      exp = chaiscript::parse_num<std::int64_t>( exp_str ) * (isExpNegative?-1:1);
539
    }
540 1
    else if( offset < str.size() && (!isspace( c ) && c != ',' && c != ']' && c != '}' )) {
541 1
      throw std::runtime_error(std::string("JSON ERROR: Number: unexpected character '") + c + "'");
542
    }
543 1
    --offset;
544

545 1
    if( isDouble ) {
546 1
      return JSON((isNegative?-1:1) * chaiscript::parse_num<double>( val ) * std::pow( 10, exp ));
547
    } else {
548 1
      if( !exp_str.empty() ) {
549 1
        return JSON((isNegative?-1:1) * static_cast<double>(chaiscript::parse_num<std::int64_t>( val )) * std::pow( 10, exp ));
550
      } else {
551 1
        return JSON((isNegative?-1:1) * chaiscript::parse_num<std::int64_t>( val ));
552
      }
553
    }
554
  }
555

556 1
  static JSON parse_bool( const std::string &str, size_t &offset ) {
557 1
    if( str.substr( offset, 4 ) == "true" ) {
558 1
      offset += 4;
559 1
      return JSON(true);
560 1
    } else if( str.substr( offset, 5 ) == "false" ) {
561 1
      offset += 5;
562 1
      return JSON(false);
563
    } else {
564 1
      throw std::runtime_error(std::string("JSON ERROR: Bool: Expected 'true' or 'false', found '") + str.substr( offset, 5 ) + "'");
565
    }
566
  }
567

568 1
  static JSON parse_null( const std::string &str, size_t &offset ) {
569 1
    if( str.substr( offset, 4 ) != "null" ) {
570 0
      throw std::runtime_error(std::string("JSON ERROR: Null: Expected 'null', found '") + str.substr( offset, 4 ) + "'");
571
    }
572 1
    offset += 4;
573 1
    return JSON();
574
  }
575

576 1
  static JSON parse_next( const std::string &str, size_t &offset ) {
577
    char value;
578 1
    consume_ws( str, offset );
579 1
    value = str.at(offset);
580 1
    switch( value ) {
581 1
      case '[' : return parse_array( str, offset );
582 1
      case '{' : return parse_object( str, offset );
583 1
      case '\"': return parse_string( str, offset );
584 1
      case 't' :
585 1
      case 'f' : return parse_bool( str, offset );
586 1
      case 'n' : return parse_null( str, offset );
587 1
      default  : if( ( value <= '9' && value >= '0' ) || value == '-' ) {
588 1
                   return parse_number( str, offset );
589
                 }
590
    }
591 1
    throw std::runtime_error(std::string("JSON ERROR: Parse: Unexpected starting character '") + value + "'");
592
  }
593

594
};
595

596
inline JSON JSON::Load( const std::string &str ) {
597
  size_t offset = 0;
598
  return JSONParser::parse_next( str, offset );
599
}
600

601
} // End Namespace json
602

603

604
#endif 

Read our documentation on viewing source code .

Loading