Thrift: Add a full-featured JSON protocol for C++.

Summary:
This change adds a new and exciting protocol to Thrift.  It uses
RFC-compliant JSON as the wire protocol and is fully human readable.
(once a little whitespace has been inserted.)  Unlike the existing
JSON protocol for Java, which is intended to allow Thrift data to be
transferred to scripting languages, this protocol is lossless and fully
read-write.  It was written by Chad Walters of Powerset and reviewed
by David Reiss.

Tested by running make check.


git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@665482 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
David Reiss 2008-02-18 01:49:37 +00:00
parent c005b1b65e
commit db0ea15310
8 changed files with 1516 additions and 0 deletions

View File

@ -31,6 +31,8 @@ libthrift_la_SOURCES = src/Thrift.cpp \
src/protocol/TBinaryProtocol.cpp \
src/protocol/TDebugProtocol.cpp \
src/protocol/TDenseProtocol.cpp \
src/protocol/TJSONProtocol.cpp \
src/protocol/TBase64Utils.cpp \
src/transport/TTransportException.cpp \
src/transport/TFileTransport.cpp \
src/transport/THttpClient.cpp \

View File

@ -0,0 +1,58 @@
// Copyright (c) 2006- Facebook
// Distributed under the Thrift Software License
//
// See accompanying file LICENSE or visit the Thrift site at:
// http://developers.facebook.com/thrift/
#include "TBase64Utils.h"
#include <boost/static_assert.hpp>
using std::string;
namespace facebook { namespace thrift { namespace protocol {
static const uint8_t *kBase64EncodeTable = (const uint8_t *)
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
void base64_encode(const uint8_t *in, uint32_t len, uint8_t *buf) {
buf[0] = kBase64EncodeTable[(in[0] >> 2) & 0x3F];
if (len == 3) {
buf[1] = kBase64EncodeTable[((in[0] << 4) + (in[1] >> 4)) & 0x3f];
buf[2] = kBase64EncodeTable[((in[1] << 2) + (in[2] >> 6)) & 0x3f];
buf[3] = kBase64EncodeTable[in[2] & 0x3f];
} else if (len == 2) {
buf[1] = kBase64EncodeTable[((in[0] << 4) + (in[1] >> 4)) & 0x3f];
buf[2] = kBase64EncodeTable[(in[1] << 2) & 0x3f];
} else { // len == 1
buf[1] = kBase64EncodeTable[(in[0] << 4) & 0x3f];
}
}
static const uint8_t kBase64DecodeTable[128] ={
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1
};
void base64_decode(uint8_t *buf, uint32_t len) {
buf[0] = (kBase64DecodeTable[buf[0]] << 2) |
(kBase64DecodeTable[buf[1]] >> 4);
if (len > 2) {
buf[1] = ((kBase64DecodeTable[buf[1]] << 4) & 0xf0) |
(kBase64DecodeTable[buf[2]] >> 2);
if (len > 3) {
buf[2] = ((kBase64DecodeTable[buf[2]] << 6) & 0xc0) |
(kBase64DecodeTable[buf[3]]);
}
}
}
}}} // facebook::thrift::protocol

View File

@ -0,0 +1,28 @@
// Copyright (c) 2006- Facebook
// Distributed under the Thrift Software License
//
// See accompanying file LICENSE or visit the Thrift site at:
// http://developers.facebook.com/thrift/
#ifndef _THRIFT_PROTOCOL_TBASE64UTILS_H_
#define _THRIFT_PROTOCOL_TBASE64UTILS_H_
#include <string>
namespace facebook { namespace thrift { namespace protocol {
// in must be at least len bytes
// len must be 1, 2, or 3
// buf must be a buffer of at least 4 bytes and may not overlap in
// the data is not padded with '='; the caller can do this if desired
void base64_encode(const uint8_t *in, uint32_t len, uint8_t *buf);
// buf must be a buffer of at least 4 bytes and contain base64 encoded values
// buf will be changed to contain output bytes
// len is number of bytes to consume from input (must be 2, 3, or 4)
// no '=' padding should be included in the input
void base64_decode(uint8_t *buf, uint32_t len);
}}} // facebook::thrift::protocol
#endif // #define _THRIFT_PROTOCOL_TBASE64UTILS_H_

View File

@ -0,0 +1,995 @@
// Copyright (c) 2006- Facebook
// Distributed under the Thrift Software License
//
// See accompanying file LICENSE or visit the Thrift site at:
// http://developers.facebook.com/thrift/
#include "TJSONProtocol.h"
#include <math.h>
#include "TBase64Utils.h"
#include <transport/TTransportException.h>
using namespace facebook::thrift::transport;
namespace facebook { namespace thrift { namespace protocol {
// Static data
static const uint8_t kJSONObjectStart = '{';
static const uint8_t kJSONObjectEnd = '}';
static const uint8_t kJSONArrayStart = '[';
static const uint8_t kJSONArrayEnd = ']';
static const uint8_t kJSONNewline = '\n';
static const uint8_t kJSONPairSeparator = ':';
static const uint8_t kJSONElemSeparator = ',';
static const uint8_t kJSONBackslash = '\\';
static const uint8_t kJSONStringDelimiter = '"';
static const uint8_t kJSONZeroChar = '0';
static const uint8_t kJSONEscapeChar = 'u';
static const std::string kJSONEscapePrefix("\\u00");
static const uint8_t kThriftVersion1 = 1;
static const std::string kThriftNan("NaN");
static const std::string kThriftInfinity("Infinity");
static const std::string kThriftNegativeInfinity("-Infinity");
static const std::string kTypeNameBool("tf");
static const std::string kTypeNameByte("i8");
static const std::string kTypeNameI16("i16");
static const std::string kTypeNameI32("i32");
static const std::string kTypeNameI64("i64");
static const std::string kTypeNameDouble("dbl");
static const std::string kTypeNameStruct("rec");
static const std::string kTypeNameString("str");
static const std::string kTypeNameMap("map");
static const std::string kTypeNameList("lst");
static const std::string kTypeNameSet("set");
static const std::string &getTypeNameForTypeID(TType typeID) {
switch (typeID) {
case T_BOOL:
return kTypeNameBool;
case T_BYTE:
return kTypeNameByte;
case T_I16:
return kTypeNameI16;
case T_I32:
return kTypeNameI32;
case T_I64:
return kTypeNameI64;
case T_DOUBLE:
return kTypeNameDouble;
case T_STRING:
return kTypeNameString;
case T_STRUCT:
return kTypeNameStruct;
case T_MAP:
return kTypeNameMap;
case T_SET:
return kTypeNameSet;
case T_LIST:
return kTypeNameList;
default:
throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
"Unrecognized type");
}
}
static TType getTypeIDForTypeName(const std::string &name) {
TType result = T_STOP; // Sentinel value
switch (name[0]) {
case 'd':
result = T_DOUBLE;
break;
case 'i':
switch (name[1]) {
case '8':
result = T_BYTE;
break;
case '1':
result = T_I16;
break;
case '3':
result = T_I32;
break;
case '6':
result = T_I64;
break;
}
break;
case 'l':
result = T_LIST;
break;
case 'm':
result = T_MAP;
break;
case 'r':
result = T_STRUCT;
break;
case 's':
if (name[1] == 't') {
result = T_STRING;
}
else if (name[1] == 'e') {
result = T_SET;
}
break;
case 't':
result = T_BOOL;
break;
}
if (result == T_STOP) {
throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
"Unrecognized type");
}
return result;
}
// This table describes the handling for the first 0x30 characters
// 0 : escape using "\u00xx" notation
// 1 : just output index
// <other> : escape using "\<other>" notation
static const uint8_t kJSONCharTable[0x30] = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
0, 0, 0, 0, 0, 0, 0, 0,'b','t','n', 0,'f','r', 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
1, 1,'"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
};
// This string's characters must match up with the elements in kEscapeCharVals.
// I don't have '/' on this list even though it appears on www.json.org --
// it is not in the RFC
const static std::string kEscapeChars("\"\\bfnrt");
// The elements of this array must match up with the sequence of characters in
// kEscapeChars
const static uint8_t kEscapeCharVals[7] = {
'"', '\\', '\b', '\f', '\n', '\r', '\t',
};
// Static helper functions
// Read 1 character from the transport trans and verify that it is the
// expected character ch.
// Throw a protocol exception if it is not.
static uint32_t readSyntaxChar(TTransport &trans, uint8_t ch) {
uint8_t b[1];
trans.readAll(b, 1);
if (b[0] != ch) {
throw TProtocolException(TProtocolException::INVALID_DATA,
"Expected \'" + std::string((char *)&ch, 1) +
"\'; got \'" + std::string((char *)b, 1) +
"\'.");
}
return 1;
}
// Borrow 1 byte from the transport trans and return the value read
// Throw a transport exception if the byte cannot be borrowed
static uint8_t borrowByte(TTransport &trans) {
uint8_t b[1];
uint32_t len = 1;
const uint8_t *buf = trans.borrow(b, &len);
if (!buf || !len) {
throw TTransportException(TTransportException::UNKNOWN,
"Could not borrow 1 byte from transport.");
}
return *buf;
}
// Return the integer value of a hex character ch.
// Throw a protocol exception if the character is not [0-9a-f].
static uint8_t hexVal(uint8_t ch) {
if ((ch >= '0') && (ch <= '9')) {
return ch - '0';
}
else if ((ch >= 'a') && (ch <= 'f')) {
return ch - 'a';
}
else {
throw TProtocolException(TProtocolException::INVALID_DATA,
"Expected hex val ([0-9a-f]); got \'"
+ std::string((char *)&ch, 1) + "\'.");
}
}
// Return the hex character representing the integer val. The value is masked
// to make sure it is in the correct range.
static uint8_t hexChar(uint8_t val) {
val &= 0x0F;
if (val < 10) {
return val + '0';
}
else {
return val + 'a';
}
}
// Return true if the character ch is in [-+0-9.Ee]; false otherwise
static bool isJSONNumeric(uint8_t ch) {
switch (ch) {
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'E':
case 'e':
return true;
}
return false;
}
/**
* Class to serve as base JSON context and base class for other context
* implementations
*/
class TJSONContext {
public:
TJSONContext() {};
virtual ~TJSONContext() {};
/**
* Write context data to the transport. Default is to do nothing.
*/
virtual uint32_t write(TTransport &trans) {
return 0;
};
/**
* Read context data from the transport. Default is to do nothing.
*/
virtual uint32_t read(TTransport &trans) {
return 0;
};
/**
* Return true if numbers need to be escaped as strings in this context.
* Default behavior is to return false.
*/
virtual bool escapeNum() {
return false;
}
};
// Context class for object member key-value pairs
class JSONPairContext : public TJSONContext {
public:
JSONPairContext() :
first_(true),
colon_(true) {
}
uint32_t write(TTransport &trans) {
if (first_) {
first_ = false;
colon_ = true;
return 0;
}
else {
trans.write(colon_ ? &kJSONPairSeparator : &kJSONElemSeparator, 1);
colon_ = !colon_;
return 1;
}
}
uint32_t read(TTransport &trans) {
if (first_) {
first_ = false;
colon_ = true;
return 0;
}
else {
uint8_t ch = (colon_ ? kJSONPairSeparator : kJSONElemSeparator);
colon_ = !colon_;
return readSyntaxChar(trans, ch);
}
}
// Numbers must be turned into strings if they are the key part of a pair
virtual bool escapeNum() {
return colon_;
}
private:
bool first_;
bool colon_;
};
// Context class for lists
class JSONListContext : public TJSONContext {
public:
JSONListContext() :
first_(true) {
}
uint32_t write(TTransport &trans) {
if (first_) {
first_ = false;
return 0;
}
else {
trans.write(&kJSONElemSeparator, 1);
return 1;
}
}
uint32_t read(TTransport &trans) {
if (first_) {
first_ = false;
return 0;
}
else {
return readSyntaxChar(trans, kJSONElemSeparator);
}
}
private:
bool first_;
};
TJSONProtocol::TJSONProtocol(boost::shared_ptr<TTransport> ptrans) :
TProtocol(ptrans),
context_(new TJSONContext()) {
}
TJSONProtocol::~TJSONProtocol() {}
void TJSONProtocol::pushContext(boost::shared_ptr<TJSONContext> c) {
contexts_.push(context_);
context_ = c;
}
void TJSONProtocol::popContext() {
context_ = contexts_.top();
contexts_.pop();
}
// Write the character ch as a JSON escape sequence ("\u00xx")
uint32_t TJSONProtocol::writeJSONEscapeChar(uint8_t ch) {
trans_->write((const uint8_t *)kJSONEscapePrefix.c_str(),
kJSONEscapePrefix.length());
uint8_t outCh = hexChar(ch >> 4);
trans_->write(&outCh, 1);
outCh = hexChar(ch);
trans_->write(&outCh, 1);
return 6;
}
// Write the character ch as part of a JSON string, escaping as appropriate.
uint32_t TJSONProtocol::writeJSONChar(uint8_t ch) {
if (ch >= 0x30) {
if (ch == kJSONBackslash) { // Only special character >= 0x30 is '\'
trans_->write(&kJSONBackslash, 1);
trans_->write(&kJSONBackslash, 1);
return 2;
}
else {
trans_->write(&ch, 1);
return 1;
}
}
else {
uint8_t outCh = kJSONCharTable[ch];
// Check if regular character, backslash escaped, or JSON escaped
if (outCh == 1) {
trans_->write(&ch, 1);
return 1;
}
else if (outCh > 1) {
trans_->write(&kJSONBackslash, 1);
trans_->write(&outCh, 1);
return 2;
}
else {
return writeJSONEscapeChar(ch);
}
}
}
// Write out the contents of the string str as a JSON string, escaping
// characters as appropriate.
uint32_t TJSONProtocol::writeJSONString(const std::string &str) {
uint32_t result = context_->write(*trans_);
result += 2; // For quotes
trans_->write(&kJSONStringDelimiter, 1);
std::string::const_iterator iter(str.begin());
std::string::const_iterator end(str.end());
while (iter != end) {
result += writeJSONChar(*iter++);
}
trans_->write(&kJSONStringDelimiter, 1);
return result;
}
// Write out the contents of the string as JSON string, base64-encoding
// the string's contents, and escaping as appropriate
uint32_t TJSONProtocol::writeJSONBase64(const std::string &str) {
uint32_t result = context_->write(*trans_);
result += 2; // For quotes
trans_->write(&kJSONStringDelimiter, 1);
uint8_t b[4];
const uint8_t *bytes = (const uint8_t *)str.c_str();
uint32_t len = str.length();
while (len >= 3) {
// Encode 3 bytes at a time
base64_encode(bytes, 3, b);
trans_->write(b, 4);
result += 4;
bytes += 3;
len -=3;
}
if (len) { // Handle remainder
base64_encode(bytes, len, b);
trans_->write(b, len + 1);
result += len + 1;
}
trans_->write(&kJSONStringDelimiter, 1);
return result;
}
// Convert the given integer type to a JSON number, or a string
// if the context requires it (eg: key in a map pair).
template <typename NumberType>
uint32_t TJSONProtocol::writeJSONInteger(NumberType num) {
uint32_t result = context_->write(*trans_);
std::string val(boost::lexical_cast<std::string>(num));
bool escapeNum = context_->escapeNum();
if (escapeNum) {
trans_->write(&kJSONStringDelimiter, 1);
result += 1;
}
trans_->write((const uint8_t *)val.c_str(), val.length());
result += val.length();
if (escapeNum) {
trans_->write(&kJSONStringDelimiter, 1);
result += 1;
}
return result;
}
// Convert the given double to a JSON string, which is either the number,
// "NaN" or "Infinity" or "-Infinity".
uint32_t TJSONProtocol::writeJSONDouble(double num) {
uint32_t result = context_->write(*trans_);
std::string val(boost::lexical_cast<std::string>(num));
// Normalize output of boost::lexical_cast for NaNs and Infinities
bool special = false;
switch (val[0]) {
case 'N':
case 'n':
val = kThriftNan;
special = true;
break;
case 'I':
case 'i':
val = kThriftInfinity;
special = true;
break;
case '-':
if ((val[1] == 'I') || (val[1] == 'i')) {
val = kThriftNegativeInfinity;
special = true;
}
break;
}
bool escapeNum = special || context_->escapeNum();
if (escapeNum) {
trans_->write(&kJSONStringDelimiter, 1);
result += 1;
}
trans_->write((const uint8_t *)val.c_str(), val.length());
result += val.length();
if (escapeNum) {
trans_->write(&kJSONStringDelimiter, 1);
result += 1;
}
return result;
}
uint32_t TJSONProtocol::writeJSONObjectStart() {
uint32_t result = context_->write(*trans_);
trans_->write(&kJSONObjectStart, 1);
pushContext(boost::shared_ptr<TJSONContext>(new JSONPairContext()));
return result + 1;
}
uint32_t TJSONProtocol::writeJSONObjectEnd() {
popContext();
trans_->write(&kJSONObjectEnd, 1);
return 1;
}
uint32_t TJSONProtocol::writeJSONArrayStart() {
uint32_t result = context_->write(*trans_);
trans_->write(&kJSONArrayStart, 1);
pushContext(boost::shared_ptr<TJSONContext>(new JSONListContext()));
return result + 1;
}
uint32_t TJSONProtocol::writeJSONArrayEnd() {
popContext();
trans_->write(&kJSONArrayEnd, 1);
return 1;
}
uint32_t TJSONProtocol::writeMessageBegin(const std::string& name,
const TMessageType messageType,
const int32_t seqid) {
uint32_t result = writeJSONArrayStart();
result += writeJSONInteger(kThriftVersion1);
result += writeJSONString(name);
result += writeJSONInteger(messageType);
result += writeJSONInteger(seqid);
return result;
}
uint32_t TJSONProtocol::writeMessageEnd() {
return writeJSONArrayEnd();
}
uint32_t TJSONProtocol::writeStructBegin(const std::string& name) {
return writeJSONObjectStart();
}
uint32_t TJSONProtocol::writeStructEnd() {
return writeJSONObjectEnd();
}
uint32_t TJSONProtocol::writeFieldBegin(const std::string& name,
const TType fieldType,
const int16_t fieldId) {
uint32_t result = writeJSONInteger(fieldId);
result += writeJSONObjectStart();
result += writeJSONString(getTypeNameForTypeID(fieldType));
return result;
}
uint32_t TJSONProtocol::writeFieldEnd() {
return writeJSONObjectEnd();
}
uint32_t TJSONProtocol::writeFieldStop() {
return 0;
}
uint32_t TJSONProtocol::writeMapBegin(const TType keyType,
const TType valType,
const uint32_t size) {
uint32_t result = writeJSONArrayStart();
result += writeJSONString(getTypeNameForTypeID(keyType));
result += writeJSONString(getTypeNameForTypeID(valType));
result += writeJSONInteger((int64_t)size);
result += writeJSONObjectStart();
return result;
}
uint32_t TJSONProtocol::writeMapEnd() {
return writeJSONObjectEnd() + writeJSONArrayEnd();
}
uint32_t TJSONProtocol::writeListBegin(const TType elemType,
const uint32_t size) {
uint32_t result = writeJSONArrayStart();
result += writeJSONString(getTypeNameForTypeID(elemType));
result += writeJSONInteger((int64_t)size);
return result;
}
uint32_t TJSONProtocol::writeListEnd() {
return writeJSONArrayEnd();
}
uint32_t TJSONProtocol::writeSetBegin(const TType elemType,
const uint32_t size) {
uint32_t result = writeJSONArrayStart();
result += writeJSONString(getTypeNameForTypeID(elemType));
result += writeJSONInteger((int64_t)size);
return result;
}
uint32_t TJSONProtocol::writeSetEnd() {
return writeJSONArrayEnd();
}
uint32_t TJSONProtocol::writeBool(const bool value) {
return writeJSONInteger(value);
}
uint32_t TJSONProtocol::writeByte(const int8_t byte) {
// writeByte() must be handled properly becuase boost::lexical cast sees
// int8_t as a text type instead of an integer type
return writeJSONInteger((int16_t)byte);
}
uint32_t TJSONProtocol::writeI16(const int16_t i16) {
return writeJSONInteger(i16);
}
uint32_t TJSONProtocol::writeI32(const int32_t i32) {
return writeJSONInteger(i32);
}
uint32_t TJSONProtocol::writeI64(const int64_t i64) {
return writeJSONInteger(i64);
}
uint32_t TJSONProtocol::writeDouble(const double dub) {
return writeJSONDouble(dub);
}
uint32_t TJSONProtocol::writeString(const std::string& str) {
return writeJSONString(str);
}
uint32_t TJSONProtocol::writeBinary(const std::string& str) {
return writeJSONBase64(str);
}
/**
* Reading functions
*/
// Reads 1 byte and verifires that it matches ch.
uint32_t TJSONProtocol::readJSONSyntaxChar(uint8_t ch) {
return readSyntaxChar(*trans_, ch);
}
// Decodes the four hex parts of a JSON escaped string character and returns
// the character via out. The first two characters must be "00".
uint32_t TJSONProtocol::readJSONEscapeChar(uint8_t *out) {
uint8_t b[2];
readJSONSyntaxChar(kJSONZeroChar);
readJSONSyntaxChar(kJSONZeroChar);
trans_->readAll(b, 2);
*out = (hexVal(b[0]) << 4) + hexVal(b[1]);
return 4;
}
// Decodes a JSON string, including unescaping, and returns the string via str
uint32_t TJSONProtocol::readJSONString(std::string &str, bool skipContext) {
uint32_t result = (skipContext ? 0 : context_->read(*trans_));
result += readJSONSyntaxChar(kJSONStringDelimiter);
uint8_t b[1];
while (true) {
result += trans_->readAll(b, 1);
if (b[0] == kJSONStringDelimiter) {
break;
}
if (b[0] == kJSONBackslash) {
result += trans_->readAll(b, 1);
if (b[0] == kJSONEscapeChar) {
result += readJSONEscapeChar(&b[0]);
}
else {
size_t pos = kEscapeChars.find(b[0]);
if (pos == std::string::npos) {
throw TProtocolException(TProtocolException::INVALID_DATA,
"Expected control char, got '" +
std::string((char *)b, 1) + "'.");
}
b[0] = kEscapeCharVals[pos];
}
}
str += b[0];
}
return result;
}
// Reads a block of base64 characters, decoding it, and returns via str
uint32_t TJSONProtocol::readJSONBase64(std::string &str) {
std::string tmp;
uint32_t result = readJSONString(tmp);
uint8_t *b = (uint8_t *)tmp.c_str();
uint32_t len = tmp.length();
while (len >= 4) {
base64_decode(b, 4);
str.append((const char *)b, 3);
b += 4;
len -= 4;
}
// Don't decode if we hit the end or got a single leftover byte (invalid
// base64 but legal for skip of regular string type)
if (len > 1) {
base64_decode(b, len);
str.append((const char *)b, len - 1);
}
return result;
}
// Reads a sequence of characters, stopping at the first one that is not
// a valid JSON numeric character.
uint32_t TJSONProtocol::readJSONNumericChars(std::string &str) {
uint32_t result = 0;
while (true) {
uint8_t ch = borrowByte(*trans_);
if (!isJSONNumeric(ch)) {
break;
}
trans_->consume(1);
str += ch;
++result;
}
return result;
}
// Reads a sequence of characters and assembles them into a number,
// returning them via num
template <typename NumberType>
uint32_t TJSONProtocol::readJSONInteger(NumberType &num) {
uint32_t result = context_->read(*trans_);
if (context_->escapeNum()) {
result += readJSONSyntaxChar(kJSONStringDelimiter);
}
std::string str;
result += readJSONNumericChars(str);
try {
num = boost::lexical_cast<NumberType>(str);
}
catch (boost::bad_lexical_cast e) {
throw new TProtocolException(TProtocolException::INVALID_DATA,
"Expected numeric value; got \"" + str +
"\"");
}
if (context_->escapeNum()) {
result += readJSONSyntaxChar(kJSONStringDelimiter);
}
return result;
}
// Reads a JSON number or string and interprets it as a double.
uint32_t TJSONProtocol::readJSONDouble(double &num) {
uint32_t result = context_->read(*trans_);
std::string str;
if (borrowByte(*trans_) == kJSONStringDelimiter) {
result += readJSONString(str, true);
// Check for NaN, Infinity and -Infinity
if (str == kThriftNan) {
num = HUGE_VAL/HUGE_VAL; // generates NaN
}
else if (str == kThriftInfinity) {
num = HUGE_VAL;
}
else if (str == kThriftNegativeInfinity) {
num = -HUGE_VAL;
}
else {
if (!context_->escapeNum()) {
// Throw exception -- we should not be in a string in this case
throw new TProtocolException(TProtocolException::INVALID_DATA,
"Numeric data unexpectedly quoted");
}
try {
num = boost::lexical_cast<double>(str);
}
catch (boost::bad_lexical_cast e) {
throw new TProtocolException(TProtocolException::INVALID_DATA,
"Expected numeric value; got \"" + str +
"\"");
}
}
}
else {
if (context_->escapeNum()) {
// This will throw - we should have had a quote if escapeNum == true
readJSONSyntaxChar(kJSONStringDelimiter);
}
result += readJSONNumericChars(str);
try {
num = boost::lexical_cast<double>(str);
}
catch (boost::bad_lexical_cast e) {
throw new TProtocolException(TProtocolException::INVALID_DATA,
"Expected numeric value; got \"" + str +
"\"");
}
}
return result;
}
uint32_t TJSONProtocol::readJSONObjectStart() {
uint32_t result = context_->read(*trans_);
result += readJSONSyntaxChar(kJSONObjectStart);
pushContext(boost::shared_ptr<TJSONContext>(new JSONPairContext()));
return result;
}
uint32_t TJSONProtocol::readJSONObjectEnd() {
uint32_t result = readJSONSyntaxChar(kJSONObjectEnd);
popContext();
return result;
}
uint32_t TJSONProtocol::readJSONArrayStart() {
uint32_t result = context_->read(*trans_);
result += readJSONSyntaxChar(kJSONArrayStart);
pushContext(boost::shared_ptr<TJSONContext>(new JSONListContext()));
return result;
}
uint32_t TJSONProtocol::readJSONArrayEnd() {
uint32_t result = readJSONSyntaxChar(kJSONArrayEnd);
popContext();
return result;
}
uint32_t TJSONProtocol::readMessageBegin(std::string& name,
TMessageType& messageType,
int32_t& seqid) {
uint32_t result = readJSONArrayStart();
std::string tmpStr;
uint64_t tmpVal = 0;
result += readJSONInteger(tmpVal);
if (tmpVal != kThriftVersion1) {
throw TProtocolException(TProtocolException::BAD_VERSION,
"Message contained bad version.");
}
result += readJSONString(name);
result += readJSONInteger(tmpVal);
messageType = (TMessageType)tmpVal;
result += readJSONInteger(tmpVal);
seqid = tmpVal;
return result;
}
uint32_t TJSONProtocol::readMessageEnd() {
return readJSONArrayEnd();
}
uint32_t TJSONProtocol::readStructBegin(std::string& name) {
return readJSONObjectStart();
}
uint32_t TJSONProtocol::readStructEnd() {
return readJSONObjectEnd();
}
uint32_t TJSONProtocol::readFieldBegin(std::string& name,
TType& fieldType,
int16_t& fieldId) {
// Check if we hit the end of the list
uint8_t b[1];
uint32_t len = 1;
const uint8_t * buf = trans_->borrow(b, &len);
if (!buf || !len) {
throw TTransportException(TTransportException::UNKNOWN,
"Could not borrow 1 byte from transport.");
}
uint32_t result = 0;
if (buf[0] == kJSONObjectEnd) {
fieldType = facebook::thrift::protocol::T_STOP;
}
else {
uint64_t tmpVal = 0;
std::string tmpStr;
result += readJSONInteger(tmpVal);
fieldId = tmpVal;
result += readJSONObjectStart();
result += readJSONString(tmpStr);
fieldType = getTypeIDForTypeName(tmpStr);
}
return result;
}
uint32_t TJSONProtocol::readFieldEnd() {
return readJSONObjectEnd();
}
uint32_t TJSONProtocol::readMapBegin(TType& keyType,
TType& valType,
uint32_t& size) {
uint64_t tmpVal = 0;
std::string tmpStr;
uint32_t result = readJSONArrayStart();
result += readJSONString(tmpStr);
keyType = getTypeIDForTypeName(tmpStr);
result += readJSONString(tmpStr);
valType = getTypeIDForTypeName(tmpStr);
result += readJSONInteger(tmpVal);
size = tmpVal;
result += readJSONObjectStart();
return result;
}
uint32_t TJSONProtocol::readMapEnd() {
return readJSONObjectEnd() + readJSONArrayEnd();
}
uint32_t TJSONProtocol::readListBegin(TType& elemType,
uint32_t& size) {
uint64_t tmpVal = 0;
std::string tmpStr;
uint32_t result = readJSONArrayStart();
result += readJSONString(tmpStr);
elemType = getTypeIDForTypeName(tmpStr);
result += readJSONInteger(tmpVal);
size = tmpVal;
return result;
}
uint32_t TJSONProtocol::readListEnd() {
return readJSONArrayEnd();
}
uint32_t TJSONProtocol::readSetBegin(TType& elemType,
uint32_t& size) {
uint64_t tmpVal = 0;
std::string tmpStr;
uint32_t result = readJSONArrayStart();
result += readJSONString(tmpStr);
elemType = getTypeIDForTypeName(tmpStr);
result += readJSONInteger(tmpVal);
size = tmpVal;
return result;
}
uint32_t TJSONProtocol::readSetEnd() {
return readJSONArrayEnd();
}
uint32_t TJSONProtocol::readBool(bool& value) {
return readJSONInteger(value);
}
// readByte() must be handled properly becuase boost::lexical cast sees int8_t
// as a text type instead of an integer type
uint32_t TJSONProtocol::readByte(int8_t& byte) {
int16_t tmp = (int16_t) byte;
uint32_t result = readJSONInteger(tmp);
assert(tmp < 256);
byte = (int8_t)tmp;
return result;
}
uint32_t TJSONProtocol::readI16(int16_t& i16) {
return readJSONInteger(i16);
}
uint32_t TJSONProtocol::readI32(int32_t& i32) {
return readJSONInteger(i32);
}
uint32_t TJSONProtocol::readI64(int64_t& i64) {
return readJSONInteger(i64);
}
uint32_t TJSONProtocol::readDouble(double& dub) {
return readJSONDouble(dub);
}
uint32_t TJSONProtocol::readString(std::string &str) {
return readJSONString(str);
}
uint32_t TJSONProtocol::readBinary(std::string &str) {
return readJSONBase64(str);
}
}}} // facebook::thrift::protocol

View File

@ -0,0 +1,291 @@
// Copyright (c) 2006- Facebook
// Distributed under the Thrift Software License
//
// See accompanying file LICENSE or visit the Thrift site at:
// http://developers.facebook.com/thrift/
#ifndef _THRIFT_PROTOCOL_TJSONPROTOCOL_H_
#define _THRIFT_PROTOCOL_TJSONPROTOCOL_H_ 1
#include "TProtocol.h"
#include <transport/TTransportUtils.h>
#include <stack>
namespace facebook { namespace thrift { namespace protocol {
// Forward declaration
class TJSONContext;
/**
* JSON protocol for Thrift.
*
* This protocol provides for protocol which uses JSON as the wire-format.
* Thrift types are represented as described below:
*
* 1. Every Thrift integer type is represented as a JSON number.
*
* 2. Thrift doubles are represented as JSON numbers. Some special values are
* represented as strings:
* a. "NaN" for not-a-number values
* b. "Infinity" for postive infinity
* c. "-Infinity" for negative infinity
*
* 3. Thrift string values are emitted as JSON strings, with appropriate
* escaping.
*
* 4. Thrift binary values are encoded into Base64 and emitted as JSON strings.
* The readBinary() method is written such that it will properly skip if
* called on a Thrift string (although it will decode garbage data).
*
* 5. Thrift structs are represented as JSON objects, with the field ID as the
* key, and the field value represented as a JSON object with a single
* key-value pair. The key is a short string identifier for that type,
* followed by the value. The valid type identifiers are: "tf" for bool,
* "i8" for byte, "i16" for 16-bit integer, "i32" for 32-bit integer, "i64"
* for 64-bit integer, "dbl" for double-precision loating point, "str" for
* string (including binary), "rec" for struct ("records"), "map" for map,
* "lst" for list, "set" for set.
*
* 6. Thrift lists and sets are represented as JSON arrays, with the first
* element of the JSON array being the string identifier for the Thrift
* element type and the second element of the JSON array being the count of
* the Thrift elements. The Thrift elements then follow.
*
* 7. Thrift maps are represented as JSON arrays, with the first two elements
* of the JSON array being the string identifiers for the Thrift key type
* and value type, followed by the count of the Thrift pairs, followed by a
* JSON object containing the key-value pairs. Note that JSON keys can only
* be strings, which means that the key type of the Thrift map should be
* restricted to numeric or string types -- in the case of numerics, they
* are serialized as strings.
*
* 8. Thrift messages are represented as JSON arrays, with the protocol
* version #, the message name, the message type, and the sequence ID as
* the first 4 elements.
*
* More discussion of the double handling is probably warranted. The aim of
* the current implementation is to match as closely as possible the behavior
* of Java's Double.toString(), which has no precision loss. Implementors in
* other languages should strive to achieve that where possible. I have not
* yet verified whether boost:lexical_cast, which is doing that work for me in
* C++, loses any precision, but I am leaving this as a future improvement. I
* may try to provide a C component for this, so that other languages could
* bind to the same underlying implementation for maximum consistency.
*
* Note further that JavaScript itself is not capable of representing
* floating point infinities -- presumably when we have a JavaScript Thrift
* client, this would mean that infinities get converted to not-a-number in
* transmission. I don't know of any work-around for this issue.
*
* @author Chad Walters <chad@powerset.com>
*/
class TJSONProtocol : public TProtocol {
public:
TJSONProtocol(boost::shared_ptr<TTransport> ptrans);
~TJSONProtocol();
private:
void pushContext(boost::shared_ptr<TJSONContext> c);
void popContext();
uint32_t writeJSONEscapeChar(uint8_t ch);
uint32_t writeJSONChar(uint8_t ch);
uint32_t writeJSONString(const std::string &str);
uint32_t writeJSONBase64(const std::string &str);
template <typename NumberType>
uint32_t writeJSONInteger(NumberType num);
uint32_t writeJSONDouble(double num);
uint32_t writeJSONObjectStart() ;
uint32_t writeJSONObjectEnd();
uint32_t writeJSONArrayStart();
uint32_t writeJSONArrayEnd();
uint32_t readJSONSyntaxChar(uint8_t ch);
uint32_t readJSONEscapeChar(uint8_t *out);
uint32_t readJSONString(std::string &str, bool skipContext = false);
uint32_t readJSONBase64(std::string &str);
uint32_t readJSONNumericChars(std::string &str);
template <typename NumberType>
uint32_t readJSONInteger(NumberType &num);
uint32_t readJSONDouble(double &num);
uint32_t readJSONObjectStart();
uint32_t readJSONObjectEnd();
uint32_t readJSONArrayStart();
uint32_t readJSONArrayEnd();
public:
/**
* Writing functions.
*/
uint32_t writeMessageBegin(const std::string& name,
const TMessageType messageType,
const int32_t seqid);
uint32_t writeMessageEnd();
uint32_t writeStructBegin(const std::string& name);
uint32_t writeStructEnd();
uint32_t writeFieldBegin(const std::string& name,
const TType fieldType,
const int16_t fieldId);
uint32_t writeFieldEnd();
uint32_t writeFieldStop();
uint32_t writeMapBegin(const TType keyType,
const TType valType,
const uint32_t size);
uint32_t writeMapEnd();
uint32_t writeListBegin(const TType elemType,
const uint32_t size);
uint32_t writeListEnd();
uint32_t writeSetBegin(const TType elemType,
const uint32_t size);
uint32_t writeSetEnd();
uint32_t writeBool(const bool value);
uint32_t writeByte(const int8_t byte);
uint32_t writeI16(const int16_t i16);
uint32_t writeI32(const int32_t i32);
uint32_t writeI64(const int64_t i64);
uint32_t writeDouble(const double dub);
uint32_t writeString(const std::string& str);
uint32_t writeBinary(const std::string& str);
/**
* Reading functions
*/
uint32_t readMessageBegin(std::string& name,
TMessageType& messageType,
int32_t& seqid);
uint32_t readMessageEnd();
uint32_t readStructBegin(std::string& name);
uint32_t readStructEnd();
uint32_t readFieldBegin(std::string& name,
TType& fieldType,
int16_t& fieldId);
uint32_t readFieldEnd();
uint32_t readMapBegin(TType& keyType,
TType& valType,
uint32_t& size);
uint32_t readMapEnd();
uint32_t readListBegin(TType& elemType,
uint32_t& size);
uint32_t readListEnd();
uint32_t readSetBegin(TType& elemType,
uint32_t& size);
uint32_t readSetEnd();
uint32_t readBool(bool& value);
uint32_t readByte(int8_t& byte);
uint32_t readI16(int16_t& i16);
uint32_t readI32(int32_t& i32);
uint32_t readI64(int64_t& i64);
uint32_t readDouble(double& dub);
uint32_t readString(std::string& str);
uint32_t readBinary(std::string& str);
private:
std::stack<boost::shared_ptr<TJSONContext> > contexts_;
boost::shared_ptr<TJSONContext> context_;
};
/**
* Constructs input and output protocol objects given transports.
*/
class TJSONProtocolFactory {
public:
TJSONProtocolFactory() {}
virtual ~TJSONProtocolFactory() {}
boost::shared_ptr<TProtocol> getProtocol(boost::shared_ptr<TTransport> trans) {
return boost::shared_ptr<TProtocol>(new TJSONProtocol(trans));
}
};
}}} // facebook::thrift::protocol
namespace facebook { namespace thrift {
template<typename ThriftStruct>
std::string ThriftJSONString(const ThriftStruct& ts) {
using namespace facebook::thrift::transport;
using namespace facebook::thrift::protocol;
TMemoryBuffer* buffer = new TMemoryBuffer;
boost::shared_ptr<TTransport> trans(buffer);
TJSONProtocol protocol(trans);
ts.write(&protocol);
uint8_t* buf;
uint32_t size;
buffer->getBuffer(&buf, &size);
return std::string((char*)buf, (unsigned int)size);
}
}} // facebook::thrift
#endif // #define _THRIFT_PROTOCOL_TJSONPROTOCOL_H_ 1

View File

@ -1,5 +1,16 @@
cpp_namespace thrift.test
struct Doubles {
1: double nan,
2: double inf,
3: double neginf,
4: double repeating,
5: double big,
6: double small,
7: double zero,
8: double negzero,
}
struct OneOfEach {
1: bool im_true,
2: bool im_false,
@ -11,6 +22,7 @@ struct OneOfEach {
8: string some_characters,
9: string zomg_unicode,
10: bool what_who,
11: binary base64,
}
struct Bonk {

119
test/JSONProtoTest.cpp Normal file
View File

@ -0,0 +1,119 @@
#include <iostream>
#include <cmath>
#include <transport/TTransportUtils.h>
#include <protocol/TJSONProtocol.h>
#include "gen-cpp/DebugProtoTest_types.h"
int main() {
using std::cout;
using std::endl;
using namespace thrift::test;
using facebook::thrift::transport::TMemoryBuffer;
using facebook::thrift::protocol::TJSONProtocol;
OneOfEach ooe;
ooe.im_true = true;
ooe.im_false = false;
ooe.a_bite = 0xd6;
ooe.integer16 = 27000;
ooe.integer32 = 1<<24;
ooe.integer64 = (uint64_t)6000 * 1000 * 1000;
ooe.double_precision = M_PI;
ooe.some_characters = "JSON THIS! \"\1";
ooe.zomg_unicode = "\xd7\n\a\t";
ooe.base64 = "\1\2\3\255";
cout << facebook::thrift::ThriftJSONString(ooe) << endl << endl;
Nesting n;
n.my_ooe = ooe;
n.my_ooe.integer16 = 16;
n.my_ooe.integer32 = 32;
n.my_ooe.integer64 = 64;
n.my_ooe.double_precision = (std::sqrt(5)+1)/2;
n.my_ooe.some_characters = ":R (me going \"rrrr\")";
n.my_ooe.zomg_unicode = "\xd3\x80\xe2\x85\xae\xce\x9d\x20"
"\xd0\x9d\xce\xbf\xe2\x85\xbf\xd0\xbe\xc9\xa1\xd0\xb3\xd0\xb0\xcf\x81\xe2\x84\x8e"
"\x20\xce\x91\x74\x74\xce\xb1\xe2\x85\xbd\xce\xba\xc7\x83\xe2\x80\xbc";
n.my_bonk.type = 31337;
n.my_bonk.message = "I am a bonk... xor!";
cout << facebook::thrift::ThriftJSONString(n) << endl << endl;
HolyMoley hm;
hm.big.push_back(ooe);
hm.big.push_back(n.my_ooe);
hm.big[0].a_bite = 0x22;
hm.big[1].a_bite = 0x33;
std::vector<std::string> stage1;
stage1.push_back("and a one");
stage1.push_back("and a two");
hm.contain.insert(stage1);
stage1.clear();
stage1.push_back("then a one, two");
stage1.push_back("three!");
stage1.push_back("FOUR!!");
hm.contain.insert(stage1);
stage1.clear();
hm.contain.insert(stage1);
std::vector<Bonk> stage2;
hm.bonks["nothing"] = stage2;
stage2.resize(stage2.size()+1);
stage2.back().type = 1;
stage2.back().message = "Wait.";
stage2.resize(stage2.size()+1);
stage2.back().type = 2;
stage2.back().message = "What?";
hm.bonks["something"] = stage2;
stage2.clear();
stage2.resize(stage2.size()+1);
stage2.back().type = 3;
stage2.back().message = "quoth";
stage2.resize(stage2.size()+1);
stage2.back().type = 4;
stage2.back().message = "the raven";
stage2.resize(stage2.size()+1);
stage2.back().type = 5;
stage2.back().message = "nevermore";
hm.bonks["poe"] = stage2;
cout << facebook::thrift::ThriftJSONString(hm) << endl << endl;
boost::shared_ptr<TMemoryBuffer> buffer(new TMemoryBuffer());
boost::shared_ptr<TJSONProtocol> proto(new TJSONProtocol(buffer));
cout << "Testing ooe" << endl;
ooe.write(proto.get());
OneOfEach ooe2;
ooe2.read(proto.get());
assert(ooe == ooe2);
cout << "Testing hm" << endl;
hm.write(proto.get());
HolyMoley hm2;
hm2.read(proto.get());
assert(hm == hm2);
Doubles dub;
dub.nan = HUGE_VAL/HUGE_VAL;
dub.inf = HUGE_VAL;
dub.neginf = -HUGE_VAL;
dub.repeating = 10.0/3.0;
dub.big = 1E+305;
dub.small = 1E-305;
dub.zero = 0.0;
dub.negzero = -0.0;
cout << facebook::thrift::ThriftJSONString(dub) << endl << endl;
return 0;
}

View File

@ -2,6 +2,7 @@ SUBDIRS = py
check_PROGRAMS = \
DebugProtoTest \
JSONProtoTest \
OptionalRequiredTest \
ReflectionTest
@ -20,6 +21,16 @@ DebugProtoTest_LDADD = \
$(top_srcdir)/lib/cpp/libthrift.la
#
# JSONProtoTest
#
JSONProtoTest_SOURCES = \
gen-cpp/DebugProtoTest_types.cpp \
JSONProtoTest.cpp
JSONProtoTest_LDADD = \
$(top_srcdir)/lib/cpp/libthrift.la
#
# OptionalRequiredTest
#