11
\$\begingroup\$

I'm creating a C++14 header-only JSON library for fun, learning experience and to use it in my personal projects.

I'm looking for feedback on:

  • Its API/interface: since I'm using modern C++, I want the API to be as expressive and easy to write/read as possible. I also want it to be terse.

  • Its implementation: I'm trying to make the library's performance acceptable, but not looking to make the "fastest" library. It would be great to get feedback on how to improve the performance. I'm also making use of move semantics and since I'm not 100% sure of my perfect forwarding functions' correctness I'm also looking for feedback on them.


The full source code is available here. The JSON library is a module of a bigger "utility" library called SSVUtils.

Here's a quick rundown of the JSON module's files:

  • Json/Json.hpp: "Main" module header file. The only file to be included by the user.
  • Json/Common/Common.hpp: Typedefs and aliases.
  • Json/Common/VecMap.hpp: Simple map-like container implementation that uses a sorted vector as the underlying storage.
  • Json/Io/*: JSON reader/writer implementations.
  • Json/Num/Num.hpp*: Data structure intended for the storage of number. Uses an union. Can store in three different representations: IntS (signed integer), IntU (unsigned integer), Real (double).
  • Json/Num/NumHelper.hpp: Helper struct to get/set Num instances from C++ numeric types.
  • Json/Val/Val.hpp: The "core" of the JSON library. A "value" object that uses an union to store one of these types:
    • Obj: Key/value JSON map.
    • Arr: JSON array.
    • Str: Generic string - implemented with std::string.
    • Num: Numeric type.
    • Bln: Boolean type;
    • Nll: Null type;
  • Json/Val/Internal/Cnv.hpp: defines conversions between C++ types and JSON structures. You can think of these conversions as serializations/de-serializations. The user can define its own conversions. Conversions do not create new values - they either fill an existing C++ object taken by non-const reference from a Json::Val, or they fill a Json::Val from an existing C++ object.
  • Json/Val/Internal/AsHelper.hpp: defines copy-conversions between C++ types and JSON structures. Usually just creates a new C++ object, fills it using its Cnv converter, then returns it.
  • Json/Val/Internal/Chk.hpp: runtime checkers to determine whether a JSON value is storing a specific C++ type.
  • Json/Val/Internal/ValItrHelper.hpp: iteration helper struct that helps with iterating over JSON values.
  • Json/Val/Internal/CnvFuncs.hpp: helper functions to automatically convert from/to a JSON value depending on its type.

Syntax usage example:

// Simple struct that we're going to serialize/deserialize
struct Person
{
    std::string name, surname;
    int age;
};

// Macro that opens/closes conversion namespace
SSVJ_CNV_NAMESPACE()
{
    // Macro that defines a converter for Person
    // `mV` is the name of the variable that will refer to the JSON value
    // `mX` is the name of the variable that will refer to the Person value
    SSVJ_CNV(Person, mV, mX)
    {
        // Converting a Person to JSON simply writes/reads a JSON array
        // containing its name, surname and age
        ssvj::cnvArr(mV, mX.name, mX.surname, mX.age);
    }
    SSVJ_CNV_END()
}
SSVJ_CNV_NAMESPACE_END()

int main()
{
    // Initialize a JSON array
    ssvj::Val people{ssvj::Arr{}}; 

    // Emplace C++ objects in the JSON array
    people.emplace<Person>("John", "Doe", 35);
    people.emplace<Person>("Bill", "Gates", 75);

    // Get copies of the C++ objects from the JSON array
    auto johnDoe(people[0].as<Person>());
    auto billGates(people[1].as<Person>());

    // Modify the copy and put it back into the JSON array
    billGates.age = 50;    
    people[1] = billGates;

    assert(people[1][0].as<std::string>() == "Bill");
    assert(people[1][1].as<std::string>() == "Gates");
    assert(people[1][2].as<int>() == 50);

    // Iterate every item of the array as a `Person`
    for(const auto& p : people.forArrAs<Person>())
    {
        std::cout << "Name: " << p.name << "\n"
                  << "Surname: " << p.surname << "\n"
                  << "Age: " << p.age << "\n\n";
    }

    // Write and read from file
    people.writeToFile("/tmp/people.json");
    auto people2(ssvj::Val::fromFile("/tmp/people.json"));

    assert(people == people2); 
}

  • What do you think about the user syntax? Can it be improved?
  • Could the implementation be improved? Are move semantics being used correctly?
  • Can the conversion of types be better both in its implementation and syntax?
\$\endgroup\$

closed as off-topic by yuri, Toby Speight, t3chb0t, Mast, Edward May 18 at 19:55

This question appears to be off-topic. The users who voted to close gave these specific reasons:

  • "Authorship of code: Since Code Review is a community where programmers improve their skills through peer review, we require that the code be posted by an author or maintainer of the code, that the code be embedded directly, and that the poster know why the code is written the way it is." – Toby Speight, Edward
  • "Lacks concrete context: Code Review requires concrete code from a project, with sufficient context for reviewers to understand how that code is used. Pseudocode, stub code, hypothetical code, obfuscated code, and generic best practices are outside the scope of this site." – yuri, t3chb0t
If this question can be reworded to fit the rules in the help center, please edit the question.

  • 3
    \$\begingroup\$ If you are interested here is my Json Serializer/DeSerializer. It allows you to declare what members of a class get serialized in one statement. Then you can just serialize/de-serialize object with a stream. Have a look at the README.md \$\endgroup\$ – Martin York Oct 15 '14 at 15:14
  • 2
    \$\begingroup\$ I think it would be better if you posted separate modules of the library for review. It is very hard for someone to review the whole library from the repo. Try posting a few specific parts on a couple other questions. Include the code in the question. You're likely to get a lot more and more complete reviews. \$\endgroup\$ – glampert Oct 16 '14 at 18:00
  • 1
    \$\begingroup\$ Relying on macros is not modern C++ in my book. There is no need for macro voodoo. Is your goal to de/serialize JSON data or manage the JSON tree? \$\endgroup\$ – aggsol Oct 20 '14 at 8:59
  • \$\begingroup\$ @CodeClown: But... I'm not relying on macros. The macros you see in the example user code exist only to make the code (much) shorter. The user is free to write the code without macros, but 3 nested namespaces must be opened. My goal is both to serialize and deserialize JSON data and also manage JSON objects directly. \$\endgroup\$ – Vittorio Romeo Oct 20 '14 at 14:34
  • 5
    \$\begingroup\$ In the question as-is right now we can only review the usage code. We can't review the library. We also can't really answer your questions. Please include the relevant library code in the question. \$\endgroup\$ – Pimgd Oct 22 '14 at 10:19
1
\$\begingroup\$

Your setup seems overly complicated. For just the de/serializing a minimal approach would be easier to understand. Especially if the the de/serializer support STL containers and streams. That requires every object to implemente a serialization interface or just the stream operator.

std::vector< Person > persons;

JsonSerializer serializer;
serializer << persons;

std::ofstream ofs;
ofs.open ("test.json");
ofs << serializer;
ofs.close();

std::string json = serializer.ToString();

JsonDeserializer deserializer(json);
deserilizer >> person;

// or just use a file stream;
std::ifstream ifs;
ifs.open ("test.txt", std::ifstream::in);

JsonDeserializer deserializer(ifs);
deserilizer >> person;

ifs.close();
\$\endgroup\$
  • \$\begingroup\$ Fixed typos, added some clarifications. \$\endgroup\$ – aggsol Oct 24 '14 at 8:29

Not the answer you're looking for? Browse other questions tagged or ask your own question.