Json

API reference

JSON document parsing examples

TEST(Example, ParsingSuccess_usingConstructor) {
    // In this example you will se how to parse simple documet and traverse parsed nodes tree

    const std::string document =
        "{\n"
        "  \"someNumber\": 5,\n"
        "  \"someArray\": [\n"
        "    \"string\", true\n"
        "  ],\n"
        "  \"someObject\": {\"someNull\": null}\n"
        "}";
    rili::JSON json(document);

    ASSERT_EQ(json.type(), rili::JSON::Type::Object);  // our root element should be Object type({...})
    auto const& root = json.object();
    ASSERT_EQ(root.size(), 3u);  // we have 3 elements inside root object - "someNumber", "someArray" and "someObject"

    auto rootSubElementIt = root.begin();
    ASSERT_NE(rootSubElementIt, root.end());

    EXPECT_EQ(rootSubElementIt->first, "someNumber");
    ASSERT_EQ(rootSubElementIt->second.type(), rili::JSON::Type::Number);  // value type of someNumber is Number
    EXPECT_EQ(rootSubElementIt->second.number(), "5");                     // value of someNumber is 5

    /**
    NOTE: JSON numbers are not converted to C++ POD number types because such conversion is not valid for all possible
    cases.

    JSON format do not specify number type precision, maximal and minimal values etc. - because of that you can
    theoretically have very big numbers in JSON document or numbers with precision bigger than double type can handle.

    Some of users rather would like in these cases use dedicated big numbers library to handle these like GNU MP.

    So we decided to not provide any conversions of numbers - instead we implement full number format validation defined
    in JSON specification.

    Aditional benefit is in better preformance of document parsing as converting numbers sometimes require
    non-negliable cpu power.
    */

    rootSubElementIt++;  // we are going to next element in root object
    ASSERT_NE(rootSubElementIt, root.end());

    EXPECT_EQ(rootSubElementIt->first, "someArray");
    ASSERT_EQ(rootSubElementIt->second.type(), rili::JSON::Type::Array);  // value type of someArray is Array
    auto const& array = rootSubElementIt->second.array();
    ASSERT_EQ(array.size(), 2u);                                // someArray have 2 elements
    ASSERT_EQ(array.front().type(), rili::JSON::Type::String);  // first element type is String
    ASSERT_EQ(array.back().type(), rili::JSON::Type::Boolean);  // second element type is Boolean
    EXPECT_EQ(array.front().string(), "string");                // first element value is "string"
    EXPECT_TRUE(array.back().boolean());                        // second element value is true

    rootSubElementIt++;  // we are going to next element in root object
    ASSERT_NE(rootSubElementIt, root.end());
    EXPECT_EQ(rootSubElementIt->first, "someObject");
    ASSERT_EQ(rootSubElementIt->second.type(), rili::JSON::Type::Object);    // value type of someObject is Object
    ASSERT_EQ(rootSubElementIt->second.object().size(), 1u);                 // someObject have 1 element
    EXPECT_EQ(rootSubElementIt->second.object().front().first, "someNull");  // someObject element name is "someNull"
    EXPECT_EQ(rootSubElementIt->second.object().front().second.type(),
              rili::JSON::Type::Null);  // someNull value type is Null

    rootSubElementIt++;  // we are going to next element (end)
    ASSERT_EQ(rootSubElementIt, root.end());
}

TEST(Example, ParsingSuccess_usingParseMethod) {
    const std::string document =
        "{\n"
        "  \"someNumber\": 5,\n"
        "  \"someArray\": [\n"
        "    \"string\", true\n"
        "  ],\n"
        "  \"someObject\": {\"someNull\": null}\n"
        "}";

    rili::JSON json;  // now json object should have Null type
    EXPECT_EQ(json.type(), rili::JSON::Type::Null);

    json.parse(document);
    EXPECT_EQ(json.type(),
              rili::JSON::Type::Object);  // it should be exactly the same like in ParsingSuccess_usingConstructor test
}

Parsing failure handling (rili::JSON::SyntaxError)

TEST(Example, ParsingFailure) {
    std::string document = "[1,2,3,4, \"5\", {]";  // here is invalid "{" without "}"

    try {
        rili::JSON json;
        json.parse(document);  // this shouldn't end succesfully
        ADD_FAILURE();
    } catch (rili::JSON::SyntaxError e) {
        EXPECT_EQ(e.position(), 16u);  // '{' is at 16 position in document
    } catch (...) {
        ADD_FAILURE();
    }

    EXPECT_THROW(rili::JSON::SyntaxError,
                 [&document]() { rili::JSON json(document); });  // the same like above but with constructor
}

Creating JSON document structure from scratch and serialization

TEST(Example, DataSerialization) {
    // Here you will see how to create document nodes tree from scratch(similar to document from
    // ParsingSuccess_usingConstructor) and then serialize it to JSON string.

    rili::JSON json;
    json.object({});  // we changed our root element type to Object and it should be value is empty (you can also use
                      // not empty if you want)

    /* we will create something like that:
        {
          "someNumber": 5,
          "someArray": [ "string", true ],
          "someObject": {"someNull": null}
        }
    */

    auto& root = json.object();
    root.push_back({"someNumber", rili::JSON()});  // here we create someNumber entry in root object with Null type
    root.back().second.number("5");                // here we set someNumber type to Number + value to 5

    root.push_back({"someArray", rili::JSON()});  // here we create someArray entry in root object with Null type
    root.back().second.array(
        {rili::JSON(), rili::JSON()});  // here we set someArray type to Array + value as array of two null type elemens
    auto& someArray = root.back().second.array();
    someArray.front().string("string");  // here we set first element value type to String + value equal to "string"
    someArray.back().boolean(true);      // here we set second element value type to Boolean + value equal to true

    root.push_back({"someObject", rili::JSON()});  // here we create someObject entry in root object with Null type
    root.back().second.object({{"someNull", rili::JSON()}});  // here we set someObject type to object and value as
                                                              // single null typed entry named "someNull"

    auto const document = json.stringify();  // here we will serialize our document nodes tree to JSONformated string
    EXPECT_EQ(document, "{\"someNumber\":5,\"someArray\":[\"string\",true],\"someObject\":{\"someNull\":null}}");
}

Changing document content

TEST(Example, DataModyfication) {
    // Here you will see how to parse, change and serialize changed document

    const std::string originalDocument =
        "{\"someNumber\":5,\"someArray\":[\"string\",true],\"someObject\":{\"someNull\":null}}";

    rili::JSON json(originalDocument);
    json.object().back().first = "myObject";  // here we change entry name someObject -> myObject
    json.object().back().second.object().back().first =
        "emptyString";                                              // here we change entry name someNull -> emptyString
    json.object().back().second.object().back().second.string("");  // here we change type from Null to String
    json.object().back().second.object().push_back(
        {"nonEmptyString", rili::JSON()});  // here we add entry nonEmptyString to myObject (with Null as value for now)
    json.object().back().second.object().back().second.string(
        "not empty");  // here we set nonEmptyString value as String with value "not empty"
    auto it = json.object().begin();
    it++;
    json.object().erase(it);  // here we erase someArrayEntry and all it subnodes

    auto const changedDocument = json.stringify();  // here we serialize changed document
    EXPECT_EQ(changedDocument,
              "{\"someNumber\":5,\"myObject\":{\"emptyString\":\"\",\"nonEmptyString\":\"not empty\"}}");
}