Created
March 12, 2012 10:48
-
-
Save pyrtsa/2021128 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The following code is in public domain. Re-releases are free to choose | |
// whether to attribute the code to its original author or not. | |
// Author: Pyry Jahkola <[email protected]> | |
#include <sstream> | |
#include <iostream> | |
#include <map> | |
#include <cassert> | |
#include <stack> | |
#include <vector> | |
namespace json { | |
// ----------------------------------------------------------------------------- | |
template <typename UnicodeIter> | |
std::ostream & to_utf8(std::ostream & os, UnicodeIter begin, UnicodeIter end) { | |
using std::uint8_t; | |
using std::uint32_t; | |
for (UnicodeIter i = begin; i != end; i++) { | |
uint32_t u = *i; // next Unicode code point | |
if (u < 0x80) { | |
os << static_cast<uint8_t>(u); | |
} else if (u < 0x800) { | |
os << static_cast<uint8_t>(0xC0 | (u >> 6)) | |
<< static_cast<uint8_t>(0x80 | (u & 0x3F)); | |
} else if (u < 0x10000) { | |
os << static_cast<uint8_t>(0xE0 | (u >> 12)) | |
<< static_cast<uint8_t>(0x80 | ((u >> 6) & 0x3F)) | |
<< static_cast<uint8_t>(0x80 | (u & 0x3F)); | |
} else if (u < 0x110000) { | |
os << static_cast<uint8_t>(0xF0 | (u >> 18)) | |
<< static_cast<uint8_t>(0x80 | ((u >> 12) & 0x3F)) | |
<< static_cast<uint8_t>(0x80 | ((u >> 6) & 0x3F)) | |
<< static_cast<uint8_t>(0x80 | (u & 0x3F)); | |
} else { | |
assert(false && "invalid Unicode value!"); | |
// U+FFFD, http://en.wikipedia.org/wiki/Replacement_character | |
os << "\xEF\xBF\xBD"; | |
} | |
} | |
return os; | |
} | |
template <typename UnicodeIter> | |
std::string to_utf8(UnicodeIter begin, UnicodeIter end) { | |
std::ostringstream os; | |
to_utf8(os, begin, end); | |
return os.str(); | |
} | |
template <typename C, typename T> | |
std::ostream & to_utf8(std::ostream & os, | |
std::basic_string<C, T> const & unicode_string) | |
{ | |
return to_utf8(os, unicode_string.begin(), unicode_string.end()); | |
} | |
template <typename C, typename T> | |
std::string to_utf8(std::basic_string<C, T> const & unicode_string) { | |
return to_utf8(unicode_string.begin(), unicode_string.end()); | |
} | |
// ----------------------------------------------------------------------------- | |
std::ostream & quote(std::ostream & os, std::string const & utf8) { | |
typedef std::string::const_iterator iter; | |
char const hexa[] = "0123456789abcdef"; | |
os << '"'; | |
for (iter i = utf8.begin(), e = utf8.end(); i != e; i++) { | |
unsigned char const c = static_cast<unsigned char>(*i); | |
if (c < 0x20) { | |
if (c == '\n') os << "\\n"; | |
else if (c == '\r') os << "\\r"; | |
else os << "\\u00" << hexa[c >> 4] << hexa[c & 0xf]; | |
} else if (c == '"') { | |
os << "\\\""; | |
} else if (c == '\\') { | |
os << "\\\\"; | |
} else { | |
os << c; | |
} | |
} | |
return os << '"'; | |
} | |
std::string quote(std::string const & s) { | |
std::ostringstream o; | |
json::quote(o, s); | |
return o.str(); | |
} | |
namespace tag { | |
struct object {}; | |
struct array {}; | |
struct end {}; | |
struct null {}; | |
} | |
extern tag::object const object; | |
extern tag::array const array; | |
extern tag::end const end; | |
extern tag::null const null; | |
template <typename Range, typename Enable=void> | |
struct range_const_iterator {}; | |
template <typename Range> | |
struct range_const_iterator<Range> { | |
typedef typename Range::const_iterator type; | |
}; | |
template <typename T, std::size_t N> struct range_const_iterator<T[N]> { | |
typedef T const * type; | |
}; | |
template <typename T, std::size_t N> struct range_const_iterator<T(&)[N]> { | |
typedef T const * type; | |
}; | |
template <typename Range> | |
typename range_const_iterator<Range>::type cbegin(Range const & r) { | |
return r.begin(); | |
} | |
template <typename Range> | |
typename range_const_iterator<Range>::type cend(Range const & r) { | |
return r.end(); | |
} | |
template <typename T, std::size_t N> | |
inline T const * cbegin(T (&r)[N]) { return r; } | |
template <typename T, std::size_t N> | |
inline T const * cend(T (&r)[N]) { return cbegin(r) + N; } | |
template <typename T, std::size_t N> | |
inline T const * cbegin(T const (&r)[N]) { return r; } | |
template <typename T, std::size_t N> | |
inline T const * cend(T const (&r)[N]) { return cbegin(r) + N; } | |
template <bool Cond, typename Then=void> struct enable_if {}; | |
template <typename Then> struct enable_if<true, Then> { typedef Then type; }; | |
namespace detail { | |
struct yes { char _[1]; }; | |
struct nay { char _[2]; }; | |
template <typename T, typename=typename T::const_iterator> | |
struct is_range_helper; | |
template <typename T> yes is_range_test(is_range_helper<T> *); | |
template <typename T> nay is_range_test(...); | |
} | |
template <bool C> struct bool_c { | |
typedef bool type; | |
static bool const value = C; | |
}; | |
template <typename T> struct is_range | |
: bool_c<sizeof(detail::is_range_test<T>(0)) == sizeof(detail::yes)> {}; | |
template <typename T> struct is_range<T &> : is_range<T> {}; | |
template <typename T> struct is_range<T const> : is_range<T> {}; | |
template <typename T, std::size_t N> struct is_range<T(&)[N]> : bool_c<true> {}; | |
template <typename T> struct is_number : bool_c<false> {}; | |
template <> struct is_number<char> : bool_c<true> {}; | |
template <> struct is_number<signed char> : bool_c<true> {}; | |
template <> struct is_number<unsigned char> : bool_c<true> {}; | |
template <> struct is_number<short> : bool_c<true> {}; | |
template <> struct is_number<int> : bool_c<true> {}; | |
template <> struct is_number<long> : bool_c<true> {}; | |
template <> struct is_number<long long> : bool_c<true> {}; | |
template <> struct is_number<unsigned short> : bool_c<true> {}; | |
template <> struct is_number<unsigned int> : bool_c<true> {}; | |
template <> struct is_number<unsigned long> : bool_c<true> {}; | |
template <> struct is_number<unsigned long long> : bool_c<true> {}; | |
// object ::= '{' (string ':' value (',' string ':' value)*)? '}' | |
// array ::= '[' (value (',' value)*)? ']' | |
// value ::= string | number | object | array | true | false | null | |
class ostream { | |
public: | |
enum state { ROOT=1, OBJECT=2, KEY=4, VALUE=8, ARRAY=16, ELEMENT=32 }; | |
private: | |
std::shared_ptr<std::ostringstream> p; | |
std::ostream & os; | |
std::stack<state> stack; | |
std::ostream & append_os() { | |
if (in_state(KEY | ELEMENT)) os << ','; | |
else if (in_state(VALUE)) os << ':'; | |
if (in_state(OBJECT | KEY)) stack.top() = VALUE; | |
else if (in_state(VALUE)) stack.top() = KEY; | |
else if (in_state(ARRAY)) stack.top() = ELEMENT; | |
return os; | |
} | |
public: | |
explicit ostream() : p(new std::ostringstream), os(*p) { stack.push(ROOT); } | |
explicit ostream(std::ostream & os) : os(os) { stack.push(ROOT); } | |
bool in_state(int mask) const { return stack.size() && stack.top() & mask; } | |
bool finished() const { return stack.empty(); } | |
std::string str() const { | |
assert(finished() && p); | |
return p ? p->str() : ""; | |
} | |
operator std::string() const { return str(); } | |
ostream & operator<<(tag::object) { | |
assert(in_state(ROOT | VALUE | ARRAY | ELEMENT)); | |
append_os() << '{'; | |
stack.push(OBJECT); | |
return *this; | |
} | |
ostream & operator<<(tag::array) { | |
assert(in_state(VALUE | ARRAY | ELEMENT)); | |
append_os() << '['; | |
stack.push(ARRAY); | |
return *this; | |
} | |
ostream & operator<<(tag::end) { | |
assert(in_state(OBJECT | KEY | ARRAY | ELEMENT)); | |
os << (in_state(OBJECT | KEY) ? '}' : ']'); | |
stack.pop(); | |
if (in_state(ROOT)) { | |
stack.pop(); // finish if we ended up at root level | |
assert(finished()); | |
} else if (in_state(VALUE)) { | |
stack.top() = KEY; | |
} | |
return *this; | |
} | |
ostream & operator<<(char const * string) { | |
return *this << std::string(string); | |
} | |
ostream & operator<<(std::string const & string) { | |
assert(in_state(OBJECT | KEY | VALUE | ARRAY | ELEMENT)); | |
quote(append_os(), string); | |
return *this; | |
} | |
ostream & operator<<(bool value) { | |
assert(in_state(VALUE | ARRAY | ELEMENT)); | |
append_os() << (value ? "true" : "false"); | |
return *this; | |
} | |
ostream & operator<<(tag::null) { | |
assert(in_state(VALUE | ARRAY | ELEMENT)); | |
append_os() << "null"; | |
return *this; | |
} | |
template <typename T> | |
typename enable_if<is_number<T>::value, ostream &>::type | |
operator<<(T number) { | |
assert(in_state(VALUE | ARRAY | ELEMENT)); | |
append_os() << static_cast<double>(number); | |
return *this; | |
} | |
template <typename K, typename V> | |
ostream & operator<<(std::map<K, V> const & object) { | |
typedef typename std::map<K, V>::const_iterator iter; | |
assert(in_state(VALUE | ARRAY | ELEMENT)); | |
*this << tag::object(); | |
for (iter i = object.begin(), e = object.end(); i != e; i++) | |
*this << i->first << i->second; | |
return *this << tag::end(); | |
} | |
template <typename Range> | |
typename enable_if<is_range<Range>::value, ostream &>::type | |
operator<<(Range const & array) { | |
using std::begin; using std::end; | |
typedef typename range_const_iterator<Range>::type iter; | |
assert(in_state(VALUE | ARRAY | ELEMENT)); | |
*this << tag::array(); | |
for (iter i = begin(array), e = end(array); i != e; i++) | |
*this << *i; | |
return *this << tag::end(); | |
} | |
template <typename Element, std::size_t N> | |
ostream & operator<<(Element const (&array)[N]) { | |
using std::begin; using std::end; | |
typedef Element const * iter; | |
assert(in_state(VALUE | ARRAY | ELEMENT)); | |
*this << tag::array(); | |
for (iter i = begin(array), e = end(array); i != e; i++) | |
*this << *i; | |
return *this << tag::end(); | |
} | |
}; | |
} // json | |
// ============================================================================= | |
std::string example1() { | |
return json::quote("Abc. \"123 \\\1 \xE2\x80\xA6\r\n"); | |
} | |
std::string example2() { | |
char const * things[3] = { "pen", "brush", "hammer" }; | |
std::vector<std::string> numbers; | |
numbers.push_back("one"); | |
numbers.push_back("two"); | |
numbers.push_back("three"); | |
using json::object; | |
using json::array; | |
using json::null; | |
using json::end; | |
return json::ostream() | |
<< object | |
<< "name" << "Hello World!" | |
<< "year" << 2012 | |
<< "things" << things | |
<< "list" << array | |
<< true | |
<< false | |
<< 2 | |
<< null | |
<< 4 | |
<< "fi\\ve" | |
<< object | |
<< "first" << "Pat" | |
<< "second" << "Matt" | |
<< "numbers" << numbers | |
<< end | |
<< end | |
<< "empty\tobject" << object << end | |
<< "empty\r\narray" << array << end | |
<< "array of empty arrays etc." << array | |
<< array << end | |
<< array << end | |
<< array << end | |
<< array << array << array << array << end << end << end << end | |
<< end | |
<< "del" "\x7F" ", huh?" << "\xE2\x80\xA6" | |
<< "2 \\0 bytes" << std::string(2, '\0') | |
<< end; | |
} | |
int main() { | |
std::cout << example2() << std::endl; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment