2022-05-03 12:21:01 +00:00
|
|
|
#include <qpdf/assert_test.h>
|
|
|
|
|
2018-12-17 16:55:11 +00:00
|
|
|
#include <qpdf/JSON.hh>
|
2022-05-04 20:28:12 +00:00
|
|
|
#include <qpdf/Pipeline.hh>
|
2024-01-14 16:38:29 +00:00
|
|
|
#include <qpdf/Pl_String.hh>
|
2022-05-21 11:41:09 +00:00
|
|
|
#include <qpdf/QPDF.hh>
|
2022-05-21 20:11:42 +00:00
|
|
|
#include <qpdf/QPDFObjectHandle.hh>
|
2024-01-14 16:38:29 +00:00
|
|
|
|
2018-12-17 16:55:11 +00:00
|
|
|
#include <iostream>
|
2022-03-07 13:46:53 +00:00
|
|
|
|
2019-03-11 20:21:27 +00:00
|
|
|
static void
|
|
|
|
check(JSON const& j, std::string const& exp)
|
2018-12-17 16:55:11 +00:00
|
|
|
{
|
2018-12-25 13:29:07 +00:00
|
|
|
if (exp != j.unparse()) {
|
|
|
|
std::cout << "Got " << j.unparse() << "; wanted " << exp << "\n";
|
2018-12-17 16:55:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
test_main()
|
|
|
|
{
|
|
|
|
JSON jstr = JSON::makeString("<1>\xcf\x80<2>\xf0\x9f\xa5\x94\\\"<3>\x03\t\b\r\n<4>");
|
|
|
|
check(
|
|
|
|
jstr,
|
|
|
|
"\"<1>\xcf\x80<2>\xf0\x9f\xa5\x94\\\\\\\"<3>"
|
|
|
|
"\\u0003\\t\\b\\r\\n<4>\"");
|
|
|
|
JSON jnull = JSON::makeNull();
|
|
|
|
check(jnull, "null");
|
2022-01-19 14:31:28 +00:00
|
|
|
assert(jnull.isNull());
|
|
|
|
std::string value;
|
|
|
|
assert(!jnull.getNumber(value));
|
2018-12-17 16:55:11 +00:00
|
|
|
JSON jarr = JSON::makeArray();
|
|
|
|
check(jarr, "[]");
|
|
|
|
JSON jstr2 = JSON::makeString("a\tb");
|
2022-01-19 14:31:28 +00:00
|
|
|
assert(jstr2.getString(value));
|
|
|
|
assert(value == "a\tb");
|
|
|
|
assert(!jstr2.getNumber(value));
|
2022-01-28 14:05:06 +00:00
|
|
|
/* cSpell: ignore jbool xavalue dvalue xdvalue */
|
2018-12-17 16:55:11 +00:00
|
|
|
JSON jint = JSON::makeInt(16059);
|
|
|
|
JSON jdouble = JSON::makeReal(3.14159);
|
|
|
|
JSON jexp = JSON::makeNumber("2.1e5");
|
2022-01-19 14:31:28 +00:00
|
|
|
JSON jbool1 = JSON::makeBool(true);
|
|
|
|
JSON jbool2 = JSON::makeBool(false);
|
|
|
|
bool bvalue = false;
|
|
|
|
assert(jbool1.getBool(bvalue));
|
|
|
|
assert(bvalue);
|
|
|
|
assert(jbool2.getBool(bvalue));
|
|
|
|
assert(!bvalue);
|
2018-12-17 16:55:11 +00:00
|
|
|
jarr.addArrayElement(jstr2);
|
|
|
|
jarr.addArrayElement(jnull);
|
|
|
|
jarr.addArrayElement(jint);
|
|
|
|
jarr.addArrayElement(jdouble);
|
|
|
|
jarr.addArrayElement(jexp);
|
|
|
|
check(
|
|
|
|
jarr,
|
|
|
|
"[\n"
|
|
|
|
" \"a\\tb\",\n"
|
|
|
|
" null,\n"
|
|
|
|
" 16059,\n"
|
2021-02-12 08:44:12 +00:00
|
|
|
" 3.14159,\n"
|
2018-12-17 16:55:11 +00:00
|
|
|
" 2.1e5\n"
|
|
|
|
"]");
|
2022-01-19 14:31:28 +00:00
|
|
|
std::vector<std::string> avalue;
|
|
|
|
assert(jarr.forEachArrayItem([&avalue](JSON j) { avalue.push_back(j.unparse()); }));
|
|
|
|
std::vector<std::string> xavalue = {
|
|
|
|
"\"a\\tb\"",
|
|
|
|
"null",
|
|
|
|
"16059",
|
|
|
|
"3.14159",
|
|
|
|
"2.1e5",
|
|
|
|
};
|
|
|
|
assert(avalue == xavalue);
|
2018-12-17 16:55:11 +00:00
|
|
|
JSON jmap = JSON::makeDictionary();
|
|
|
|
check(jmap, "{}");
|
|
|
|
jmap.addDictionaryMember("b", jstr2);
|
|
|
|
jmap.addDictionaryMember("a", jarr);
|
|
|
|
jmap.addDictionaryMember("c\r\nd", jnull);
|
|
|
|
jmap.addDictionaryMember("yes", JSON::makeBool(false));
|
|
|
|
jmap.addDictionaryMember("no", JSON::makeBool(true));
|
|
|
|
jmap.addDictionaryMember("empty_dict", JSON::makeDictionary());
|
|
|
|
jmap.addDictionaryMember("empty_list", JSON::makeArray());
|
|
|
|
jmap.addDictionaryMember("single", JSON::makeArray()).addArrayElement(JSON::makeInt(12));
|
2024-01-14 16:38:51 +00:00
|
|
|
std::string jm_str;
|
|
|
|
assert(jmap.getDictItem("b").getString(jm_str));
|
|
|
|
assert(!jmap.getDictItem("b2").getString(jm_str));
|
|
|
|
assert(!jstr2.getDictItem("b").getString(jm_str));
|
|
|
|
assert(jm_str == "a\tb");
|
|
|
|
|
2018-12-17 16:55:11 +00:00
|
|
|
check(
|
|
|
|
jmap,
|
|
|
|
"{\n"
|
|
|
|
" \"a\": [\n"
|
|
|
|
" \"a\\tb\",\n"
|
|
|
|
" null,\n"
|
|
|
|
" 16059,\n"
|
2021-02-12 08:44:12 +00:00
|
|
|
" 3.14159,\n"
|
2018-12-17 16:55:11 +00:00
|
|
|
" 2.1e5\n"
|
|
|
|
" ],\n"
|
|
|
|
" \"b\": \"a\\tb\",\n"
|
|
|
|
" \"c\\r\\nd\": null,\n"
|
|
|
|
" \"empty_dict\": {},\n"
|
|
|
|
" \"empty_list\": [],\n"
|
|
|
|
" \"no\": true,\n"
|
|
|
|
" \"single\": [\n"
|
|
|
|
" 12\n"
|
|
|
|
" ],\n"
|
|
|
|
" \"yes\": false\n"
|
|
|
|
"}");
|
2022-05-07 11:53:45 +00:00
|
|
|
for (int i = 1; i <= JSON::LATEST; ++i) {
|
|
|
|
check(QPDFObjectHandle::newReal("0.12").getJSON(i), "0.12");
|
|
|
|
check(QPDFObjectHandle::newReal(".34").getJSON(i), "0.34");
|
|
|
|
check(QPDFObjectHandle::newReal("-0.56").getJSON(i), "-0.56");
|
|
|
|
check(QPDFObjectHandle::newReal("-.78").getJSON(i), "-0.78");
|
2024-08-05 23:21:23 +00:00
|
|
|
check(QPDFObjectHandle::newReal("-78.").getJSON(i), "-78.0");
|
2022-05-07 11:53:45 +00:00
|
|
|
}
|
2022-01-19 14:31:28 +00:00
|
|
|
JSON jmap2 = JSON::parse(R"({"a": 1, "b": "two", "c": [true]})");
|
|
|
|
std::map<std::string, std::string> dvalue;
|
|
|
|
assert(jmap2.forEachDictItem(
|
|
|
|
[&dvalue](std::string const& k, JSON j) { dvalue[k] = j.unparse(); }));
|
|
|
|
std::map<std::string, std::string> xdvalue = {
|
|
|
|
{"a", "1"},
|
|
|
|
{"b", "\"two\""},
|
|
|
|
{"c", "[\n true\n]"},
|
|
|
|
};
|
|
|
|
assert(dvalue == xdvalue);
|
2022-05-04 20:28:12 +00:00
|
|
|
auto blob_data = [](Pipeline* p) { *p << "\x01\x02\x03\x04\x05\xff\xfe\xfd\xfc\xfb"; };
|
|
|
|
JSON jblob = JSON::makeDictionary();
|
|
|
|
jblob.addDictionaryMember("normal", JSON::parse(R"("string")"));
|
|
|
|
jblob.addDictionaryMember("blob", JSON::makeBlob(blob_data));
|
|
|
|
// cSpell:ignore AQIDBAX
|
|
|
|
check(
|
|
|
|
jblob,
|
|
|
|
"{\n"
|
|
|
|
" \"blob\": \"AQIDBAX//v38+w==\",\n"
|
|
|
|
" \"normal\": \"string\"\n"
|
|
|
|
"}");
|
2024-01-14 16:38:29 +00:00
|
|
|
|
2024-02-06 20:30:29 +00:00
|
|
|
try {
|
|
|
|
JSON::parse("\"");
|
|
|
|
assert(false);
|
|
|
|
} catch (std::runtime_error&) {
|
|
|
|
}
|
|
|
|
|
2024-01-14 16:38:29 +00:00
|
|
|
// Check default constructed JSON object (order as per JSON.hh).
|
|
|
|
JSON uninitialized;
|
|
|
|
std::string ws;
|
2024-02-04 21:11:53 +00:00
|
|
|
auto pl = Pl_String("", nullptr, ws);
|
2024-01-14 16:38:29 +00:00
|
|
|
uninitialized.write(&pl);
|
|
|
|
assert(ws == "null");
|
|
|
|
assert(uninitialized.unparse() == "null");
|
|
|
|
try {
|
|
|
|
uninitialized.addDictionaryMember("key", jarr);
|
|
|
|
assert(false);
|
|
|
|
} catch (std::runtime_error&) {
|
|
|
|
}
|
|
|
|
assert(jmap.addDictionaryMember("42", uninitialized).isNull());
|
|
|
|
try {
|
|
|
|
uninitialized.addArrayElement(jarr);
|
|
|
|
assert(false);
|
|
|
|
} catch (std::runtime_error&) {
|
|
|
|
}
|
|
|
|
assert(jarr.addArrayElement(uninitialized).isNull());
|
|
|
|
assert(!uninitialized.isArray());
|
|
|
|
assert(!uninitialized.isDictionary());
|
|
|
|
try {
|
|
|
|
uninitialized.checkDictionaryKeySeen("key");
|
|
|
|
assert(false);
|
|
|
|
} catch (std::logic_error&) {
|
|
|
|
}
|
|
|
|
std::string st_out = "unchanged";
|
|
|
|
assert(!uninitialized.getString(st_out));
|
|
|
|
assert(!uninitialized.getNumber(st_out));
|
|
|
|
bool b_out = true;
|
|
|
|
assert(!uninitialized.getBool(b_out));
|
|
|
|
assert(b_out && st_out == "unchanged");
|
|
|
|
assert(!uninitialized.isNull());
|
|
|
|
assert(uninitialized.getDictItem("42").isNull());
|
|
|
|
assert(!uninitialized.forEachDictItem([](auto k, auto v) {}));
|
|
|
|
assert(!uninitialized.forEachArrayItem([](auto v) {}));
|
|
|
|
std::list<std::string> e;
|
|
|
|
assert(!uninitialized.checkSchema(JSON(), 0, e));
|
|
|
|
assert(!uninitialized.checkSchema(JSON(), e));
|
|
|
|
assert(e.empty());
|
|
|
|
uninitialized.setStart(0);
|
|
|
|
uninitialized.setEnd(0);
|
|
|
|
assert(uninitialized.getStart() == 0);
|
|
|
|
assert(uninitialized.getEnd() == 0);
|
2018-12-17 16:55:11 +00:00
|
|
|
}
|
|
|
|
|
2022-01-22 19:33:26 +00:00
|
|
|
static void
|
|
|
|
check_schema(JSON& obj, JSON& schema, unsigned long flags, bool exp, std::string const& description)
|
2018-12-17 16:55:11 +00:00
|
|
|
{
|
|
|
|
std::list<std::string> errors;
|
|
|
|
std::cout << "--- " << description << std::endl;
|
2022-01-22 19:33:26 +00:00
|
|
|
assert(exp == obj.checkSchema(schema, flags, errors));
|
2022-04-30 17:23:18 +00:00
|
|
|
for (auto const& error: errors) {
|
|
|
|
std::cout << error << std::endl;
|
2018-12-17 16:55:11 +00:00
|
|
|
}
|
|
|
|
std::cout << "---" << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
test_schema()
|
|
|
|
{
|
2022-01-28 14:05:06 +00:00
|
|
|
/* cSpell: ignore ptional ebra */
|
2022-01-17 23:40:38 +00:00
|
|
|
JSON schema = JSON::parse(R"(
|
|
|
|
{
|
|
|
|
"one": {
|
|
|
|
"a": {
|
|
|
|
"q": "queue",
|
|
|
|
"r": {
|
2022-01-22 19:33:26 +00:00
|
|
|
"x": "ecks"
|
2022-01-17 23:40:38 +00:00
|
|
|
},
|
|
|
|
"s": [
|
2022-07-24 20:17:03 +00:00
|
|
|
{
|
|
|
|
"ss": "esses"
|
|
|
|
}
|
2022-01-17 23:40:38 +00:00
|
|
|
]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"two": [
|
|
|
|
{
|
|
|
|
"goose": "gander",
|
|
|
|
"glarp": "enspliel"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"three": {
|
|
|
|
"<objid>": {
|
|
|
|
"z": "ebra",
|
2022-01-22 19:33:26 +00:00
|
|
|
"o": "ptional"
|
2022-01-17 23:40:38 +00:00
|
|
|
}
|
2022-07-24 20:44:51 +00:00
|
|
|
},
|
|
|
|
"four": [
|
|
|
|
{ "first": "first element" },
|
|
|
|
{ "second": "second element" }
|
|
|
|
]
|
2022-01-17 23:40:38 +00:00
|
|
|
}
|
|
|
|
)");
|
|
|
|
|
|
|
|
JSON a = JSON::parse(R"(["not a", "dictionary"])");
|
2022-01-22 19:33:26 +00:00
|
|
|
check_schema(a, schema, 0, false, "top-level type mismatch");
|
2022-01-17 23:40:38 +00:00
|
|
|
JSON b = JSON::parse(R"(
|
|
|
|
{
|
|
|
|
"one": {
|
|
|
|
"a": {
|
|
|
|
"t": "oops",
|
|
|
|
"r": [
|
|
|
|
"x",
|
|
|
|
"ecks",
|
|
|
|
"y",
|
|
|
|
"why"
|
|
|
|
],
|
|
|
|
"s": {
|
|
|
|
"z": "esses"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"two": [
|
|
|
|
{
|
|
|
|
"goose": "0 gander",
|
|
|
|
"glarp": "0 enspliel"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"goose": "1 gander",
|
|
|
|
"flarp": "1 enspliel"
|
|
|
|
},
|
|
|
|
2,
|
|
|
|
[
|
|
|
|
"three"
|
|
|
|
],
|
|
|
|
{
|
|
|
|
"goose": "4 gander",
|
|
|
|
"glarp": 4
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"three": {
|
|
|
|
"anything": {
|
|
|
|
"x": "oops",
|
|
|
|
"o": "okay"
|
|
|
|
},
|
|
|
|
"else": {
|
|
|
|
"z": "okay"
|
|
|
|
}
|
2022-07-24 20:44:51 +00:00
|
|
|
},
|
|
|
|
"four": [
|
|
|
|
{"first": "missing second"}
|
|
|
|
]
|
2022-01-17 23:40:38 +00:00
|
|
|
}
|
|
|
|
)");
|
|
|
|
|
2022-01-22 19:33:26 +00:00
|
|
|
check_schema(b, schema, 0, false, "missing items");
|
|
|
|
|
|
|
|
JSON bad_schema = JSON::parse(R"({"a": true, "b": "potato?"})");
|
|
|
|
check_schema(bad_schema, bad_schema, 0, false, "bad schema field type");
|
|
|
|
|
2022-07-24 20:44:51 +00:00
|
|
|
JSON c = JSON::parse(R"(
|
|
|
|
{
|
|
|
|
"four": [
|
|
|
|
{ "first": 1 },
|
|
|
|
{ "oops": [2] }
|
|
|
|
]
|
|
|
|
}
|
|
|
|
)");
|
|
|
|
check_schema(c, schema, JSON::f_optional, false, "array element mismatch");
|
|
|
|
|
2022-07-24 20:17:03 +00:00
|
|
|
// "two" exercises the case of the JSON containing a single
|
|
|
|
// element where the schema has an array.
|
2022-01-22 19:33:26 +00:00
|
|
|
JSON good = JSON::parse(R"(
|
|
|
|
{
|
|
|
|
"one": {
|
|
|
|
"a": {
|
|
|
|
"q": "potato",
|
|
|
|
"r": {
|
|
|
|
"x": [1, null]
|
|
|
|
},
|
|
|
|
"s": [
|
2022-07-24 20:17:03 +00:00
|
|
|
{"ss": null},
|
|
|
|
{"ss": "anything"}
|
2022-01-22 19:33:26 +00:00
|
|
|
]
|
|
|
|
}
|
|
|
|
},
|
2022-07-24 20:17:03 +00:00
|
|
|
"two": {
|
|
|
|
"glarp": "enspliel",
|
|
|
|
"goose": 3.14
|
|
|
|
},
|
2022-01-22 19:33:26 +00:00
|
|
|
"three": {
|
|
|
|
"<objid>": {
|
|
|
|
"z": "ebra"
|
|
|
|
}
|
2022-07-24 20:44:51 +00:00
|
|
|
},
|
|
|
|
"four": [
|
|
|
|
{ "first": 1 },
|
|
|
|
{ "second": [2] }
|
|
|
|
]
|
2022-01-22 19:33:26 +00:00
|
|
|
}
|
|
|
|
)");
|
|
|
|
check_schema(good, schema, 0, false, "not optional");
|
|
|
|
check_schema(good, schema, JSON::f_optional, true, "pass");
|
2018-12-17 16:55:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
main()
|
|
|
|
{
|
|
|
|
test_main();
|
|
|
|
test_schema();
|
2022-05-21 11:41:09 +00:00
|
|
|
assert(QPDF::test_json_validators());
|
2018-12-17 16:55:11 +00:00
|
|
|
|
|
|
|
std::cout << "end of json tests\n";
|
|
|
|
return 0;
|
|
|
|
}
|