C++ JSON parser/composer Klassentemplate
C++ JSON parser/composer class template
Liest/schreibt JSON Text von/zu string
oder iostream
. Siehe Beispiele
(und ganz unten das kleine Testprogram). Verwende einfach die statischen
Funktionen sw::json::parse(...)
und sw::json::compose(...)
.
Features und Dinge, die man wissen sollte:
Parser und Composer.
Strict/non-strict parsing/composing (siehe unten)
Versteht Unicode Escapesequenzen und wandelt diese in UTF-8 um
Versteht (und ignoriert) Zeilenkommentare:
// COMMENTS <newline>
Versteht (und ignoriert) Mehrzeilenkommentare:
/* COMMENTS */
Wirft verständliche Exceptions mit Angabe von Zeile/Spalte
Die Formatierung des importierten JSON-"Dokuments" wird nicht gespeichert, die Daten sind aber identisch. Was ich damit meine: Die Klasse taugt zum Einlesen von JSON-Konfiguration und für den Datentransfer, nicht um "human readable" Konfigurationsdateien zu editieren.
Unicode Escapesequenzen (, die schon beim Parsen in UTF8 gewandelt wurden,) sind beim Schreiben ("Re-Composen") UTF8.
Schreibt immer komprimiert, d.h. alle überflüssigen Leerzeichen fliegen raus. Die Ausgabe ist eine Zeile.
Die Klasse benötigt Exceptions, also diese nicht mit
-fno-exceptions
abschalten. RTTI-Support wird nicht gebraucht,-fno-rtti
is ok.
Reads/writes JSON fomatted text from/to string
or iostream
. See the examples
(and the bottommost test program). Simply use the static functions
sw::json::parse(...)
and sw::json::compose(...)
.
Features and things to know about it:
Has Parser and composer.
Strict/non-strict parsing/composing (see below)
Understands unicode escapes, converts latter to utf8
Understands single line comments:
// COMMENTS <newline>
Understands multi line comments:
/* COMMENTS */
Throws understandable exceptions with error line/colum.
The 'document structure' is not saved, parse and re-compose can have different output. The data, however, are identical.
Comments are generally removed.
Parsed unicode escape sequences will be composed as UTF8.
Always composed in compressed form (no newlines, no spaces).
- Use this class for reading (e.g. config files) and data exchange, not for modifying human readable JSON 'documents'.
Does not work with compiler option
-fno-exceptions
, but with-fno-rtti
.
Dateien
Files
json.hh var.hh microtest/json.cc
Beispiele
Examples
#include <sw/json.hh>
#include <iostream>
#include <sstream>
using namespace std;
using namespace sw;
int main(int argc, char** argv)
{
// Parse from string:
sw::var a = sw::json::parse("{ a:10, b:[1,2,3], c:\"foo\" }");
// Parse from stream:
std::stringstream ss("{ a:10, b:[1,2,3], c:\"foo\" }");
sw::var b = sw::json::parse(ss); // Could be as well std::cin or file stream.
// Print results:
cout << "-------------------------------------" << endl
<< "a= ";
a.dump(cout);
cout << endl;
// Compose:
cout << "-------------------------------------" << endl;
// Strict composing
cout << "composed (strict)= ";
sw::json::compose(a, cout);
cout << endl;
// Non-strict composing
cout << "composed (non-strict)= ";
sw::json::compose(a, cout, sw::json::o_no);
cout << endl;
// Compose to string:
string s = sw::json::compose(a, sw::json::o_no);
cout << "composed string = " << s << endl;
}
// Output:
//
// g++ -Wall -O3 -pedantic -std=c++98 main.cc -o test-main -I../ -lm
// ./test-main
// -------------------------------------
// a= map(3) =
// {
// "a": 10
// "b": vector(3) =
// [
// 0: 1
// 1: 2
// 2: 3
// ]
// "c": foo
// }
//
// -------------------------------------
// composed (strict)= {"a":10,"b":[1,2,3],"c":"foo"}
// composed (non-strict)= {a:10,b:[1,2,3],c:"foo"}
// composed string = {a:10,b:[1,2,3],c:"foo"}
Klassenquelltext
Class source code
/**
* @package de.atwillys.cc.swl
* @license BSD (simplified)
* @author Stefan Wilhelm (stfwi)
*
* @json.hh
* @ccflags
* @ldflags
* @platform linux, bsd, windows
* @standard >= c++98
*
* -----------------------------------------------------------------------------
*
* JSON parser / composer class template. Requires variable type class
* `sw::detail::basic_var<...>` / `sw::var`.
*
* namespace sw { namespace detail {
* template <typename string_typeype> class basic_json;
* }}
*
* Default specialisation with std::string:
*
* namespace sw {
* typedef detail::basic_json<std::string> json;
* }
*
* -----------------------------------------------------------------------------
*
* Brief features and annotations
*
* - parser, composer.
* - Strict/non-strict parsing/composing (see below)
* - Understands unicode escapes, converts latter to utf8
* - Understands single line comments: `// COMMENTS <newline>`
* - Understands multi line comments: `/ * COMMENTS * /`
* - Throws understandable exceptions with error line/colum.
*
* - The 'document structure' is not saved, parse and re-compose can have
* different output. The data, however, are identical.
* - Comments are generally removed.
* - Parsed unicode escape sequences will be composed as UTF8.
* - Always composed in compressed form (no newlines, no spaces).
*
* --> Use this class for reading (e.g. config files) and data exchange,
* not for modifying human readable JSON 'documents'.
*
* - Does not work with compiler option `-fno-exceptions`, but with `-fno-rtti`.
*
* -----------------------------------------------------------------------------
*
* The main functions used are static and named `parse(...)` and `compose(...)`.
* `parse()` can take either a `istream` reference or a const `string` reference,
* `compose()` can either write to a `ostream` or return a `string`.
*
* As additional argument, options can be passed to the parser/composer, where
* currently the only options are `o_no` (no options) and `o_strict`, which
* means
*
* - `o_strict`: The parser shall not try to "overlook" inconsistencies
* but throw on everything that could mean trouble.
*
* - `o_strict`: The composer must apply strict rules, means e.g. it must
* quote object keys, even if they are compliant to alphanumeric
* variable name conventions ( `{ "a":10 }`, NOT {a:10} ).
*
* The parser is non-strict by default, the composer is strict by default.
*
* Usage:
*
* - Parse:
*
* sw::var result = sw::json::parse(stream, (<options>));
*
* sw::var result = sw::json::parse(string, (<options>));
*
* e.g.:
*
* sw::var r = sw::json::parse(cin); // strict is optional
* r.dump(cerr);
*
* - Compose:
*
* sw::json::compose((const sw::var&) what, (ostream&) where); // --> ostream
*
* string result = sw::json::compose((const sw::var&) what); // --> string
*
* -----------------------------------------------------------------------------
* +++ BSD license header (You know that ...) +++
* Copyright (c) 2010-2014, Stefan Wilhelm (stfwi, <cerbero s@atwilly s.de>)
* All rights reserved.
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met: (1) Redistributions
* of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer. (2) Redistributions in binary form must reproduce
* the above copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the distribution.
* (3) Neither the name of atwillys.de nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
* AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
* -----------------------------------------------------------------------------
*/
#ifndef SW__JSON_HH
#define SW__JSON_HH
#include "var.hh"
#include <string>
#include <cctype>
#include <iostream>
#include <vector>
#include <stack>
#include <stdexcept>
#include <algorithm>
namespace sw { namespace detail {
template <typename StringType>
class basic_json
{
//////////////////////////////////////////////////////////////////////////////
// Types
public:
typedef StringType string_type;
typedef typename string_type::value_type char_type; // Char type
typedef unsigned int ucchar_t; // Unicode character type, >= uint16
typedef std::basic_istream<char_type> istream_type;
typedef std::basic_ostream<char_type> ostream_type;
typedef std::basic_stringstream<char_type> sstream_type;
typedef basic_var<string_type, typename sw::var::float_type, typename sw::var::int_type> var_type;
typedef std::runtime_error exception_type;
typedef enum { o_no=0, o_strict } options_type;
//////////////////////////////////////////////////////////////////////////////
// Static "main functions"
public:
/**
* Parses a JSON text stream into an sw::var variable.
* @param istream_type& stream
* @param bool strict=false
* @return var_type
*/
static inline var_type parse(istream_type& stream, bool strict=false)
{ var_type r; basic_json()(stream, r, strict ? o_strict:o_no); return r; }
/**
* Parses a JSON text into an sw::var variable.
* @param const string_type& text
* @param bool strict=false
* @return var_type
*/
static inline var_type parse(const string_type& txt, bool strict=false)
{ var_type r; sstream_type s(txt); basic_json()(s, r, strict ? o_strict:o_no ); return r; }
/**
* Composes JSON to a given stream. Note that comments are omitted and the
* order of object key-value pairs can differ from a JSON string parsed
* before.
* @param const var_type& v
* @param ostream_type& stream
* @param options_type opts = o_strict
* @return ostream_type& stream
*/
static inline ostream_type& compose(const var_type& v, ostream_type& stream, options_type opts=o_strict)
{ basic_json()(v, stream, opts); return stream; }
/**
* Composes JSON to a given string. Note that comments are omitted and the
* order of object key-value pairs can differ from a JSON string parsed
* before.
* @param const var_type& v
* @param ostream_type& stream
* @param options_type opts = o_strict
* @return ostream_type& stream
*/
static inline string_type compose(const var_type& v, options_type opts=o_strict)
{ sstream_type s; basic_json()(v, s, opts); return s.str(); }
//////////////////////////////////////////////////////////////////////////////
// Construction / destruction
public:
/**
* Default constructor
*/
inline basic_json() :
is_(NULL), os_(NULL), line_(1), line_pos_(1), eof_overread_('\n'), opts_(o_no)
{ ; }
/**
* Destructor
*/
virtual ~basic_json()
{ ; }
//////////////////////////////////////////////////////////////////////////////
// "Main methods" exposed as operator (), getters.
public:
/**
* Parses a JSON text stream into an sw::var variable.
* @param istream_type& stream
* @param bool strict=false
* @param var_type& result
* @return var_type&
*/
inline var_type& operator() (istream_type& stream, var_type& result, options_type opts=o_no)
{ is_ = &stream; opts_=opts; parse_root(result); return result; }
/**
* Composes JSON to a given stream. Note that comments are omitted and the
* order of object key-value pairs can differ from a JSON string parsed
* before.
* @param const var_type& v
* @param ostream_type& stream
* @param options_type opts = o_strict
* @return ostream_type& stream
*/
inline ostream_type& operator() (const var_type& v, ostream_type& stream, options_type opts=o_strict)
{ os_ = &stream; opts_=opts; compose_root(v); return stream; }
/**
* True if strict parsing/composing.
* @return bool
*/
inline bool strict() const
{ return (opts_ & o_strict) == o_strict; }
//////////////////////////////////////////////////////////////////////////////
// Parse
protected:
inline bool eof() const
{ return is_->eof(); }
/**
* Get next char from stream. (_is must be assigned!). Throw exception
* on bad stream. Returns 0 on EOF.
* @return ch_t
*/
inline char_type get()
{
char_type c;
if(!is_->get(c)) {
if(!is_->eof()) {
throw exception_type("Read error");
} else if(eof_overread_ != '\0') {
// We accept one char overread before throwing.
c = '\0'; eof_overread_ = '\0';
} else {
throw exception_type("Unexpected end of input");
}
} else {
if(c == '\n') { line_++; line_pos_=0; }
line_pos_++;
}
return c;
}
/**
* Take a glance at the next character without pulling it out of the stream.
* @return char_type
*/
inline char_type peek() const
{ char_type c=is_->peek(); return (c==istream_type::traits_type::eof()) ? 0:c; }
/**
* Put a character back into the stream. Decrements position but does not
* unwrap line counter.
* @param char_type c
*/
inline void putback(char_type c)
{ if(c) { line_pos_--; is_->putback(c); } }
/**
* Skips spaces
*/
inline void skip_spaces()
{ while(is_->good() && std::isspace(peek()) && get()); }
/**
* Detects // and / * * / comments and skips them.
*/
void skip_comments()
{
// Could be multiple comments joined, eventually get() will force abort
// on eof().
while(!eof()) {
skip_spaces();
if(peek() != '/') return;
char_type c = get();
char_type c1 = peek();
if((c1 != '/') && (c1 != '*')) { putback(c); return; }
if(c1 == '/') {
while((c=get()) && c!='\n' && c!='\r');
if(eof()) { eof_overread_ = '\n'; return; }
} else {
while((c=get()) && !(c=='*' && peek()=='/'));
get();
}
}
}
/**
* Starting point of the whole parse process
* @param var_type &node
*/
void parse_root(var_type &node)
{
try {
eof_overread_ = '\n';
line_ = 1;
line_pos_ = 1;
node.clear();
if(!is_) throw exception_type("Input stream pointer is NULL. Can't parse.");
skip_comments();
if(eof()) return; // Empty input --> var will be `unset`.
parse_any(node);
skip_comments();
if(!eof()) throw exception_type("End of input expected");
} catch(const exception_type &e) {
try { node.clear(); } catch(...) { ; }
sstream_type ss; ss << "JSON parser [@" << line_ << ":" << line_pos_ << "]: " << e.what();
throw exception_type(ss.str());
}
}
/**
* Relay for parsing others.
* @param var_type &node
*/
void parse_any(var_type &node)
{
skip_comments();
char_type c = get();
if(c == '"') {
parse_string(node);
} else if(c == '[') {
parse_array(node);
} else if(c == '{') {
parse_object(node);
} else if(std::isdigit(c) || c=='-' || c=='+' || c=='.') {
putback(c); parse_number(node);
} else if(std::isalpha(c) || c=='_') {
putback(c); parse_literal(node);
} else if(c != 0) {
throw exception_type(string_type("Unexpected character '")+c+"'");
} else {
node.clear();
}
}
/**
* Parses a number, assigns either int or floating point value to the node.
* Throws on error.
* @param var_type &node
*/
void parse_number(var_type &node)
{
sstream_type ss;
char_type c;
bool is_float = false;
if(peek() == '+') get();
while((c = get())) {
if(std::isdigit(c)) {
ss << c;
} else if(c=='.' || c=='e' || c=='E' || c=='+' || c=='-') {
ss << c;
is_float = true;
} else {
putback(c);
break;
}
}
double d;
string_type s = ss.str();
if(!(ss >> d) || !ss.eof()) { // Expected: Whole string is the number
node.f(std::numeric_limits<double>::quiet_NaN());
throw exception_type(string_type("Invalid numeric expression: '")+s+"'");
} else if(is_float) {
node.f(d);
} else {
node.i((typename var_type::int_type)d);
}
}
/**
* Parse known literals (case insensitively). Throw on error.
* @param var_type &node
*/
void parse_literal(var_type &node)
{
char_type c = get();
if(!std::isalpha(c) && c != '_') {
throw exception_type("Bug: Can't parse a literal not starting with an `alnum`!");
}
std::string s;
s.reserve(16);
s += c;
while((c = get()) && (std::isalnum(c) || c=='_')) s += c;
putback(c);
std::string ls(s);
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
if(s == "null") {
node = var_type::nul();
} else if(s == "false") {
node.b(false);
} else if(s == "true") {
node.b(true);
} else if(!strict() && s == "undefined") {
node.clear();
} else {
throw exception_type(string_type("Unknown literal '") + ls + "'");
}
}
/**
* Parse array. Expected that the leading '[' literal is already shifted off.
* Throws on error.
* @param var_type &node
*/
void parse_array(var_type &node)
{
node.v(var_type::empty_vector());
skip_comments();
if(peek() == ']') { get(); return; }
while(true) {
var_type v;
parse_any(v);
node.push_back(v);
skip_comments();
char_type c = get();
if(c == ']') break;
if(c != ',') throw exception_type("Expected ']' or ',' while parsing array");
}
}
/**
* Parse object. Expected that the leading '{' literal is already shifted off.
* Throws on error.
* @param var_type &node
*/
void parse_object(var_type &node)
{
node.m(var_type::empty_map());
skip_comments();
if(peek() == '}') { get(); return; }
while(true) {
string_type key = parse_object_key();
skip_comments();
if(get() != ':') throw exception_type("Missing ':' after object key");
var_type v;
parse_any(v);
node.m(key, v);
skip_comments();
char_type c = get();
if(c == '}') break;
if(c != ',') throw exception_type("Expected '}' or ',' while parsing object");
}
}
/**
*
* @return
*/
string_type parse_object_key()
{
skip_comments();
char c = get();
if(c == '"') {
var_type v;
parse_string(v);
return v.s();
} else if(std::isalpha(c) || c=='_' || (!strict() && std::isdigit(c))) {
string_type s; s.reserve(32);
s += c;
while((c=get()) && (std::isalnum(c) || c=='_')) s += c;
if(!std::isspace(c)) putback(c);
return s;
} else {
throw exception_type(string_type("Invalid object key start character '")+c+"'");
}
}
/**
* Parse string. Expected that the beginning literal is already shifted off
* the stream.
* @param var_type &node
*/
void parse_string(var_type &node)
{
sstream_type ss;
char_type c;
while((c = get())) {
switch(c) {
case '"':
node.s(ss.str());
return;
break;
case '\\':
switch(peek()) {
case '\\':
case '"' :
case '/' : ss << get(); break;
case 'n' : ss << "\n"; get(); break;
case 'r' : ss << "\r"; get(); break;
case 't' : ss << "\t"; get(); break;
case 'u' : putback(c); ss << parse_unicode_escape_to_utf8(); break;
case 'v' : ss << "\v"; get(); break;
case 'f' : ss << "\f"; get(); break;
case 'b' : ss << "\b"; get(); break;
case 'a' : ss << "\a"; get(); break;
default :
if(!strict()) {
ss << '\\';
} else {
throw exception_type(string_type("Unexpected escape character '\\")+c+"'");
}
}
break;
default:
ss << c;
}
}
throw exception_type("Unexpected end of file while parsing string.");
}
/**
* Returns utf8 string from one or more (concatenated) unicode escape
* sequences.
* @return string_type
*/
string_type parse_unicode_escape_to_utf8()
{
string_type s; s.reserve(8);
while(peek()=='\\') {
ucchar_t uc;
char_type c = get();
if(peek() != 'u') { putback(c); break; }
get();
uc = parse_ucc_esc();
if(uc >= 0xd800 && uc <= 0xdbff) { // It's a surrog. pair?
if(get()!='\\' || get()!='u') {
throw exception_type("Missing second unicode escape sequence for surrogate pair.");
}
ucchar_t uc2 = parse_ucc_esc();
uc = ((1ul)<<16) + ((uc & (((1ul<<10)-1)<<10)) | (uc2 & ((((1ul)<<10)-1))));
}
s += ucc2utf8(uc);
}
return s;
}
ucchar_t parse_ucc_esc()
{
ucchar_t uc = 0;
char_type c;
for(int i=0; i<4; ++i) {
c = ::tolower(get());
uc <<= 4;
if (c>='0' && c<='9') uc|= (c-'0');
else if(c>='a' && c<='f') uc|= (c-'a'+10);
else throw exception_type("Invalid hex character while parsing unicode escape.");
}
return uc;
}
static string_type ucc2utf8(ucchar_t c)
{
unsigned char s[5] = {0,0,0,0,0};
if(c < 0x00000080) {
s[0] = (c);
} else if (c < 0x00000800u) {
s[0] = (0xc0u | (0x1fu & (c >> 6)));
s[1] = (0x80u | (0x3fu & (c >> 0)));
} else if (c < 0x00010000u) {
s[0] = (0xE0u | (0x0fu & (c >> 12)));
s[1] = (0x80u | (0x3fu & (c >> 6)));
s[2] = (0x80u | (0x3fu & (c >> 0)));
} else if (c < 0x00110000u) {
s[0] = (0xf0u | (0x07u & (c >> 18)));
s[1] = (0x80u | (0x3fu & (c >> 12)));
s[2] = (0x80u | (0x3fu & (c >> 6)));
s[3] = (0x80u | (0x3fu & (c >> 0)));
}
return string_type((char*)s);
}
//////////////////////////////////////////////////////////////////////////////
// Compose
protected:
inline ostream_type & put()
{ if(os_->good()) return *os_; throw exception_type("Compose output stream failed"); }
/**
* Compose starting point.
* @param const var_type&
* @param ostream_type& os
*/
void compose_root(const var_type& o)
{
try {
line_ = line_pos_ = 0;
eof_overread_ = 0;
is_ = 0;
if(o.is_unset()) return;
if((!os_) || !os_->good()) throw exception_type("Cannot compose, out stream is 'bad'");
compose_r(o);
} catch(const exception_type &e) {
sstream_type s; s << "JSON composer: " << e.what();
throw exception_type(s.str());
}
}
/**
* Recursive composing.
* @param const var_type&
* @param ostream_type& os
*/
void compose_r(const var_type& o)
{
if(o.is_unset()) {
return;
} else if(o.is_null()) {
put() << "null";
} else if(o.is_bool()) {
put() << (o.b() ? "true" : "false");
} else if(o.is_int()) {
put() << o.i();
} else if(o.is_float()) {
put() << o.f();
} else if(o.is_string()) {
compose_string(o.s());
} else if(o.is_vector()) {
put() << "[";
int end = o.size()-1;
int i=-1;
while(++i < end) { compose_r(o.v(i)); put() << ","; }
if(i<=end) compose_r(o.v(i));
put() << "]";
} else if(o.is_map()) {
put() << "{";
typename var_type::map_t::const_iterator end = o.m().end();
for(typename var_type::map_t::const_iterator it=o.m().begin(); it!=end; ++it) {
compose_object_key(it->first);
put() << ":";
compose_r(it->second);
typename var_type::map_t::const_iterator cit = it;
if(++cit != end)put() << ",";
}
put() << "}";
}
}
/**
* Normally identically to compose_string. If nonstrict, alphanumeric keys
* are not escaped using string literals.
* @param const string_type &s
*/
void compose_object_key(const string_type &s)
{
if(s.empty()) {
put() << "\"\"";
} else if(!strict() && std::isalpha(s[0])) {
typename string_type::const_iterator it=s.begin();
typename string_type::const_iterator end=s.end();
while((++it)!=end) {
if(!std::isalnum(*it) && (*it) != '_') {
compose_string(s);
return;
}
}
put() << s;
} else {
compose_string(s);
}
}
/**
* String composition
* @param const string_type &s
* @return void
*/
void compose_string(const string_type &s)
{
put() << "\"";
typename string_type::const_iterator p=s.begin();
typename string_type::const_iterator e=s.end();
for(; p!=e; ++p) {
if((*p) < 0) {
(*os_) << (*p); // pass through UTF8, no stream bad check here
} else if((*p) == 0) {
put() << ucc2uchex(0); // Just to ensure ...
} else if((*p) != ' ' && (std::iscntrl(*p) || std::isspace(*p))) {
switch((*p)) {
case '\n': (*os_) << "\\n"; break;
case '\r': (*os_) << "\\r"; break;
case '\t': (*os_) << "\\t"; break;
case '\f': (*os_) << "\\f"; break;
case '\v': (*os_) << "\\v"; break;
case '\a': (*os_) << "\\a"; break;
case '\b': (*os_) << "\\b"; break;
default: (*os_) << ucc2uchex(((1u<<7)-1) & (*p));
}
} else if((*p)=='\\' || (*p)=='"') {
(*os_) << '\\' << (*p);
} else {
(*os_) << (*p); // pass through normal char, no stream bad check here
}
}
put() << "\"";
}
/**
* Note: Stripped version, no surrogates
* @param unsigned uc
* @return string_type
*/
inline string_type ucc2uchex(unsigned uc)
{
uc &= (1u<<16)-1;
char s[7] = { '\\','u','0','0','0','0', 0};
int i = 6;
while(i<7 && uc!=0) { // uint wrap -> i<0
char c = (uc & 0x0f);
uc >>= 4;
s[--i] = (c<10) ? (c+'0') : (c-10+'a');
}
return s;
}
//////////////////////////////////////////////////////////////////////////////
// Instance variables
protected:
istream_type *is_;
ostream_type *os_;
int line_, line_pos_;
char_type eof_overread_;
options_type opts_;
};
}}
////////////////////////////////////////////////////////////////////////////////
// Default specialisation
namespace sw {
typedef detail::basic_json<std::string> json;
}
#endif
Mehr Beispiele
More Examples
#include <json.hh>
#include "test.hh"
using namespace std;
void parse(string s, const sw::json::var_type &expect=sw::json::var_type::undef())
{
sw::json::var_type json;
try {
std::string sout;
{
std::stringstream ssout;
ssout << "JSON : '" << s << "'" << endl << "OUTPUT: ";
json = sw::json::parse(s);
json.dump(ssout);
sout = ssout.str();
while(sout.length() > 1 && ::isspace(sout[sout.length()-1])) { // std98: no .back()
sout.resize(sout.length()-1);
}
}
test_comment("Check: " << endl << sout);
if(expect.is_unset() || expect == "<IGNORE>") {
test_pass("Ok : yes (no expectation/ignored)");
} else if(expect == "<EXCEPTION>") {
test_fail("Ok : no (exception was expected)");
} else if(json == expect) {
test_pass("Ok : yes");
} else {
test_fail("Ok : no");
}
} catch(const sw::json::exception_type &e) {
test_comment("Exception: " << e.what());
if(expect == "<EXCEPTION>") {
test_pass("Ok : yes (expected exception)");
} else if(expect == "<IGNORE>") {
test_pass("Ok : yes (ignored)");
} else {
test_fail(std::string("Ok : no, exception: ") + e.what());
}
} catch(...) {
test_fail("Ok : no (unexpected exception type)");
}
}
void compose(const string &json_parse, const string &expect="<IGNORE>",
sw::json::options_type opts=sw::json::o_strict)
{
try {
std::stringstream sout;
sout << "JSON IN : '" << json_parse << "'" << endl
<< "EXPECT : '" << expect << "'" << endl
<< "JSON OUT: ";
sw::json::var_type j = sw::json::parse(json_parse);
string s = sw::json::compose(j, opts);
sout << "'" << s << "'";
test_comment("Composed: " << endl << sout.str());
if(expect == "<IGNORE>") {
test_pass("Ok : yes (no expectation/ignored)");
} else if(expect == s) {
test_pass("Ok : yes");
} else {
test_fail("Ok : no");
}
} catch(const sw::json::exception_type& e) {
test_fail(std::string("Ok : no (exception ") + e.what() + ")");
} catch(...) {
test_fail("Ok : no (unexpected exception)");
}
}
////////////////////////////////////////////////////////////////////////////////
void test_num()
{
parse("12345", 12345);
parse("12.345", 12.345);
parse("12.34e5", 12.34e5);
parse("12.3E5", 12.3e5);
parse("1.2e-5", 1.2e-5);
parse("-1.2e-5", -1.2e-5);
parse("+1.2e-5", 1.2e-5);
parse("1.2e+5", 1.2e5);
parse(".2e-5", 0.2e-5);
parse(".1", 0.1);
parse(" 12345 ", 12345);
parse("12.34.5", "<EXCEPTION>");
parse("12.34r5", "<EXCEPTION>");
parse("12.3E4e5", "<EXCEPTION>");
}
void test_literal()
{
parse("null", sw::var::nul());
parse("true", true);
parse("false", false);
parse(" null", sw::var::nul());
parse("Nulll", "<EXCEPTION>");
parse("NULL", sw::var::nul());
}
void test_string()
{
parse(" \"Hello\" ", "Hello");
parse(" \"He\\\"llo\" ", "He\"llo");
parse(" \"He\\\\\\\"llo\" ", "He\\\"llo");
parse(" \"Hello\\\" ", "<EXCEPTION>");
parse(" \"Hello\\\" ", "<EXCEPTION>");
parse(" \"\\n\" ", "\n");
parse(" \"\\r\" ", "\r");
parse(" \"\\t\" ", "\t");
parse(" \"\\f\" ", "\f");
parse(" \"\\a\" ", "\a");
parse(" \"\\v\" ", "\v");
parse(" \"\\b\" ", "\b");
parse(" \"\\\\\" ", "\\");
}
void test_array()
{
parse("[]");
parse("[ 1, 2, 3, 4, 5 ]");
parse("[ 1, \"A\", null, false, 5.5 ]");
parse(" [1,\"A\",null,false,5.5] ");
parse(" [\"A\", [], [1,2,3], [4,5,6] ] ");
parse("[[[[[[[[1]]]]]]]]");
}
void test_object()
{
parse("{}");
parse("{ \"a\" : 10 }");
parse("{a:10}");
parse("{ a : \"Hello\" }");
parse("{a:1,b:2,c:3}");
parse("{a:{b:1,c:2,d:3}, e:{f:4,g:5.5}, h:{}, i : { } }");
}
void test_combined()
{
parse(
"\n{ // This is an object \n"
"a: [ 1, 2.1e5, .3, {a_1:1,a_2:2}], // a: is an array \n"
"b: { \"b1\":\"b1 value\" } , // b: is an object \n"
"c: [false /* means 0 */, true, /*>*/null/*<*/, \"$\"] // c: array as well \n"
"\n}\n"
);
}
void test_comments()
{
parse("// Comment ignored\n\n");
parse("// REM\n //REM\n 10 //REM");
parse("[ // REM\n 10, //REM\n 20 //REM \n ]");
parse("/**/");
parse("/*REM*/");
parse("10/*REM*/");
parse("/*REM*/10");
parse("/*R/E/M*/10");
parse("/*R//E//M*/10");
parse("// /*REM*/\n10");
parse("[ /*<<*/10/*>>*/, 20 //COMMENT\n, /*//*/30/*Value30*/ ]");
parse("/**/4/**/0", "<EXCEPTION>");
}
////////////////////////////////////////////////////////////////////////////////
void test_compose_basic()
{
compose("", "");
compose("null", "null");
compose("true", "true");
compose("false", "false");
compose("\"\"", "\"\"");
compose("[]", "[]");
compose("{}", "{}");
compose("1234", "1234");
compose("1.234", "1.234");
compose("1e3", "1000");
compose(".1", "0.1");
}
void test_compose_array()
{
compose("[]", "[]");
compose("[1]", "[1]");
compose("[1,2]", "[1,2]");
compose("[[],[]]", "[[],[]]");
compose("[[],[[1]]]", "[[],[[1]]]");
}
void test_compose_object()
{
compose("{}", "{}");
compose("{a:1}", "{\"a\":1}");
compose("{a:1}", "{a:1}", sw::json::o_no);
compose("{1:1}", "{\"1\":1}", sw::json::o_no);
compose("{_:1}", "{\"_\":1}", sw::json::o_no);
compose("{_a:1}", "{\"_a\":1}", sw::json::o_no);
compose("{abcdefghijklmnopqrstuvwxyz01234567890aaaaaaaaaaaaaaaaaaaaaaaaaaaa:1}",
"{\"abcdefghijklmnopqrstuvwxyz01234567890aaaaaaaaaaaaaaaaaaaaaaaaaaaa\":1}");
compose("{a_1:1}", "{a_1:1}", sw::json::o_no);
}
void test_compose_string()
{
compose("\"\"", "\"\"");
compose("\"H\"", "\"H\"");
compose("\"Hello\"", "\"Hello\"");
compose("\" \\\\ \"", "\" \\\\ \"");
compose("\" \\n \"", "\" \\n \"");
compose("\" \\u0009 \"", "\" \\t \"");
compose("\" \\u000a \"", "\" \\n \"");
compose("\" \\u000d \"", "\" \\r \"");
compose("\" \\u000b \"", "\" \\v \"");
compose("\" \\u0007 \"", "\" \\a \"");
compose("\" \\u000c \"", "\" \\f \"");
}
void test_compose_combined()
{
compose("[1,{a:1},2.2,.3,\"\"]", "[1,{\"a\":1},2.2,0.3,\"\"]");
compose("{c:15,b:10,a:5}", "{a:5,b:10,c:15}", sw::json::o_no);
}
////////////////////////////////////////////////////////////////////////////////
void test()
{
test_num();
test_literal();
test_string();
test_array();
test_object();
test_comments();
test_combined();
test_compose_basic();
test_compose_array();
test_compose_object();
test_compose_string();
test_compose_combined();
}