Name: spotify-json
Owner: Spotify
Description: Fast and nice to use C++ JSON library.
Created: 2016-09-23 13:35:41.0
Updated: 2018-05-02 14:45:04.0
Pushed: 2018-05-02 14:45:02.0
Homepage: null
Size: 7589
Language: C++
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
A C++11 JSON writer and parser library. It
spotify-json depends on Google's double-conversion library, which must be linked in to the code that uses spotify-json.
lude <iostream>
lude <map>
lude <string>
lude <spotify/json.hpp>
g namespace spotify::json;
ct Track {
d::string uri;
d::string uid;
d::map<std::string, std::string> metadata;
space spotify {
space json {
pecialize spotify::json::default_codec_t to specify default behavior when
ncoding and decoding objects of certain types.
late <>
ct default_codec_t<Track> {
atic object_t<Track> codec() {
auto codec = object<Track>();
codec.required("uri", &Track::uri);
codec.optional("uid", &Track::uid);
codec.optional("metadata", &Track::metadata);
return codec;
/ namespace json
/ namespace spotify
main() {
nst auto parsed_track = decode<Track>(R"({ "uri": "spotify:track:xyz", "metadata": { "a": "b" } })");
d::cout << "Parsed track with uri " << parsed_track.uri << std::endl;
ack track;
ack.uri = "spotify:track:abc";
ack.uid = "a-uid";
nst auto json = encode(track);
d::cout << "Encoded the track into " << json << std::endl;
turn 0;
spotify-json offers a range of codec types that can serialize and parse specific JSON values. There are codecs for each of the basic data types that JSON offers: strings, numbers, arrays, booleans, objects and null.
A codec for integers can be made using
codec::number<int>()
. The codec for strings can be
instantiated with codec::string()
.
Codecs are composable. It is for example possible to construct a codec for
parsing and serialization of JSON arrays of numbers, such as [1,4,2]
:
codec::array<std::vector<int>>(codec::number<int>())
.
Constructing deeply nested codecs manually as above can become tedious. To ease
this pain, default_codec
is a helper function
that makes it easy to construct codecs for built-in types. For example,
default_codec<int>()
is a codec that can parse and serialize numbers, and
default_codec<std::vector<int>>()
is one that works on arrays of numbers.
It is possible to work with JSON objects with arbitrary keys. For example,
default_codec<std::map<std::string, bool>>()
is a codec for JSON objects
with strings as keys and booleans as values.
Parsing is done using the decode
function:
{
code(codec::number<int>(), "123") == 123;
code<int>("123") == 123; // Shortcut for decode(default_codec<int>(), "123")
code<std::vector<int>>("[1,2,3]") == std::vector{ 1, 2, 3 };
tch (const decode_exception &e) {
d::cout << "Failed to decode: " << e.what() << std::endl;
decode
throws
decode_exception
when parsing fails. There is
also a function try_decode
that doesn't throw on
parse errors:
result = 0;
try_decode(result, "123")) {
sult == 123;
se {
Decoding failed!
Similarly, serialization is done using encode
:
de(codec::number<int>(), 123) == "123";
de(123) == "123"; // Shortcut for encode(default_codec<int>(), 123)
de(std::vector<int>{ 1, 2, 3 }) == "[1,2,3]";
Working with basic types such as numbers, strings, booleans and arrays is all nice and dandy, but most practical applications need to deal with rich JSON schemas that involve objects.
Many JSON libraries work by parsing JSON strings into a tree structure that can be read by the application. In our experience, this approach often leads to large amounts of boilerplate code to extract the information in this tree object into statically typed counterparts that are practical to use in C++. This boilerplate is painful to write, bug-prone and slow due to unnecessary copying. SAX-style event based libraries such as yajl avoid the slowdown but require even more boilerplate.
spotify-json avoids these issues by parsing the JSON directly into statically
typed data structures. To explain how, let's use the example of a basic
two-dimensional coordinate, represented in JSON as {"x":1,"y":2}
. In C++, such
a coordinate may be represented as a struct:
ct Coordinate {
ordinate() = default;
ordinate(int x, int y) : x(x), y(y) {}
t x = 0;
t y = 0;
With spotify-json, it is possible to construct a codec that can convert
Coordinate
directly to and from JSON:
coordinate_codec = object<Coordinate>();
dinate_codec.required("x", &Coordinate::x);
dinate_codec.required("y", &Coordinate::y);
The use of required
will cause parsing to fail if the fields are missing.
There is also an optional
method. For more information, see
object_t
's API documentation.
This codec can be used with encode
and decode
:
de(coordinate_codec, Coordinate(10, 0)) == R"({"x":10,"y":0})";
t Coordinate coord = decode(coordinate_codec, R"({ "x": 12, "y": 13 })");
d.x == 12;
d.y == 13;
Objects can be nested. To demonstrate this, let's introduce another data type:
ct Player {
d::string name;
d::string instrument;
ordinate position;
A codec for Player
might be created with
player_codec = object<Player>();
er_codec.required("name", &Player::name);
er_codec.required("instrument", &Player::instrument);
ecause there is no default_codec for Coordinate, we need to pass in the
odec explicitly:
er_codec.required("position", &Player::position, coordinate_codec);
et's use it:
er player;
er.name = "Daniel";
er.instrument = "guitar";
de(player_codec, player) == R"({"name":"Daniel","instrument":"guitar","position":{"x":0,"y":0}})";
Since codecs are just normal objects, it is possible to create and use
several different codecs for any given data type. This makes it possibile to
parameterize parsing and do other fancy things, but for most data types there
will only really exist one codec. For these cases, it is possible to extend
the default_codec
helper to support your own data types.
space spotify {
space json {
late <>
ct default_codec_t<Coordinate> {
atic object_t<Coordinate> codec() {
auto codec = object<Coordinate>();
codec.required("x", &Coordinate::x);
codec.required("y", &Coordinate::y);
return codec;
late <>
ct default_codec_t<Player> {
atic object_t<Player> codec() {
auto codec = object<Player>();
codec.required("name", &Player::name);
codec.required("instrument", &Player::instrument);
codec.required("position", &Player::position);
return codec;
/ namespace json
/ namespace spotify
Coordinate
and Player
can now be used like any other type that spotify-json
supports out of the box:
de(Coordinate(10, 0)) == R"({"x":10,"y":0})";
de<std::vector<Coordinate>>(R"([{ "x": 1, "y": -1 }])") == std::vector<Coordinate>{ Coordinate(1, -1) };
er player;
er.name = "Martin";
er.instrument = "drums";
de(player) == R"({"name":"Martin","instrument":"drums","position":{"x":0,"y":0}})";
The examples above cover the most commonly used parts of spotify-json. The library supports more things that sometimes come in handy:
array
, vector
, deque
, list
, set
, unordered_set
,
pair
, tuple
,
map
and unordered_map
enum
s and similar typesunique_ptr
s and
shared_ptr
sboost::optional
boost::chrono
and std::chrono
typesIf your project is built with CMake, it is easy to use spotify-json. Here is an example of how it can be done:
vendor/
CMakeLists.txt
of your project:subdirectory(vendor/spotify-json)
et_link_libraries([YOUR TARGET] spotify-json)
export BOOST_ROOT=/path/to/boost
export BOOST_LIBRARYDIR=/path/to/boost/lib/
mkdir build
cd build
cmake -G <generator-name> ..
Run “cmake –help” for a list of generators available on your system.
cd build
ctest -j 8
This project adheres to the Open Code of Conduct. By participating, you are expected to honor this code.