peerplays-fc/src/io/json.cpp
2019-01-07 11:13:48 -05:00

808 lines
25 KiB
C++

#include <fc/io/json.hpp>
#include <fc/exception/exception.hpp>
#include <fc/io/iostream.hpp>
#include <fc/io/buffered_iostream.hpp>
#include <fc/io/fstream.hpp>
#include <fc/io/sstream.hpp>
#include <fc/log/logger.hpp>
#include <cstdint>
#include <iostream>
#include <fstream>
#include <sstream>
#include <boost/filesystem/fstream.hpp>
namespace fc
{
// forward declarations of provided functions
template<typename T, json::parse_type parser_type> variant variant_from_stream( T& in, uint32_t max_depth );
template<typename T> char parseEscape( T& in );
template<typename T> fc::string stringFromStream( T& in );
template<typename T> bool skip_white_space( T& in );
template<typename T> fc::string stringFromToken( T& in );
template<typename T> variant_object objectFromStreamBase( T& in, std::function<std::string(T&)>& get_key, std::function<variant(T&)>& get_value );
template<typename T, json::parse_type parser_type> variant_object objectFromStream( T& in, uint32_t max_depth );
template<typename T> variants arrayFromStreamBase( T& in, std::function<variant(T&)>& get_value );
template<typename T, json::parse_type parser_type> variants arrayFromStream( T& in, uint32_t max_depth );
template<typename T, json::parse_type parser_type> variant number_from_stream( T& in );
template<typename T> variant token_from_stream( T& in );
void escape_string( const string& str, ostream& os );
template<typename T> void to_stream( T& os, const variants& a, json::output_formatting format, uint32_t max_depth );
template<typename T> void to_stream( T& os, const variant_object& o, json::output_formatting format, uint32_t max_depth );
template<typename T> void to_stream( T& os, const variant& v, json::output_formatting format, uint32_t max_depth );
fc::string pretty_print( const fc::string& v, uint8_t indent );
}
#if __cplusplus > 201402L
#define FALLTHROUGH [[fallthrough]];
#else
#define FALLTHROUGH
#endif
#include <fc/io/json_relaxed.hpp>
namespace fc
{
template<typename T>
char parseEscape( T& in )
{
if( in.peek() == '\\' )
{
try {
in.get();
switch( in.peek() )
{
case 't':
in.get();
return '\t';
case 'n':
in.get();
return '\n';
case 'r':
in.get();
return '\r';
case '\\':
in.get();
return '\\';
default:
return in.get();
}
} FC_RETHROW_EXCEPTIONS( info, "Stream ended with '\\'" );
}
FC_THROW_EXCEPTION( parse_error_exception, "Expected '\\'" );
}
template<typename T>
bool skip_white_space( T& in )
{
bool skipped = false;
while( true )
{
switch( in.peek() )
{
case ' ':
case '\t':
case '\n':
case '\r':
skipped = true;
in.get();
break;
default:
return skipped;
}
}
}
template<typename T>
fc::string stringFromStream( T& in )
{
fc::stringstream token;
try
{
char c = in.peek();
if( c != '"' )
FC_THROW_EXCEPTION( parse_error_exception,
"Expected '\"' but read '${char}'",
("char", string(&c, (&c) + 1) ) );
in.get();
while( true )
{
switch( c = in.peek() )
{
case '\\':
token << parseEscape( in );
break;
case 0x04:
FC_THROW_EXCEPTION( parse_error_exception, "EOF before closing '\"' in string '${token}'",
("token", token.str() ) );
case '"':
in.get();
return token.str();
default:
token << c;
in.get();
}
}
FC_THROW_EXCEPTION( parse_error_exception, "EOF before closing '\"' in string '${token}'",
("token", token.str() ) );
} FC_RETHROW_EXCEPTIONS( warn, "while parsing token '${token}'",
("token", token.str() ) );
}
template<typename T>
fc::string stringFromToken( T& in )
{
fc::stringstream token;
try
{
char c = in.peek();
while( true )
{
switch( c = in.peek() )
{
case '\\':
token << parseEscape( in );
break;
case '\t':
case ' ':
case '\0':
case '\n':
in.get();
return token.str();
default:
if( isalnum( c ) || c == '_' || c == '-' || c == '.' || c == ':' || c == '/' )
{
token << c;
in.get();
}
else return token.str();
}
}
return token.str();
}
catch( const fc::eof_exception& eof )
{
return token.str();
}
catch (const std::ios_base::failure&)
{
return token.str();
}
FC_RETHROW_EXCEPTIONS( warn, "while parsing token '${token}'",
("token", token.str() ) );
}
template<typename T>
variant_object objectFromStreamBase( T& in, std::function<std::string(T&)>& get_key, std::function<variant(T&)>& get_value )
{
mutable_variant_object obj;
try
{
char c = in.peek();
if( c != '{' )
FC_THROW_EXCEPTION( parse_error_exception,
"Expected '{', but read '${char}'",
("char",string(&c, &c + 1)) );
in.get();
while( in.peek() != '}' )
{
if( in.peek() == ',' )
{
in.get();
continue;
}
if( skip_white_space(in) ) continue;
string key = get_key( in );
skip_white_space(in);
if( in.peek() != ':' )
{
FC_THROW_EXCEPTION( parse_error_exception, "Expected ':' after key \"${key}\"",
("key", key) );
}
in.get();
auto val = get_value( in );
obj(std::move(key),std::move(val));
}
if( in.peek() == '}' )
{
in.get();
return obj;
}
FC_THROW_EXCEPTION( parse_error_exception, "Expected '}' after ${variant}", ("variant", obj ) );
}
catch( const fc::eof_exception& e )
{
FC_THROW_EXCEPTION( parse_error_exception, "Unexpected EOF: ${e}", ("e", e.to_detail_string() ) );
}
catch( const std::ios_base::failure& e )
{
FC_THROW_EXCEPTION( parse_error_exception, "Unexpected EOF: ${e}", ("e", e.what() ) );
} FC_RETHROW_EXCEPTIONS( warn, "Error parsing object" );
}
template<typename T, json::parse_type parser_type>
variant_object objectFromStream( T& in, uint32_t max_depth )
{
std::function<std::string(T&)> get_key = []( T& in ){ return stringFromStream( in ); };
std::function<variant(T&)> get_value = [max_depth]( T& in ){ return variant_from_stream<T, parser_type>( in, max_depth ); };
return objectFromStreamBase<T>( in, get_key, get_value );
}
template<typename T>
variants arrayFromStreamBase( T& in, std::function<variant(T&)>& get_value )
{
variants ar;
try
{
if( in.peek() != '[' )
FC_THROW_EXCEPTION( parse_error_exception, "Expected '['" );
in.get();
while( in.peek() != ']' )
{
if( in.peek() == ',' )
{
in.get();
continue;
}
if( skip_white_space(in) ) continue;
ar.push_back( get_value(in) );
}
if( in.peek() != ']' )
FC_THROW_EXCEPTION( parse_error_exception, "Expected ']' after parsing ${variant}",
("variant", ar) );
in.get();
} FC_RETHROW_EXCEPTIONS( warn, "Attempting to parse array ${array}",
("array", ar ) );
return ar;
}
template<typename T, json::parse_type parser_type>
variants arrayFromStream( T& in, uint32_t max_depth )
{
std::function<variant(T&)> get_value = [max_depth]( T& in ){ return variant_from_stream<T, parser_type>( in, max_depth ); };
return arrayFromStreamBase<T>( in, get_value );
}
template<typename T, json::parse_type parser_type>
variant number_from_stream( T& in )
{
fc::stringstream ss;
bool dot = false;
bool neg = false;
if( in.peek() == '-')
{
neg = true;
ss.put( in.get() );
}
bool done = false;
try
{
char c;
while((c = in.peek()) && !done)
{
switch( c )
{
case '.':
if (dot)
FC_THROW_EXCEPTION(parse_error_exception, "Can't parse a number with two decimal places");
dot = true;
FALLTHROUGH
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
ss.put( in.get() );
break;
default:
if( isalnum( c ) )
{
return ss.str() + stringFromToken( in );
}
done = true;
break;
}
}
}
catch (fc::eof_exception&)
{ // EOF ends the loop
}
catch (const std::ios_base::failure&)
{ // read error ends the loop
}
fc::string str = ss.str();
if (str == "-." || str == "." || str == "-") // check the obviously wrong things we could have encountered
FC_THROW_EXCEPTION(parse_error_exception, "Can't parse token \"${token}\" as a JSON numeric constant", ("token", str));
if( dot )
return
#ifdef WITH_EXOTIC_JSON_PARSERS
parser_type == json::legacy_parser_with_string_doubles ? variant(str) :
#endif
variant(to_double(str));
if( neg )
return to_int64(str);
return to_uint64(str);
}
template<typename T>
variant token_from_stream( T& in )
{
std::stringstream ss;
ss.exceptions( std::ifstream::badbit );
bool received_eof = false;
bool done = false;
try
{
char c;
while((c = in.peek()) && !done)
{
switch( c )
{
case 'n':
case 'u':
case 'l':
case 't':
case 'r':
case 'e':
case 'f':
case 'a':
case 's':
ss.put( in.get() );
break;
default:
done = true;
break;
}
}
}
catch (fc::eof_exception&)
{
received_eof = true;
}
catch (const std::ios_base::failure&)
{
received_eof = true;
}
// we can get here either by processing a delimiter as in "null,"
// an EOF like "null<EOF>", or an invalid token like "nullZ"
fc::string str = ss.str();
if( str == "null" )
return variant();
if( str == "true" )
return true;
if( str == "false" )
return false;
else
{
if (received_eof)
{
if (str.empty())
FC_THROW_EXCEPTION( parse_error_exception, "Unexpected EOF" );
else
return str;
}
else
{
// if we've reached this point, we've either seen a partial
// token ("tru<EOF>") or something our simple parser couldn't
// make out ("falfe")
// A strict JSON parser would signal this as an error, but we
// will just treat the malformed token as an un-quoted string.
return str + stringFromToken(in);
}
}
}
template<typename T, json::parse_type parser_type>
variant variant_from_stream( T& in, uint32_t max_depth )
{
if( max_depth == 0 )
FC_THROW_EXCEPTION( parse_error_exception, "Too many nested items in JSON input!" );
skip_white_space(in);
signed char c = in.peek();
switch( c )
{
case '"':
return stringFromStream( in );
case '{':
return objectFromStream<T, parser_type>( in, max_depth - 1 );
case '[':
return arrayFromStream<T, parser_type>( in, max_depth - 1 );
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return number_from_stream<T, parser_type>( in );
// null, true, false, or 'warning' / string
case 'n':
case 't':
case 'f':
return token_from_stream( in );
case 0x04: // ^D end of transmission
case EOF:
FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" );
case 0:
if( parser_type == fc::json::broken_nul_parser )
return variant();
FALLTHROUGH
default:
FC_THROW_EXCEPTION( parse_error_exception, "Unexpected char '${c}' in \"${s}\"",
("c", c)("s", stringFromToken(in)) );
}
}
variant json::from_string( const std::string& utf8_str, parse_type ptype, uint32_t max_depth )
{ try {
fc::istream_ptr in( new fc::stringstream( utf8_str ) );
fc::buffered_istream bin( in );
return from_stream( bin, ptype, max_depth );
} FC_RETHROW_EXCEPTIONS( warn, "", ("str",utf8_str) ) }
variants json::variants_from_string( const std::string& utf8_str, parse_type ptype, uint32_t max_depth )
{
variants result;
try {
fc::stringstream in( utf8_str );
while( true )
result.push_back(json_relaxed::variant_from_stream<fc::stringstream, false>( in, max_depth ));
} catch ( const fc::eof_exception& ) {
return result;
} FC_RETHROW_EXCEPTIONS( warn, "", ("str",utf8_str) )
}
/**
* Convert '\t', '\a', '\n', '\\' and '"' to "\t\a\n\\\""
*
* All other characters are printed as UTF8.
*/
void escape_string( const string& str, ostream& os )
{
os << '"';
for( auto itr = str.begin(); itr != str.end(); ++itr )
{
switch( *itr )
{
case '\b': // \x08
os << "\\b";
break;
case '\f': // \x0c
os << "\\f";
break;
case '\n': // \x0a
os << "\\n";
break;
case '\r': // \x0d
os << "\\r";
break;
case '\t': // \x09
os << "\\t";
break;
case '\\':
os << "\\\\";
break;
case '\"':
os << "\\\"";
break;
case '\x00': os << "\\u0000"; break;
case '\x01': os << "\\u0001"; break;
case '\x02': os << "\\u0002"; break;
case '\x03': os << "\\u0003"; break;
case '\x04': os << "\\u0004"; break;
case '\x05': os << "\\u0005"; break;
case '\x06': os << "\\u0006"; break;
case '\x07': os << "\\u0007"; break; // \a is not valid JSON
// case '\x08': os << "\\u0008"; break; // \b
// case '\x09': os << "\\u0009"; break; // \t
// case '\x0a': os << "\\u000a"; break; // \n
case '\x0b': os << "\\u000b"; break;
// case '\x0c': os << "\\u000c"; break; // \f
// case '\x0d': os << "\\u000d"; break; // \r
case '\x0e': os << "\\u000e"; break;
case '\x0f': os << "\\u000f"; break;
case '\x10': os << "\\u0010"; break;
case '\x11': os << "\\u0011"; break;
case '\x12': os << "\\u0012"; break;
case '\x13': os << "\\u0013"; break;
case '\x14': os << "\\u0014"; break;
case '\x15': os << "\\u0015"; break;
case '\x16': os << "\\u0016"; break;
case '\x17': os << "\\u0017"; break;
case '\x18': os << "\\u0018"; break;
case '\x19': os << "\\u0019"; break;
case '\x1a': os << "\\u001a"; break;
case '\x1b': os << "\\u001b"; break;
case '\x1c': os << "\\u001c"; break;
case '\x1d': os << "\\u001d"; break;
case '\x1e': os << "\\u001e"; break;
case '\x1f': os << "\\u001f"; break;
default:
os << *itr;
}
}
os << '"';
}
ostream& json::to_stream( ostream& out, const fc::string& str )
{
escape_string( str, out );
return out;
}
template<typename T>
void to_stream( T& os, const variants& a, json::output_formatting format, uint32_t max_depth )
{
os << '[';
auto itr = a.begin();
while( itr != a.end() )
{
to_stream( os, *itr, format, max_depth );
++itr;
if( itr != a.end() )
os << ',';
}
os << ']';
}
template<typename T>
void to_stream( T& os, const variant_object& o, json::output_formatting format, uint32_t max_depth )
{
os << '{';
auto itr = o.begin();
while( itr != o.end() )
{
escape_string( itr->key(), os );
os << ':';
to_stream( os, itr->value(), format, max_depth );
++itr;
if( itr != o.end() )
os << ',';
}
os << '}';
}
template<typename T>
void to_stream( T& os, const variant& v, json::output_formatting format, uint32_t max_depth )
{
FC_ASSERT( max_depth > 0, "Too many nested objects!" );
switch( v.get_type() )
{
case variant::null_type:
os << "null";
return;
case variant::int64_type:
if( format == json::stringify_large_ints_and_doubles &&
( v.as_int64() > INT32_MAX || v.as_int64() < INT32_MIN ) )
os << '"'<<v.as_string()<<'"';
else
os << v.as_int64();
return;
case variant::uint64_type:
if( format == json::stringify_large_ints_and_doubles &&
v.as_uint64() > 0xffffffff )
os << '"'<<v.as_string()<<'"';
else
os << v.as_uint64();
return;
case variant::double_type:
if (format == json::stringify_large_ints_and_doubles)
os << '"'<<v.as_string()<<'"';
else
os << v.as_string();
return;
case variant::bool_type:
os << v.as_string();
return;
case variant::string_type:
escape_string( v.get_string(), os );
return;
case variant::blob_type:
escape_string( v.as_string(), os );
return;
case variant::array_type:
to_stream( os, v.get_array(), format, max_depth - 1 );
return;
case variant::object_type:
to_stream(os, v.get_object(), format, max_depth - 1 );
return;
default:
FC_THROW_EXCEPTION( fc::invalid_arg_exception, "Unsupported variant type: " + std::to_string(v.get_type()) );
}
}
fc::string json::to_string( const variant& v, output_formatting format, uint32_t max_depth )
{
fc::stringstream ss;
fc::to_stream( ss, v, format, max_depth );
return ss.str();
}
fc::string pretty_print( const fc::string& v, uint8_t indent ) {
int level = 0;
fc::stringstream ss;
bool first = false;
bool quote = false;
bool escape = false;
for( uint32_t i = 0; i < v.size(); ++i ) {
switch( v[i] ) {
case '\\':
if( !escape ) {
if( quote )
escape = true;
} else { escape = false; }
ss<<v[i];
break;
case ':':
if( !quote ) {
ss<<": ";
} else {
ss<<':';
}
break;
case '"':
if( first ) {
ss<<'\n';
for( int i = 0; i < level*indent; ++i ) ss<<' ';
first = false;
}
if( !escape ) {
quote = !quote;
}
escape = false;
ss<<'"';
break;
case '{':
case '[':
ss<<v[i];
if( !quote ) {
++level;
first = true;
}else {
escape = false;
}
break;
case '}':
case ']':
if( !quote ) {
if( v[i-1] != '[' && v[i-1] != '{' ) {
ss<<'\n';
}
--level;
if( !first ) {
for( int i = 0; i < level*indent; ++i ) ss<<' ';
}
first = false;
ss<<v[i];
break;
} else {
escape = false;
ss<<v[i];
}
break;
case ',':
if( !quote ) {
ss<<',';
first = true;
} else {
escape = false;
ss<<',';
}
break;
case 'n':
//If we're in quotes and see a \n, just print it literally but unset the escape flag.
if( quote && escape )
escape = false;
FALLTHROUGH
default:
if( first ) {
ss<<'\n';
for( int i = 0; i < level*indent; ++i ) ss<<' ';
first = false;
}
ss << v[i];
}
}
return ss.str();
}
fc::string json::to_pretty_string( const variant& v, output_formatting format, uint32_t max_depth )
{
return pretty_print(to_string(v, format, max_depth), 2);
}
void json::save_to_file( const variant& v, const fc::path& fi, bool pretty, output_formatting format, uint32_t max_depth )
{
if( pretty )
{
auto str = json::to_pretty_string( v, format, max_depth );
fc::ofstream o(fi);
o.write( str.c_str(), str.size() );
}
else
{
fc::ofstream o(fi);
fc::to_stream( o, v, format, max_depth );
}
}
variant json::from_file( const fc::path& p, parse_type ptype, uint32_t max_depth )
{
fc::istream_ptr in( new fc::ifstream( p ) );
fc::buffered_istream bin( in );
return from_stream( bin, ptype, max_depth );
}
variant json::from_stream( buffered_istream& in, parse_type ptype, uint32_t max_depth )
{
switch( ptype )
{
case legacy_parser:
return variant_from_stream<fc::buffered_istream, legacy_parser>( in, max_depth );
#ifdef WITH_EXOTIC_JSON_PARSERS
case legacy_parser_with_string_doubles:
return variant_from_stream<fc::buffered_istream, legacy_parser_with_string_doubles>( in, max_depth );
case strict_parser:
return json_relaxed::variant_from_stream<buffered_istream, true>( in, max_depth );
case relaxed_parser:
return json_relaxed::variant_from_stream<buffered_istream, false>( in, max_depth );
#endif
case broken_nul_parser:
return variant_from_stream<fc::buffered_istream, broken_nul_parser>( in, max_depth );
default:
FC_ASSERT( false, "Unknown JSON parser type {ptype}", ("ptype", ptype) );
}
}
ostream& json::to_stream( ostream& out, const variant& v, output_formatting format, uint32_t max_depth )
{
fc::to_stream( out, v, format, max_depth );
return out;
}
ostream& json::to_stream( ostream& out, const variants& v, output_formatting format, uint32_t max_depth )
{
fc::to_stream( out, v, format, max_depth );
return out;
}
ostream& json::to_stream( ostream& out, const variant_object& v, output_formatting format, uint32_t max_depth )
{
fc::to_stream( out, v, format, max_depth );
return out;
}
bool json::is_valid( const std::string& utf8_str, parse_type ptype, uint32_t max_depth )
{
if( utf8_str.size() == 0 ) return false;
fc::istream_ptr in( new fc::stringstream( utf8_str ) );
fc::buffered_istream bin( in );
from_stream( bin, ptype, max_depth );
try { bin.peek(); } catch ( const eof_exception& e ) { return true; }
return false;
}
} // fc